From 069fe93f74b77edd75e2ddda0ba30d768d938248 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Sep 2021 21:50:32 +0200 Subject: [PATCH 0001/1133] Update links to v1.19 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index 87078b71..69475f2c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -268,10 +268,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.18`][direct-scrcpy-server] - _(SHA-256: 641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3)_ + - [`scrcpy-server-v1.19`][direct-scrcpy-server] + _(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 1cd70a83..b9da20b5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.18) +# scrcpy (v1.19) [Read in another language](#translations) @@ -88,10 +88,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.18.zip`][direct-win64] - _(SHA-256: 37212f5087fe6f3e258f1d44fa5c02207496b30e1d7ec442cbcf8358910a5c63)_ + - [`scrcpy-win64-v1.19.zip`][direct-win64] + _(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-win64-v1.18.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-win64-v1.19.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index 9158bdd4..dcb254f9 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18 -PREBUILT_SERVER_SHA256=641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 +PREBUILT_SERVER_SHA256=876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From fa100b814b0d1866dbf30efbcfd24334dc8ddce2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Sep 2021 10:46:25 +0200 Subject: [PATCH 0002/1133] Add support for expandNotificationsPanel() variant Some custom vendor ROM added an int as a parameter. Fixes #2551 --- .../scrcpy/wrappers/StatusBarManager.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java index 5b1e5f5e..7a19e6e5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -11,6 +11,7 @@ public class StatusBarManager { private final IInterface manager; private Method expandNotificationsPanelMethod; + private boolean expandNotificationPanelMethodCustomVersion; private Method expandSettingsPanelMethod; private boolean expandSettingsPanelMethodNewVersion = true; private Method collapsePanelsMethod; @@ -21,7 +22,13 @@ public class StatusBarManager { private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException { if (expandNotificationsPanelMethod == null) { - expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); + try { + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); + } catch (NoSuchMethodException e) { + // Custom version for custom vendor ROM: + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel", int.class); + expandNotificationPanelMethodCustomVersion = true; + } } return expandNotificationsPanelMethod; } @@ -50,7 +57,11 @@ public class StatusBarManager { public void expandNotificationsPanel() { try { Method method = getExpandNotificationsPanelMethod(); - method.invoke(manager); + if (expandNotificationPanelMethodCustomVersion) { + method.invoke(manager, 0); + } else { + method.invoke(manager); + } } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); } From a846c01883e15cbf04ec2c76c07c7e0f074306b2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Sep 2021 21:36:21 +0200 Subject: [PATCH 0003/1133] Fix link in README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b9da20b5..1a964e41 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,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: From 31131039bbb0d1840c64a0be79edfc3d5531bfb1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Sep 2021 18:27:37 +0200 Subject: [PATCH 0004/1133] Add missing includes Refs #2616 --- app/src/decoder.c | 1 + app/src/recorder.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/src/decoder.c b/app/src/decoder.c index aa5018b3..7c67e836 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -1,5 +1,6 @@ #include "decoder.h" +#include #include #include "events.h" diff --git a/app/src/recorder.c b/app/src/recorder.c index c98b6b8c..3b5fe070 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -1,6 +1,8 @@ #include "recorder.h" #include +#include +#include #include #include "util/log.h" From 8df42cec82cb5856e6c10a4089e6b2953310da6e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Sep 2021 15:33:17 +0200 Subject: [PATCH 0005/1133] 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 (again) Fixes #2656 --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 2f7109c5..f98c53d0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -56,17 +56,13 @@ public class ScreenEncoder implements Device.RotationListener { public void streamScreen(Device device, FileDescriptor fd) throws IOException { Workarounds.prepareMainLooper(); - - try { - internalStreamScreen(device, fd); - } catch (NullPointerException e) { - // Retry with workarounds enabled: - // - // - Ln.d("Applying workarounds to avoid NullPointerException"); + if (Build.BRAND.equalsIgnoreCase("meizu")) { + // + // Workarounds.fillAppInfo(); - internalStreamScreen(device, fd); } + + internalStreamScreen(device, fd); } private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { From 63b2b5ca4e2cca5165e2aede0ad8a8de659f0942 Mon Sep 17 00:00:00 2001 From: Alberto Pasqualetto <39854348+albertopasqualetto@users.noreply.github.com> Date: Tue, 21 Sep 2021 01:35:55 +0200 Subject: [PATCH 0006/1133] Update italian translation to v1.19 PR #2650 Signed-off-by: Romain Vimont --- FAQ.it.md | 18 ++++++++ FAQ.md | 2 +- README.it.md | 121 ++++++++++++++++++++++++++++++++++++++++----------- README.md | 2 +- 4 files changed, 116 insertions(+), 27 deletions(-) diff --git a/FAQ.it.md b/FAQ.it.md index 5c5830ce..0da656c0 100644 --- a/FAQ.it.md +++ b/FAQ.it.md @@ -140,6 +140,24 @@ Potresti anche dover configurare il [comportamento di ridimensionamento][scaling [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 +### Problema con Wayland + +Per impostazione predefinita, SDL utilizza x11 su Linux. Il [video driver] può essere cambiato attraversio la variabile d'ambiente `SDL_VIDEODRIVER`: + +[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver + +```bash +export SDL_VIDEODRIVER=wayland +scrcpy +``` + +Su alcune distribuzioni (almeno Fedora), il pacchetto `libdecor` deve essere installato manualmente. + +Vedi le issues [#2554] e [#2559]. + +[#2554]: https://github.com/Genymobile/scrcpy/issues/2554 +[#2559]: https://github.com/Genymobile/scrcpy/issues/2559 + ### Crash del compositore KWin diff --git a/FAQ.md b/FAQ.md index f74845a5..6a0aabae 100644 --- a/FAQ.md +++ b/FAQ.md @@ -258,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) diff --git a/README.it.md b/README.it.md index 37416f1d..6b5d6884 100644 --- a/README.it.md +++ b/README.it.md @@ -1,6 +1,6 @@ _Apri il [README](README.md) originale e sempre aggiornato._ -# scrcpy (v1.17) +# scrcpy (v1.19) Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_. Funziona su _GNU/Linux_, _Windows_ e _macOS_. @@ -205,10 +205,11 @@ Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il Per bloccare l'orientamento della trasmissione: ```bash -scrcpy --lock-video-orientation 0 # orientamento naturale -scrcpy --lock-video-orientation 1 # 90° antiorario -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 90° orario +scrcpy --lock-video-orientation # orientamento iniziale (corrente) +scrcpy --lock-video-orientation=0 # orientamento naturale +scrcpy --lock-video-orientation=1 # 90° antiorario +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 90° orario ``` Questo influisce sull'orientamento della registrazione. @@ -231,7 +232,9 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n scrcpy --encoder _ ``` -### Registrazione +### Cattura + +#### Registrazione È possibile registrare lo schermo durante la trasmissione: @@ -253,6 +256,75 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation +#### v4l2loopback + +Su Linux è possibile inviare il flusso video ad un dispositivo v4l2 loopback, cosicchè un dispositivo Android possa essere aperto come una webcam da qualsiasi strumento compatibile con v4l2. + +Il modulo `v4l2loopback` deve essere installato: + +```bash +sudo apt install v4l2loopback-dkms +``` + +Per creare un dispositvo v4l2: + +```bash +sudo modprobe v4l2loopback +``` + +Questo creerà un nuovo dispositivo video in `/dev/videoN` dove `N` è un intero (più [opzioni](https://github.com/umlaeute/v4l2loopback#options) sono disponibili per crere più dispositivi o dispositivi con ID specifici). + +Per elencare i dispositvi attivati: + +```bash +# necessita del pacchetto v4l-utils +v4l2-ctl --list-devices + +# semplice ma potrebbe essere sufficiente +ls /dev/video* +``` + +Per avviare scrcpy utilizzando un v4l2 sink: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # disabilita la finestra di trasmissione +scrcpy --v4l2-sink=/dev/videoN -N # versione corta +``` + +(sostituisci `N` con l'ID del dispositivo, controlla con `ls /dev/video*`) + +Una volta abilitato, puoi aprire il tuo flusso video con uno strumento compatibile con v4l2: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC potrebbe aggiungere del ritardo per il buffer +``` + +Per esempio potresti catturare il video in [OBS]. + +[OBS]: https://obsproject.com/ + + +#### Buffering + +È possibile aggiungere del buffer. Questo aumenta la latenza ma riduce il jitter (vedi [#2464]). + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +L'opzione è disponibile per il buffer della visualizzazione: + +```bash +scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione +``` + +e per il V4L2 sink: + +```bash +scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink +``` + + ### Connessione #### Wireless @@ -479,15 +551,6 @@ scrcpy --turn-screen-off --stay-awake scrcpy -Sw ``` -#### Renderizzare i fotogrammi scaduti - -Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti. - -Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare: - -```bash -scrcpy --render-expired-frames -``` #### Mostrare i tocchi @@ -607,14 +670,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console. #### Trasferimento di file verso il dispositivo -Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_. +Per trasferire un file in `/sdcard/Download` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_. Non c'è alcuna risposta visiva, un log è stampato nella console. La cartella di destinazione può essere cambiata all'avvio: ```bash -scrcpy --push-target=/sdcard/Download/ +scrcpy --push-target=/sdcard/Movies/ ``` @@ -653,10 +716,10 @@ _[Super] è il pulsante Windows o Cmd._ | Rotazione schermo a sinistra | MOD+ _(sinistra)_ | Rotazione schermo a destra | MOD+ _(destra)_ | Ridimensiona finestra a 1:1 (pixel-perfect) | MOD+g - | Ridimensiona la finestra per rimuovere i bordi neri | MOD+w \| _Doppio click¹_ + | Ridimensiona la finestra per rimuovere i bordi neri | MOD+w \| _Doppio click sinistro¹_ | Premi il tasto `HOME` | MOD+h \| _Click centrale_ | Premi il tasto `BACK` | MOD+b \| _Click destro²_ - | Premi il tasto `APP_SWITCH` | MOD+s + | Premi il tasto `APP_SWITCH` | MOD+s \| _4° click³_ | Premi il tasto `MENU` (sblocca lo schermo) | MOD+m | Premi il tasto `VOLUME_UP` | MOD+ _(su)_ | Premi il tasto `VOLUME_DOWN` | MOD+ _(giù)_ @@ -665,18 +728,26 @@ _[Super] è il pulsante Windows o Cmd._ | Spegni lo schermo del dispositivo (continua a trasmettere) | MOD+o | Accendi lo schermo del dispositivo | MOD+Shift+o | Ruota lo schermo del dispositivo | MOD+r - | Espandi il pannello delle notifiche | MOD+n - | Chiudi il pannello delle notifiche | MOD+Shift+n - | Copia negli appunti³ | MOD+c - | Taglia negli appunti³ | MOD+x - | Sincronizza gli appunti e incolla³ | MOD+v + | Espandi il pannello delle notifiche | MOD+n \| _5° click³_ + | Espandi il pannello delle impostazioni | MOD+n+n \| _Doppio 5° click³_ + | Chiudi pannelli | MOD+Shift+n + | Copia negli appunti⁴ | MOD+c + | Taglia negli appunti⁴ | MOD+x + | Sincronizza gli appunti e incolla⁴ | MOD+v | Inietta il testo degli appunti del computer | MOD+Shift+v | Abilita/Disabilita il contatore FPS (su stdout) | MOD+i | Pizzica per zoomare | Ctrl+_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 MOD. +2. Poi premi due volte n. +3. Infine rilascia MOD. Tutte le scorciatoie Ctrl+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva. diff --git a/README.md b/README.md index 1a964e41..ce8effd3 100644 --- a/README.md +++ b/README.md @@ -888,7 +888,7 @@ 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) From 96e3963afceb526cef8c099060ef32856180b0b2 Mon Sep 17 00:00:00 2001 From: Pedro Miguel A Carraro Date: Wed, 6 Oct 2021 13:19:40 -0300 Subject: [PATCH 0007/1133] Update Readme.pt-br.md to v1.19 PR #2686 Signed-off-by: Romain Vimont --- README.md | 2 +- README.pt-br.md | 172 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 131 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index ce8effd3..81410b80 100644 --- a/README.md +++ b/README.md @@ -891,7 +891,7 @@ This README is available in other languages: - [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) diff --git a/README.pt-br.md b/README.pt-br.md index 3549f0fb..cdfeafeb 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -1,6 +1,6 @@ _Apenas o [README](README.md) original é garantido estar atualizado._ -# scrcpy (v1.17) +# scrcpy (v1.19) Esta aplicação fornece exibição e controle de dispositivos Android conectados via USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_. @@ -38,6 +38,18 @@ controlá-lo usando teclado e mouse. Packaging status +### 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 +brew install android-platform-tools +``` -# Homebrew < 2.6.0 -brew cask install android-platform-tools +Está também disponivel em [MacPorts], que prepara o adb para você: + +```bash +sudo port install scrcpy ``` +[MacPorts]: https://www.macports.org/ + + Você também pode [compilar o app manualmente][BUILD]. @@ -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 @@ _[Super] é tipicamente a tecla Windows ou Cmd. | Mudar modo de tela cheia | MOD+f | Rotacionar display para esquerda | MOD+ _(esquerda)_ | Rotacionar display para direita | MOD+ _(direita)_ - | Redimensionar janela para 1:1 (pixel-perfect) | MOD+g - | Redimensionar janela para remover bordas pretas | MOD+w \| _Clique-duplo¹_ + | Redimensionar janela para 1:1 (pixel-perfeito) | MOD+g + | Redimensionar janela para remover bordas pretas | MOD+w \| _Clique-duplo-esquerdo¹_ | Clicar em `HOME` | MOD+h \| _Clique-do-meio_ | Clicar em `BACK` | MOD+b \| _Clique-direito²_ - | Clicar em `APP_SWITCH` | MOD+s - | Clicar em `MENU` (desbloquear tela | MOD+m + | Clicar em `APP_SWITCH` | MOD+s \| _Clique-do-4.°³_ + | Clicar em `MENU` (desbloquear tela) | MOD+m | Clicar em `VOLUME_UP` | MOD+ _(cima)_ | Clicar em `VOLUME_DOWN` | MOD+ _(baixo)_ | Clicar em `POWER` | MOD+p @@ -707,18 +786,27 @@ _[Super] é tipicamente a tecla Windows ou Cmd. | Desligar tela do dispositivo (continuar espelhando) | MOD+o | Ligar tela do dispositivo | MOD+Shift+o | Rotacionar tela do dispositivo | MOD+r - | Expandir painel de notificação | MOD+n - | Colapsar painel de notificação | MOD+Shift+n - | Copiar para área de transferência³ | MOD+c - | Recortar para área de transferência³ | MOD+x - | Sincronizar áreas de transferência e colar³ | MOD+v + | Expandir painel de notificação | MOD+n \| _Clique-do-5.°³_ + | Expandir painel de configurção | MOD+n+n \| _Clique-duplo-do-5.°³_ + | Colapsar paineis | MOD+Shift+n + | Copiar para área de transferência⁴ | MOD+c + | Recortar para área de transferência⁴ | MOD+x + | Sincronizar áreas de transferência e colar⁴ | MOD+v | Injetar texto da área de transferência do computador | MOD+Shift+v | Ativar/desativar contador de FPS (em stdout) | MOD+i - | Pinçar para dar zoom | Ctrl+_clicar-e-mover_ + | Pinçar para dar zoom | Ctrl+_Clicar-e-mover_ + +_¹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": -_¹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._ + 1. Mantenha pressionado MOD. + 2. Depois click duas vezes n. + 3. Finalmente, solte MOD. Todos os atalhos Ctrl+_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 From 07d75eb336264cab4eb4cabfde482ce0104eba2b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Oct 2021 16:21:01 +0200 Subject: [PATCH 0008/1133] Simplify net_send_all() There is no need to declare the variable before the loop. --- app/src/util/net.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index 17299424..2b5a0e5e 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -116,9 +116,8 @@ net_send(socket_t socket, const void *buf, size_t len) { ssize_t net_send_all(socket_t socket, const void *buf, size_t len) { size_t copied = 0; - ssize_t w = 0; while (len > 0) { - w = send(socket, buf, len, 0); + ssize_t w = send(socket, buf, len, 0); if (w == -1) { return copied ? (ssize_t) copied : -1; } From 46d3e35c3079ede0ec4f92db9e35939d45aa3898 Mon Sep 17 00:00:00 2001 From: zhongkaizhu Date: Wed, 20 Oct 2021 19:35:58 +0800 Subject: [PATCH 0009/1133] Fix "Could not find 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 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/v4l2_sink.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index cae3eee9..95e20541 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -183,8 +183,11 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { goto error_mutex_destroy; } - // FIXME - const AVOutputFormat *format = find_muxer("video4linux2,v4l2"); + const AVOutputFormat *format = find_muxer("v4l2"); + if (!format) { + // Alternative name + format = find_muxer("video4linux2"); + } if (!format) { LOGE("Could not find v4l2 muxer"); goto error_cond_destroy; From a7e41b0f85f3babb8916f9728d1d2c3e60aa40c8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 21 Oct 2021 18:36:16 +0200 Subject: [PATCH 0010/1133] Fix code style --- app/src/controller.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 17844c98..b85ac02d 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -47,7 +47,7 @@ controller_destroy(struct controller *controller) { bool controller_push_msg(struct controller *controller, - const struct control_msg *msg) { + const struct control_msg *msg) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { control_msg_log(msg); } @@ -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; } From 7229e3cce033591127148523197c9fd56a1ab78d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH 0011/1133] Extract util function to build a local file path Finding a local file in the scrcpy directory may be useful for files other than scrcpy-server in the future. --- app/src/server.c | 40 ++-------------------------------------- app/src/util/process.c | 42 ++++++++++++++++++++++++++++++++++++++++++ app/src/util/process.h | 5 +++++ 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index e3c8c344..7c0d3d3f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -51,48 +50,13 @@ get_server_path(void) { // the absolute path is hardcoded return server_path; #else - - // 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); - } - - // dirname() does not work correctly everywhere, so get the parent - // directory manually. - // See - char *p = strrchr(executable_path, PATH_SEPARATOR); - if (!p) { - LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')", - executable_path, PATH_SEPARATOR); - free(executable_path); - return strdup(SERVER_FILENAME); - } - - *p = '\0'; // modify executable_path in place - char *dir = executable_path; - size_t dirlen = strlen(dir); - - // sizeof(SERVER_FILENAME) gives statically the size including the null byte - size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); - char *server_path = malloc(len); + char *server_path = get_local_file_path(SERVER_FILENAME); if (!server_path) { - LOGE("Could not alloc server path string, " + LOGE("Could not get local file path, " "using " SERVER_FILENAME " from current directory"); - free(executable_path); return strdup(SERVER_FILENAME); } - memcpy(server_path, dir, dirlen); - server_path[dirlen] = PATH_SEPARATOR; - memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); - // the final null byte has been copied with SERVER_FILENAME - - free(executable_path); - LOGD("Using server (portable): %s", server_path); return server_path; #endif diff --git a/app/src/util/process.c b/app/src/util/process.c index 5edeeee6..a9af4d67 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -1,5 +1,6 @@ #include "process.h" +#include #include "log.h" bool @@ -19,3 +20,44 @@ 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 + 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; +} diff --git a/app/src/util/process.h b/app/src/util/process.h index 7838a848..6aca6bf5 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -74,6 +74,11 @@ 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); From 156d958e77763428eee48e3433af4d7f5344f00c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 Oct 2021 18:08:31 +0200 Subject: [PATCH 0012/1133] Move common instruction out of ifdef Both ifdef-branches return server_path. --- app/src/server.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 7c0d3d3f..4c1a43f5 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -47,8 +47,6 @@ 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) { @@ -58,8 +56,9 @@ get_server_path(void) { } LOGD("Using server (portable): %s", server_path); - return server_path; #endif + + return server_path; } static bool From 0e4564da03df723dfe799cbcf15d26b3bdaaf6a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH 0013/1133] Add icon loader Add helper to load icons from image files via FFmpeg. --- app/meson.build | 1 + app/src/icon.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++ app/src/icon.h | 16 ++++ 3 files changed, 264 insertions(+) create mode 100644 app/src/icon.c create mode 100644 app/src/icon.h diff --git a/app/meson.build b/app/meson.build index f5345803..698120a5 100644 --- a/app/meson.build +++ b/app/meson.build @@ -9,6 +9,7 @@ src = [ 'src/decoder.c', 'src/device_msg.c', 'src/event_converter.c', + 'src/icon.c', 'src/file_handler.c', 'src/fps_counter.c', 'src/frame_buffer.c', diff --git a/app/src/icon.c b/app/src/icon.c new file mode 100644 index 00000000..119c5eeb --- /dev/null +++ b/app/src/icon.c @@ -0,0 +1,247 @@ +#include "icon.h" + +#include +#include +#include +#include +#include + +#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; + 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_rgb = desc->flags & AV_PIX_FMT_FLAG_RGB + && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); + if (!is_packed_rgb) { + LOGE("Could not load non-RGB 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; + } + + 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); +} diff --git a/app/src/icon.h b/app/src/icon.h new file mode 100644 index 00000000..8df53671 --- /dev/null +++ b/app/src/icon.h @@ -0,0 +1,16 @@ +#ifndef ICON_H +#define ICON_H + +#include "common.h" + +#include +#include +#include + +SDL_Surface * +scrcpy_icon_load(void); + +void +scrcpy_icon_destroy(SDL_Surface *icon); + +#endif From 12ed2f24020b95e0699f48864962da819414fd44 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH 0014/1133] Add support for palette icon formats To support more icon formats. --- app/src/icon.c | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/app/src/icon.c b/app/src/icon.c index 119c5eeb..607c7162 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -173,6 +173,7 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) { 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; } } @@ -190,10 +191,9 @@ load_from_path(const char *path) { goto error; } - bool is_packed_rgb = desc->flags & AV_PIX_FMT_FLAG_RGB - && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); - if (!is_packed_rgb) { - LOGE("Could not load non-RGB icon"); + bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR); + if (!is_packed) { + LOGE("Could not load non-packed icon"); goto error; } @@ -217,6 +217,41 @@ load_from_path(const char *path) { 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). + // +#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; From 6004f0b6b069ef8c97e00a7b6f240413e24b28d7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH 0015/1133] Use a new scrcpy icon Use the new icon designed by @varlesh: Load it from a PNG file (SDL only supports bitmap icons). --- app/meson.build | 3 +++ app/src/screen.c | 7 +++---- data/icon.png | Bin 0 -> 6530 bytes release.mk | 2 ++ run | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 data/icon.png diff --git a/app/meson.build b/app/meson.build index 698120a5..b842bd04 100644 --- a/app/meson.build +++ b/app/meson.build @@ -151,6 +151,9 @@ 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 diff --git a/app/src/screen.c b/app/src/screen.c index 3cd4329f..8a2748a9 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -5,9 +5,8 @@ #include #include "events.h" -#include "icon.xpm" +#include "icon.h" #include "scrcpy.h" -#include "tiny_xpm.h" #include "video_buffer.h" #include "util/log.h" @@ -405,10 +404,10 @@ screen_init(struct screen *screen, const struct screen_params *params) { LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } - SDL_Surface *icon = read_xpm(icon_xpm); + SDL_Surface *icon = scrcpy_icon_load(); if (icon) { SDL_SetWindowIcon(screen->window, icon); - SDL_FreeSurface(icon); + scrcpy_icon_destroy(icon); } else { LOGW("Could not load icon"); } diff --git a/data/icon.png b/data/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b96a1aff9b842976bae652c6fc22af7daed8bbf2 GIT binary patch literal 6530 zcmc&(2Uk-~w?3hS7J9_cq`Wi{F$hwm7pay=5u_y)0YQ2PfgnvfqIBs<6_BEUfJ9md zD!q4*PNX+O$<2Ge`w#A2>#lXq%$z;5_RKo7XFq#C6ODMNMNi8?3jhGUj<$wA0DwqM z5I_wkRpuV0b^ySxqobi}=r{Ezo5lsnehGG?Yp7BXu7*ZeVguXQQ7 zkNmGCQK(T*o+^jm?*;kltnd!4cDAdTpnRw+(4>aA_ufUSdHrZx%d|8q$ z%kLUl5Q%bao(g0-C%!uu;|-%Had=zATf%HjWm{@^wyNzBKLIXemS+y*d0*_~9U9j>^nHLt{OL#l~b5g(HojhO3ktgf}+AU6J#NIF>RgWOVU*S0?2`_in;1aA(j1oOj~2|$iZRa zevm$KF;MHw0Ly!e;wm7y&a^H#_Ar}SLoC2t5zNHhs4>5?woOk)i~c)vW&Np)08;>{ zM%0u*H-X{F#NMG608%^d6m3y{G>8YuYEcDyAUpXfm;jmu)1^%PL{KNi%ih_m6pX<0 zNHTMSatU+}bF9+61t5u`>iQ^5MVQz$%_IdBg*oZo0WYkd*dKRlUF8bw3yt*Oi%Yt}_>nOZR5}ZAsVi*MTvTvUHuBXujSZ=1{qq>cVYRy|BC&dN zg>sEyjR~R>0Uj6&VR%3gs_Ek8BxC&)BHO*X+_~(UTxx+ahoPq+sEMdSJ4(}e*7Y}^ zs3WXP++IBJ-VUz}nUK7o9)*B1{%O35WPgr!$#G~pSG~*>jYPIk=UDRl-O``W zFKBFF=#4`&9$P3T&B=44Y!ez#_kk8RGoB5 zun3|#AwYxW89rf2Yu@j>|ThbrbMRspng2)lEAjr8HOwp9x)h90l zTh6kH4OW0^Tg0j9JB=$(_B)K_+BoSv+;y|+349C(KEqV7CaxaxZJh-u$^KE>F~MJ+ zf*H#&eDo{*X*MLE9ar;LSz3wDQat7n=1cEifl3{CRNAuh*{EI?!8Mmt$b#>D#qHyKJ3{?HtpTAL{Q zw#7)Egc3=*afDELT0zEL*iz=J3DD26+_OqJuE)(f&m8$S6l@VOIq>(?wyxj- zC36_<``VWx@Hq#OPpR)~$(#?o5)X?(obBsx`i%z;hvJ^x@G-~;7E(X|c)ouht9>i0 z{&8*`AR(0Ob)je7Bhk(ZMkU&l`TRKkTyExY>d_NxKaPlU^Ji)K5fdSG*ddZ>o^Jha zqFntHZ;()uyoIe9asE&(<~+*xGuG}suKX-}_@^JS5LXqFs@5Nd25Hx;I;454gBZi| zF^#h~1kcP*#Eqez)qq6mV*A&#nF#;yT}3)fUUrdPs=KMIP#g3~nBMQkeJ(A%y`r_6TWyW)tf+Ig>7sI5ch;Ycy+%vH zKEce9i~Nh8%!l*Q#H>N09MR%Z=G;}-NHAJCHbjVsPpeEfl)|$scuQq~TUf=f%~?M` zldGn=?bxvQ#QVagEGS$UqC$5<=B0ylFp_aEUqs}I#opCOmw~Q}Dg4oLHPe;+;VC9w zz<%fH;eBroMLwv=)rvb?v#BpqR_AeTS<2)gYfjTY~}LfF16XKq_@ zI6Ztqd!BJB#l7u)zZwTkWBRpqa0dDWUyRAkP?is&Qm>QR z5U{k;9+5vd=By&D!g&7LZB)!#j}r|cpBZzh$Oke(uPxkfM3K#0R3fkRu=vifXRF;% z*!y+;Jk7DLtK@uwEuljHpY!a~=2k-|!ncT$gbxEjW!+kJ-jEZPC=2g2KWd3jtDfFC zL)-^A9LRV=`9)6#nx4c{oi&J5VLirXN!4I2p5>r8?Yj)_JMRkobG53hBRralwe+CY z>;@I;4 zK7U_1?n87`oH6R7#4kQ!>&`2DFmyM)%1i5#)1w0Hy$geowuH>kj@~pA!dca;Q7KRaEcj% z(Uiea(5Z+19u&Dtf~4M>2oMIa<)H;4_&Sqq$~O-ftbi|F8^gTNgltTW;YDZ(957>~ zTpe1lhER?$xb82Rw37gSwN*H2Q@!H{9DJVqAvjX72K>`D3FhiHCj1X^U?OA{_ z41i$5vm)kcN^nNkMY!Z`JdAGB`zw>e6!J#Ju(`yKfnly<(t8u5fnY+sMu$>dBxx-i z2Lq|vg@1}~joDS~^D+#aqLQz2zE_1GhB=V*<>i1}BV_It$5y|)7N6#eUs@^9q{^VJ z;jWisA*oGQzZj#E5e-&yAamWGOrwzsFf)LMUS_qU08?qS9{GYzDd$`Tc5QTW-_IFkHj@PhCH8V{ew(@q&J!eGXWF~f6=}`}L)*UG&s*H+e8i*o!6)no2rc%5<}%OmWJRd)MKUhAhfJ7J|7(kKXzHn}(+YrEJ|b z>46Cr7n4vxzw<2G^5B7?71>qHtY(N_R^@xoSdF4Z0N&gA-{bp-9~MW;$aDbsPGF|t zro=aoa*kVP=*U}iA7Cpzr7eP_`QYelxY#aw9s)+m`0e$-mY)5t^Z7TGdGc$W=7$+& zfY_uvKmlU5DSA3?dV4)ZwB}~hgC^&G^Rt96=Uyf+Jj>=s;R-?N zzES&{5-@ZOeV)aDLcV;Oi&uVlbNG)SPw{W}1dVU$>k-0ShJ@RoLvM|6A}*{sow=7D z<}d0}z4kXL2|RpYeM=*|MupRt&xPb7e};idM*+DO(C1C-A`7dCMdbpxWZRBJsvdod zR)21x0SZ_18SU}#FExf+Av3oo?fqLsYc7L8Rw4t6!ORd9XYlUwxDHrmuj-AMoEq`( zn~eU8;y|*ua4z`fan{+gYq>Df{zX)BpX$i0rk7O2_F*pFztNAvC%zGfxb!XcQ$=r(ry&Dpb>ata*Foj*fs z0x5o%X!~r5>D=eX=DMD`SF8oFm*}g#wLLX=PQV!8zRkpZ;ikR2Af6!k!MN?8>nTE3 zwdE;sdPwc5;ug_=#)tB@v?SnwAScEmKZk_sW0klpIw!#(T$C`gYjXWk5SghA+QztC zz}X;Skd@`(>He;J>UN&VoY0I!I~_RBklanu zwDHr4^DSR$@d%K}AaFO_|BcqYhqGrF`a3KKXD+eJ(E)!}>3?wo4WT1TCOWOt25qYA zE4?#g8#)?IVeylOH-UUc=Js0(F)@_nU^cSY=dH-EGe$E9^ z(E%^OmY({4n}7Bx3y^!Woe{zwFi-P4pqT`#rUbA!P47EpZ*RoImtpxhJ z*{1#F%GW{cA9JoC-<8^;XoAll%U_(BRk|}l=i!~|bCGV{g#O;?@GbGHlPr|vh!~JU z;BEC(nF|Q}6P9~BM%!4)u-787I}I<>W-Wm@I)(b zea}`}2T?m?oZle!YmA&t{WcWnCw;c;adH&!7U%SNC>|=w10FC1!w)YUp4wl=%U%^$ zsBGz?2=vq9`LvDehxDG*;nv1>==_ci!H~Q=5H#cY0MEgS zU7_ttv`UBVGohX6;uF!6m>-WGDDFMFG`Uw1Ax_*?Ko*9`z+JyR$T8HlKbmjapou9!^CgS0^ta0XNzTJ2*TN!J zx`2v?^rR5Qbd9;Fx*nSlh4@M4k>&X-flQQqvUDo*bh0cS(>_bm%U_nIS8!@BHZEJm zm*Uv&)ZDCjnME!xtJv&NIJ&r*_~lz~px<+^LgCaV2*H}UDF-{;#*lv#5@nobv*UscraUo)jui|LF|`QC&!w~Cy1tAkuPV`Gr7DX~R&lp2C{ zK5fw=VdzQ-YB%tWb>X8=gUj=OQYm?67Hyss#ME8FPGp*gtQ$p9$@#PrNr98qa@|`G z={b&1NXcp?WMQwGrHfqold;RIh)*z<*B%3s18F33rlk_pzCCo^`xl#U{#*S@$q ztFO>FD9O7GfiG^yiu4pB*M7=+b zCcO3B4SWqBCW_UjJvcqhZftle8DtCPuwvF8w*$7)=$z}p(iUe_d4k;=#}uT5C>+$| z47`LFHXaU5&4vJwNRWbn_n9mkn709>5DZsUNC!nzy`!iG-zSJN|0~@6i`UN~(4wLD zKk61`tW56D@=}nXJlp!}>)DWCVNM;=XjkpuGx*w{RAAmT(BUr{{=_MB0WO5Vxn<0c zDHIHC4#u#e8z0h%q?Cpq^)dLF`cHuu0Mj zmFV3WC=Y^;JP8M!;zMKvI1u{^D$f`oDh%Wcu`tV0oLVyIUI%s<=i!|`MxS@4i*)b5 zlME5bxIS7oB00zi=KafpdXnU)cY`Ek&nenYt*rboG^9`34+u7qr=-rlzxT;G94%B@Ud|Ga=H$_#HVrE%;NL4l zT39^rSd)k3QA#e<82kIn0ks`XcCiIf{~?Vq|1wAaQ@nEl{HZyBWDb=aM@U|2>AD34 zJ+L7ge11AjBFn1VF7L9FIIz16+;`uVa*$8dKOcVa?*JypUiCZRfzM9kmqtcLY0R>K zz~s75v{SF@z0HQelkNK?#%*h|$7p9Lya{O z*Xrw~ZUu4wcegq!{IVl3Ft80_YHAveAwvxn=c+w=)Xxs?QdToIDAz}Z-Qul%K3t4u z6@G|k{VHwD>;S}lJu{AtjXiOm`1HNaYL;47z7!E6fv1Gsc3Z%>tE#HrT3cNud^_82 z&eyd+Sp8)s?}I<}8LzUSc-L>A8+5FQy-(kilAZnM$7jr)LBmSw#tlcEl+Bk}w?2r5 z1C(^%`SY&H+8rkal38#WR#`eSsANeyYtqiv-iD@pooIBo@jpBMqgB>&8Ynjr{dLYo zHt$tQhzPB2HU$+`RB8S0EPfF;Kk^=3ml=o7f%G-)tCP zc2;}wPS??~d}plEjHWcLs7L@QxRzc84%l>DVxZDy=Pyv^Q4BZ?2S7#!?KK*j>UL#$ zc_`@^tdgt`enQMv%C=VNbRkSWyi+cIsfM#xwz%g-j{ptLfoM$kH>LouYTi0NgWSsx zA{@=XUjzqVhVXUd+X=>9MXJ|K{4vKM-z*~Z9uXx)*bd2fo%_z)LiuaXn$Y8KHkfiQ zr&hQEKnMdHqReBi%*D1hdx-4QXkLLXe`utgY@#I)o)O}FAbOjJMNmMUk55x zbm2!(4z(V@$6z!Jft<@N1SC_C>Iy)P8OrsMi=ah=%vJduQs)sMIJ!cF0tOU4z5<25 z%V(03r!k449cbJ?IFKoX)#K(`1Lrj<9n5KHX#7Chg#toC-y&(z<`KfG%(QA|`00in z4bpbZBwY?rBa!pQ30!ioMuB+VPJ%BFPwpf`L*HVQRFxIkc7<`ZB?aq%<{lP{J+J`1 zWX2+jl$Q(iz`olwT(WM!8ffY|m)Y!FWC&{_-4M_1uiw+FlXV|Fpr0b0%ga}k7Q^Vce>cxREb zI98E@{e%oHgc;_!t Date: Sat, 23 Oct 2021 09:25:59 +0200 Subject: [PATCH 0016/1133] Add icon source in SVG format Scrcpy only uses the PNG format (because SDL only supports bitmap icons), but keep the SVG source in the repo. --- data/icon.svg | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 data/icon.svg diff --git a/data/icon.svg b/data/icon.svg new file mode 100644 index 00000000..0ab92c2a --- /dev/null +++ b/data/icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + From 1e340caf76ef0ffa3c9f3d0ecd7d9a42472ae73a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Oct 2021 18:51:20 +0200 Subject: [PATCH 0017/1133] Remove legacy scrcpy icon Remove the old icon in XPM format and the code to load it. --- app/meson.build | 1 - app/src/icon.xpm | 53 -------------------- app/src/scrcpy.c | 1 - app/src/tiny_xpm.c | 119 --------------------------------------------- app/src/tiny_xpm.h | 11 ----- 5 files changed, 185 deletions(-) delete mode 100644 app/src/icon.xpm delete mode 100644 app/src/tiny_xpm.c delete mode 100644 app/src/tiny_xpm.h diff --git a/app/meson.build b/app/meson.build index b842bd04..13978e91 100644 --- a/app/meson.build +++ b/app/meson.build @@ -21,7 +21,6 @@ src = [ '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', diff --git a/app/src/icon.xpm b/app/src/icon.xpm deleted file mode 100644 index 73b29da9..00000000 --- a/app/src/icon.xpm +++ /dev/null @@ -1,53 +0,0 @@ -/* XPM */ -static char * icon_xpm[] = { -"48 48 2 1", -" c None", -". c #96C13E", -" .. .. ", -" ... ... ", -" ... ...... ... ", -" ................ ", -" .............. ", -" ................ ", -" .................. ", -" .................... ", -" ..... ........ ..... ", -" ..... ........ ..... ", -" ...................... ", -" ........................ ", -" ........................ ", -" ........................ ", -" ", -" ", -" .... ........................ .... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" ...... ........................ ...... ", -" .... ........................ .... ", -" ........................ ", -" ...................... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" ...... ...... ", -" .... .... "}; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6a285788..ce402ecc 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -22,7 +22,6 @@ #include "screen.h" #include "server.h" #include "stream.h" -#include "tiny_xpm.h" #include "util/log.h" #include "util/net.h" #ifdef HAVE_V4L2 diff --git a/app/src/tiny_xpm.c b/app/src/tiny_xpm.c deleted file mode 100644 index df1f9e53..00000000 --- a/app/src/tiny_xpm.c +++ /dev/null @@ -1,119 +0,0 @@ -#include "tiny_xpm.h" - -#include -#include -#include -#include -#include - -#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; -} diff --git a/app/src/tiny_xpm.h b/app/src/tiny_xpm.h deleted file mode 100644 index 29b42d14..00000000 --- a/app/src/tiny_xpm.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TINYXPM_H -#define TINYXPM_H - -#include "common.h" - -#include - -SDL_Surface * -read_xpm(char *xpm[]); - -#endif From 580f5d8bb929777cf101ebbc09a7235d177356ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 Oct 2021 09:29:30 +0200 Subject: [PATCH 0018/1133] Add scrcpy icon to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b9da20b5..98b54f54 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # scrcpy (v1.19) +![icon](data/icon.png) + [Read in another language](#translations) This application provides display and control of Android devices connected on From d6568f64af8bbb76b800fd083b270de0e9cb7e17 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 Oct 2021 18:21:31 +0200 Subject: [PATCH 0019/1133] Mention SCRCPY_ICON_PATH envvar in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 98b54f54..41fddb97 100644 --- a/README.md +++ b/README.md @@ -830,6 +830,8 @@ To override the path of the `scrcpy-server` file, configure its path in [useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 +To override the icon, configure its path in `SCRCPY_ICON_PATH`. + ## Why _scrcpy_? From bea3197c3f3e906992486c28a604b10b22726bb0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 Oct 2021 18:22:48 +0200 Subject: [PATCH 0020/1133] Remove unused markdown link in README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 41fddb97..84dd5ff5 100644 --- a/README.md +++ b/README.md @@ -828,8 +828,6 @@ ADB=/path/to/adb scrcpy To override the path of the `scrcpy-server` file, configure its path in `SCRCPY_SERVER_PATH`. -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - To override the icon, configure its path in `SCRCPY_ICON_PATH`. From 056d36ce4aee1a496bb90fb470b9b41f79d2df24 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Oct 2021 17:13:12 +0200 Subject: [PATCH 0021/1133] Fix trait header guards --- app/src/trait/frame_sink.h | 4 ++-- app/src/trait/packet_sink.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/trait/frame_sink.h b/app/src/trait/frame_sink.h index 64ab0de9..0214ab3e 100644 --- a/app/src/trait/frame_sink.h +++ b/app/src/trait/frame_sink.h @@ -1,5 +1,5 @@ -#ifndef SC_FRAME_SINK -#define SC_FRAME_SINK +#ifndef SC_FRAME_SINK_H +#define SC_FRAME_SINK_H #include "common.h" diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h index fe9c137d..1fef765f 100644 --- a/app/src/trait/packet_sink.h +++ b/app/src/trait/packet_sink.h @@ -1,5 +1,5 @@ -#ifndef SC_PACKET_SINK -#define SC_PACKET_SINK +#ifndef SC_PACKET_SINK_H +#define SC_PACKET_SINK_H #include "common.h" From bcf5a9750f65e63e3ab2abd263c7393cb2748b8e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Oct 2021 17:11:20 +0200 Subject: [PATCH 0022/1133] Extract keyboard processor trait This will allow to provide alternative key processors. --- app/meson.build | 1 + app/src/event_converter.c | 151 -------------------- app/src/event_converter.h | 10 -- app/src/input_manager.c | 65 +-------- app/src/input_manager.h | 10 +- app/src/keyboard_inject.c | 254 ++++++++++++++++++++++++++++++++++ app/src/keyboard_inject.h | 30 ++++ app/src/scrcpy.c | 12 +- app/src/trait/key_processor.h | 29 ++++ 9 files changed, 335 insertions(+), 227 deletions(-) create mode 100644 app/src/keyboard_inject.c create mode 100644 app/src/keyboard_inject.h create mode 100644 app/src/trait/key_processor.h diff --git a/app/meson.build b/app/meson.build index 13978e91..78060cff 100644 --- a/app/meson.build +++ b/app/meson.build @@ -14,6 +14,7 @@ src = [ 'src/fps_counter.c', 'src/frame_buffer.c', 'src/input_manager.c', + 'src/keyboard_inject.c', 'src/opengl.c', 'src/receiver.c', 'src/recorder.c', diff --git a/app/src/event_converter.c b/app/src/event_converter.c index a3c2da89..71f9de16 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -3,157 +3,6 @@ #define MAP(FROM, TO) case FROM: *to = TO; return true #define FAIL default: return false -bool -convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { - switch (from) { - MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); - MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); - FAIL; - } -} - -static enum android_metastate -autocomplete_metastate(enum android_metastate metastate) { - // fill dependent flags - if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { - metastate |= AMETA_SHIFT_ON; - } - if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { - metastate |= AMETA_CTRL_ON; - } - if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - metastate |= AMETA_ALT_ON; - } - if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { - metastate |= AMETA_META_ON; - } - - return metastate; -} - -enum android_metastate -convert_meta_state(SDL_Keymod mod) { - enum android_metastate metastate = 0; - if (mod & KMOD_LSHIFT) { - metastate |= AMETA_SHIFT_LEFT_ON; - } - if (mod & KMOD_RSHIFT) { - metastate |= AMETA_SHIFT_RIGHT_ON; - } - if (mod & KMOD_LCTRL) { - metastate |= AMETA_CTRL_LEFT_ON; - } - if (mod & KMOD_RCTRL) { - metastate |= AMETA_CTRL_RIGHT_ON; - } - if (mod & KMOD_LALT) { - metastate |= AMETA_ALT_LEFT_ON; - } - if (mod & KMOD_RALT) { - metastate |= AMETA_ALT_RIGHT_ON; - } - if (mod & KMOD_LGUI) { // Windows key - metastate |= AMETA_META_LEFT_ON; - } - if (mod & KMOD_RGUI) { // Windows key - metastate |= AMETA_META_RIGHT_ON; - } - if (mod & KMOD_NUM) { - metastate |= AMETA_NUM_LOCK_ON; - } - if (mod & KMOD_CAPS) { - metastate |= AMETA_CAPS_LOCK_ON; - } - if (mod & KMOD_MODE) { // Alt Gr - // no mapping? - } - - // fill the dependent fields - return autocomplete_metastate(metastate); -} - -bool -convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, - bool prefer_text) { - switch (from) { - MAP(SDLK_RETURN, AKEYCODE_ENTER); - MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); - MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); - MAP(SDLK_BACKSPACE, AKEYCODE_DEL); - MAP(SDLK_TAB, AKEYCODE_TAB); - MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); - MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); - MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); - MAP(SDLK_END, AKEYCODE_MOVE_END); - MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); - MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); - MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); - MAP(SDLK_UP, AKEYCODE_DPAD_UP); - MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); - MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); - MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); - MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); - } - - if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { - // Handle Numpad events when Num Lock is disabled - // If SHIFT is pressed, a text event will be sent instead - switch(from) { - MAP(SDLK_KP_0, AKEYCODE_INSERT); - MAP(SDLK_KP_1, AKEYCODE_MOVE_END); - MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); - MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); - MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); - MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); - MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); - MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); - MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); - } - } - - if (prefer_text && !(mod & KMOD_CTRL)) { - // do not forward alpha and space key events (unless Ctrl is pressed) - return false; - } - - if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { - return false; - } - // if ALT and META are not pressed, also handle letters and space - switch (from) { - MAP(SDLK_a, AKEYCODE_A); - MAP(SDLK_b, AKEYCODE_B); - MAP(SDLK_c, AKEYCODE_C); - MAP(SDLK_d, AKEYCODE_D); - MAP(SDLK_e, AKEYCODE_E); - MAP(SDLK_f, AKEYCODE_F); - MAP(SDLK_g, AKEYCODE_G); - MAP(SDLK_h, AKEYCODE_H); - MAP(SDLK_i, AKEYCODE_I); - MAP(SDLK_j, AKEYCODE_J); - MAP(SDLK_k, AKEYCODE_K); - MAP(SDLK_l, AKEYCODE_L); - MAP(SDLK_m, AKEYCODE_M); - MAP(SDLK_n, AKEYCODE_N); - MAP(SDLK_o, AKEYCODE_O); - MAP(SDLK_p, AKEYCODE_P); - MAP(SDLK_q, AKEYCODE_Q); - MAP(SDLK_r, AKEYCODE_R); - MAP(SDLK_s, AKEYCODE_S); - MAP(SDLK_t, AKEYCODE_T); - MAP(SDLK_u, AKEYCODE_U); - MAP(SDLK_v, AKEYCODE_V); - MAP(SDLK_w, AKEYCODE_W); - MAP(SDLK_x, AKEYCODE_X); - MAP(SDLK_y, AKEYCODE_Y); - MAP(SDLK_z, AKEYCODE_Z); - MAP(SDLK_SPACE, AKEYCODE_SPACE); - FAIL; - } -} - enum android_motionevent_buttons convert_mouse_buttons(uint32_t state) { enum android_motionevent_buttons buttons = 0; diff --git a/app/src/event_converter.h b/app/src/event_converter.h index d28e9fdc..71e8edec 100644 --- a/app/src/event_converter.h +++ b/app/src/event_converter.h @@ -8,16 +8,6 @@ #include "control_msg.h" -bool -convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to); - -enum android_metastate -convert_meta_state(SDL_Keymod mod); - -bool -convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, - bool prefer_text); - enum android_motionevent_buttons convert_mouse_buttons(uint32_t state); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index a5d0ad07..764760cf 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -53,15 +53,15 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, + struct screen *screen, struct sc_key_processor *kp, const struct scrcpy_options *options) { + assert(!options->control || (kp && kp->ops)); + im->controller = controller; im->screen = screen; - im->repeat = 0; + im->kp = kp; im->control = options->control; - im->forward_key_repeat = options->forward_key_repeat; - im->prefer_text = options->prefer_text; im->forward_all_clicks = options->forward_all_clicks; im->legacy_paste = options->legacy_paste; @@ -323,26 +323,8 @@ input_manager_process_text_input(struct input_manager *im, // A shortcut must never generate text events return; } - if (!im->prefer_text) { - char c = event->text[0]; - if (isalpha(c) || c == ' ') { - assert(event->text[1] == '\0'); - // letters and space are handled as raw key event - return; - } - } - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - msg.inject_text.text = strdup(event->text); - if (!msg.inject_text.text) { - LOGW("Could not strdup input text"); - return; - } - if (!controller_push_msg(im->controller, &msg)) { - free(msg.inject_text.text); - LOGW("Could not request 'inject text'"); - } + im->kp->ops->process_text(im->kp, event); } static bool @@ -375,27 +357,6 @@ inverse_point(struct point point, struct size size) { return point; } -static bool -convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, - bool prefer_text, uint32_t repeat) { - to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; - - if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { - return false; - } - - uint16_t mod = from->keysym.mod; - if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, - prefer_text)) { - return false; - } - - to->inject_keycode.repeat = repeat; - to->inject_keycode.metastate = convert_meta_state(mod); - - return true; -} - static void input_manager_process_key(struct input_manager *im, const SDL_KeyboardEvent *event) { @@ -549,15 +510,6 @@ input_manager_process_key(struct input_manager *im, return; } - if (event->repeat) { - if (!im->forward_key_repeat) { - return; - } - ++im->repeat; - } else { - im->repeat = 0; - } - if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { if (im->legacy_paste) { // inject the text as input events @@ -569,12 +521,7 @@ input_manager_process_key(struct input_manager *im, set_device_clipboard(controller, false); } - struct control_msg msg; - if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'inject keycode'"); - } - } + im->kp->ops->process_key(im->kp, event); } static bool diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 1dd7825f..cdd32295 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -11,18 +11,15 @@ #include "fps_counter.h" #include "scrcpy.h" #include "screen.h" +#include "trait/key_processor.h" struct input_manager { struct controller *controller; struct screen *screen; - // SDL reports repeated events as a boolean, but Android expects the actual - // number of repetitions. This variable keeps track of the count. - unsigned repeat; + struct sc_key_processor *kp; bool control; - bool forward_key_repeat; - bool prefer_text; bool forward_all_clicks; bool legacy_paste; @@ -43,7 +40,8 @@ struct input_manager { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, const struct scrcpy_options *options); + struct screen *screen, struct sc_key_processor *kp, + const struct scrcpy_options *options); bool input_manager_handle_event(struct input_manager *im, SDL_Event *event); diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c new file mode 100644 index 00000000..bcc85da8 --- /dev/null +++ b/app/src/keyboard_inject.c @@ -0,0 +1,254 @@ +#include "keyboard_inject.h" + +#include +#include + +#include "android/input.h" +#include "control_msg.h" +#include "controller.h" +#include "util/log.h" + +/** Downcast key processor to sc_keyboard_inject */ +#define DOWNCAST(KP) \ + container_of(KP, struct sc_keyboard_inject, key_processor) + +#define MAP(FROM, TO) case FROM: *to = TO; return true +#define FAIL default: return false +static bool +convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { + switch (from) { + MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); + MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); + FAIL; + } +} + +static bool +convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, + bool prefer_text) { + switch (from) { + MAP(SDLK_RETURN, AKEYCODE_ENTER); + MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); + MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); + MAP(SDLK_BACKSPACE, AKEYCODE_DEL); + MAP(SDLK_TAB, AKEYCODE_TAB); + MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); + MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); + MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); + MAP(SDLK_END, AKEYCODE_MOVE_END); + MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); + MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); + MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); + MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); + MAP(SDLK_UP, AKEYCODE_DPAD_UP); + MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); + MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); + MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); + MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); + } + + if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { + // Handle Numpad events when Num Lock is disabled + // If SHIFT is pressed, a text event will be sent instead + switch(from) { + MAP(SDLK_KP_0, AKEYCODE_INSERT); + MAP(SDLK_KP_1, AKEYCODE_MOVE_END); + MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); + MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); + MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); + MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); + MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); + MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); + MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); + MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); + } + } + + if (prefer_text && !(mod & KMOD_CTRL)) { + // do not forward alpha and space key events (unless Ctrl is pressed) + return false; + } + + if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { + return false; + } + // if ALT and META are not pressed, also handle letters and space + switch (from) { + MAP(SDLK_a, AKEYCODE_A); + MAP(SDLK_b, AKEYCODE_B); + MAP(SDLK_c, AKEYCODE_C); + MAP(SDLK_d, AKEYCODE_D); + MAP(SDLK_e, AKEYCODE_E); + MAP(SDLK_f, AKEYCODE_F); + MAP(SDLK_g, AKEYCODE_G); + MAP(SDLK_h, AKEYCODE_H); + MAP(SDLK_i, AKEYCODE_I); + MAP(SDLK_j, AKEYCODE_J); + MAP(SDLK_k, AKEYCODE_K); + MAP(SDLK_l, AKEYCODE_L); + MAP(SDLK_m, AKEYCODE_M); + MAP(SDLK_n, AKEYCODE_N); + MAP(SDLK_o, AKEYCODE_O); + MAP(SDLK_p, AKEYCODE_P); + MAP(SDLK_q, AKEYCODE_Q); + MAP(SDLK_r, AKEYCODE_R); + MAP(SDLK_s, AKEYCODE_S); + MAP(SDLK_t, AKEYCODE_T); + MAP(SDLK_u, AKEYCODE_U); + MAP(SDLK_v, AKEYCODE_V); + MAP(SDLK_w, AKEYCODE_W); + MAP(SDLK_x, AKEYCODE_X); + MAP(SDLK_y, AKEYCODE_Y); + MAP(SDLK_z, AKEYCODE_Z); + MAP(SDLK_SPACE, AKEYCODE_SPACE); + FAIL; + } +} + +static enum android_metastate +autocomplete_metastate(enum android_metastate metastate) { + // fill dependent flags + if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { + metastate |= AMETA_SHIFT_ON; + } + if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { + metastate |= AMETA_CTRL_ON; + } + if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { + metastate |= AMETA_ALT_ON; + } + if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { + metastate |= AMETA_META_ON; + } + + return metastate; +} + +static enum android_metastate +convert_meta_state(SDL_Keymod mod) { + enum android_metastate metastate = 0; + if (mod & KMOD_LSHIFT) { + metastate |= AMETA_SHIFT_LEFT_ON; + } + if (mod & KMOD_RSHIFT) { + metastate |= AMETA_SHIFT_RIGHT_ON; + } + if (mod & KMOD_LCTRL) { + metastate |= AMETA_CTRL_LEFT_ON; + } + if (mod & KMOD_RCTRL) { + metastate |= AMETA_CTRL_RIGHT_ON; + } + if (mod & KMOD_LALT) { + metastate |= AMETA_ALT_LEFT_ON; + } + if (mod & KMOD_RALT) { + metastate |= AMETA_ALT_RIGHT_ON; + } + if (mod & KMOD_LGUI) { // Windows key + metastate |= AMETA_META_LEFT_ON; + } + if (mod & KMOD_RGUI) { // Windows key + metastate |= AMETA_META_RIGHT_ON; + } + if (mod & KMOD_NUM) { + metastate |= AMETA_NUM_LOCK_ON; + } + if (mod & KMOD_CAPS) { + metastate |= AMETA_CAPS_LOCK_ON; + } + if (mod & KMOD_MODE) { // Alt Gr + // no mapping? + } + + // fill the dependent fields + return autocomplete_metastate(metastate); +} + +static bool +convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, + bool prefer_text, uint32_t repeat) { + to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + + if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { + return false; + } + + uint16_t mod = from->keysym.mod; + if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, + prefer_text)) { + return false; + } + + to->inject_keycode.repeat = repeat; + to->inject_keycode.metastate = convert_meta_state(mod); + + return true; +} + +static void +sc_key_processor_process_key(struct sc_key_processor *kp, + const SDL_KeyboardEvent *event) { + struct sc_keyboard_inject *ki = DOWNCAST(kp); + + if (event->repeat) { + if (!ki->forward_key_repeat) { + return; + } + ++ki->repeat; + } else { + ki->repeat = 0; + } + + struct control_msg msg; + if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) { + if (!controller_push_msg(ki->controller, &msg)) { + LOGW("Could not request 'inject keycode'"); + } + } +} + +static void +sc_key_processor_process_text(struct sc_key_processor *kp, + const SDL_TextInputEvent *event) { + struct sc_keyboard_inject *ki = DOWNCAST(kp); + + if (!ki->prefer_text) { + char c = event->text[0]; + if (isalpha(c) || c == ' ') { + assert(event->text[1] == '\0'); + // letters and space are handled as raw key event + return; + } + } + + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; + msg.inject_text.text = strdup(event->text); + if (!msg.inject_text.text) { + LOGW("Could not strdup input text"); + return; + } + if (!controller_push_msg(ki->controller, &msg)) { + free(msg.inject_text.text); + LOGW("Could not request 'inject text'"); + } +} + +void +sc_keyboard_inject_init(struct sc_keyboard_inject *ki, + struct controller *controller, + const struct scrcpy_options *options) { + ki->controller = controller; + ki->prefer_text = options->prefer_text; + ki->forward_key_repeat = options->forward_key_repeat; + + ki->repeat = 0; + + static const struct sc_key_processor_ops ops = { + .process_key = sc_key_processor_process_key, + .process_text = sc_key_processor_process_text, + }; + + ki->key_processor.ops = &ops; +} diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h new file mode 100644 index 00000000..e59de46d --- /dev/null +++ b/app/src/keyboard_inject.h @@ -0,0 +1,30 @@ +#ifndef SC_KEYBOARD_INJECT_H +#define SC_KEYBOARD_INJECT_H + +#include "common.h" + +#include + +#include "controller.h" +#include "scrcpy.h" +#include "trait/key_processor.h" + +struct sc_keyboard_inject { + struct sc_key_processor key_processor; // key processor trait + + struct controller *controller; + + // SDL reports repeated events as a boolean, but Android expects the actual + // number of repetitions. This variable keeps track of the count. + unsigned repeat; + + bool prefer_text; + bool forward_key_repeat; +}; + +void +sc_keyboard_inject_init(struct sc_keyboard_inject *ki, + struct controller *controller, + const struct scrcpy_options *options); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ce402ecc..def96bb7 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -18,6 +18,7 @@ #include "events.h" #include "file_handler.h" #include "input_manager.h" +#include "keyboard_inject.h" #include "recorder.h" #include "screen.h" #include "server.h" @@ -39,6 +40,7 @@ struct scrcpy { #endif struct controller controller; struct file_handler file_handler; + struct sc_keyboard_inject keyboard_inject; struct input_manager input_manager; }; @@ -411,7 +413,15 @@ scrcpy(const struct scrcpy_options *options) { } stream_started = true; - input_manager_init(&s->input_manager, &s->controller, &s->screen, options); + struct sc_key_processor *kp = NULL; + + if (options->control) { + sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, options); + kp = &s->keyboard_inject.key_processor; + } + + input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, + options); ret = event_loop(s, options); LOGD("quit..."); diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h new file mode 100644 index 00000000..5790310b --- /dev/null +++ b/app/src/trait/key_processor.h @@ -0,0 +1,29 @@ +#ifndef SC_KEY_PROCESSOR_H +#define SC_KEY_PROCESSOR_H + +#include "common.h" + +#include +#include + +#include + +/** + * Key processor trait. + * + * Component able to process and inject keys should implement this trait. + */ +struct sc_key_processor { + const struct sc_key_processor_ops *ops; +}; + +struct sc_key_processor_ops { + void + (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event); + + void + (*process_text)(struct sc_key_processor *kp, + const SDL_TextInputEvent *event); +}; + +#endif From f7d1efdf1dc8d35a5de7e7654e71a96be2a8bd7c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Oct 2021 17:44:14 +0200 Subject: [PATCH 0023/1133] Extract mouse processor trait Mainly for consistency with the keyboard processor trait. This could allow to provide alternative mouse processors. --- app/meson.build | 2 +- app/src/event_converter.c | 44 ------- app/src/event_converter.h | 20 --- app/src/input_manager.c | 131 ++------------------ app/src/input_manager.h | 3 + app/src/mouse_inject.c | 211 ++++++++++++++++++++++++++++++++ app/src/mouse_inject.h | 24 ++++ app/src/scrcpy.c | 8 +- app/src/trait/mouse_processor.h | 39 ++++++ 9 files changed, 298 insertions(+), 184 deletions(-) delete mode 100644 app/src/event_converter.c delete mode 100644 app/src/event_converter.h create mode 100644 app/src/mouse_inject.c create mode 100644 app/src/mouse_inject.h create mode 100644 app/src/trait/mouse_processor.h diff --git a/app/meson.build b/app/meson.build index 78060cff..46d3994f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -8,13 +8,13 @@ src = [ 'src/controller.c', 'src/decoder.c', 'src/device_msg.c', - 'src/event_converter.c', 'src/icon.c', 'src/file_handler.c', 'src/fps_counter.c', 'src/frame_buffer.c', 'src/input_manager.c', 'src/keyboard_inject.c', + 'src/mouse_inject.c', 'src/opengl.c', 'src/receiver.c', 'src/recorder.c', diff --git a/app/src/event_converter.c b/app/src/event_converter.c deleted file mode 100644 index 71f9de16..00000000 --- a/app/src/event_converter.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "event_converter.h" - -#define MAP(FROM, TO) case FROM: *to = TO; return true -#define FAIL default: return false - -enum android_motionevent_buttons -convert_mouse_buttons(uint32_t state) { - enum android_motionevent_buttons buttons = 0; - if (state & SDL_BUTTON_LMASK) { - buttons |= AMOTION_EVENT_BUTTON_PRIMARY; - } - if (state & SDL_BUTTON_RMASK) { - buttons |= AMOTION_EVENT_BUTTON_SECONDARY; - } - if (state & SDL_BUTTON_MMASK) { - buttons |= AMOTION_EVENT_BUTTON_TERTIARY; - } - if (state & SDL_BUTTON_X1MASK) { - buttons |= AMOTION_EVENT_BUTTON_BACK; - } - if (state & SDL_BUTTON_X2MASK) { - buttons |= AMOTION_EVENT_BUTTON_FORWARD; - } - return buttons; -} - -bool -convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { - switch (from) { - MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); - MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); - FAIL; - } -} - -bool -convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { - switch (from) { - MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE); - MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN); - MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP); - FAIL; - } -} diff --git a/app/src/event_converter.h b/app/src/event_converter.h deleted file mode 100644 index 71e8edec..00000000 --- a/app/src/event_converter.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef CONVERT_H -#define CONVERT_H - -#include "common.h" - -#include -#include - -#include "control_msg.h" - -enum android_motionevent_buttons -convert_mouse_buttons(uint32_t state); - -bool -convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to); - -bool -convert_touch_action(SDL_EventType from, enum android_motionevent_action *to); - -#endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 764760cf..bba3c998 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -3,7 +3,6 @@ #include #include -#include "event_converter.h" #include "util/log.h" static const int ACTION_DOWN = 1; @@ -54,12 +53,15 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { void input_manager_init(struct input_manager *im, struct controller *controller, struct screen *screen, struct sc_key_processor *kp, + struct sc_mouse_processor *mp, const struct scrcpy_options *options) { assert(!options->control || (kp && kp->ops)); + assert(!options->control || (mp && mp->ops)); im->controller = controller; im->screen = screen; im->kp = kp; + im->mp = mp; im->control = options->control; im->forward_all_clicks = options->forward_all_clicks; @@ -524,21 +526,6 @@ input_manager_process_key(struct input_manager *im, im->kp->ops->process_key(im->kp, event); } -static bool -convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; - to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point = - screen_convert_window_to_frame_coords(screen, from->x, from->y); - to->inject_touch_event.pressure = 1.f; - to->inject_touch_event.buttons = convert_mouse_buttons(from->state); - - return true; -} - static void input_manager_process_mouse_motion(struct input_manager *im, const SDL_MouseMotionEvent *event) { @@ -554,79 +541,22 @@ input_manager_process_mouse_motion(struct input_manager *im, // simulated from touch events, so it's a duplicate return; } - struct control_msg msg; - if (!convert_mouse_motion(event, im->screen, &msg)) { - return; - } - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse motion event'"); - } + im->mp->ops->process_mouse_motion(im->mp, event); if (im->vfinger_down) { - struct point mouse = msg.inject_touch_event.position.point; + struct point mouse = + screen_convert_window_to_frame_coords(im->screen, event->x, + event->y); struct point vfinger = inverse_point(mouse, im->screen->frame_size); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } } -static bool -convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - - if (!convert_touch_action(from->type, &to->inject_touch_event.action)) { - return false; - } - - to->inject_touch_event.pointer_id = from->fingerId; - to->inject_touch_event.position.screen_size = screen->frame_size; - - int dw; - int dh; - SDL_GL_GetDrawableSize(screen->window, &dw, &dh); - - // SDL touch event coordinates are normalized in the range [0; 1] - int32_t x = from->x * dw; - int32_t y = from->y * dh; - to->inject_touch_event.position.point = - screen_convert_drawable_to_frame_coords(screen, x, y); - - to->inject_touch_event.pressure = from->pressure; - to->inject_touch_event.buttons = 0; - return true; -} - static void input_manager_process_touch(struct input_manager *im, const SDL_TouchFingerEvent *event) { - struct control_msg msg; - if (convert_touch(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject touch event'"); - } - } -} - -static bool -convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - - if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) { - return false; - } - - to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point = - screen_convert_window_to_frame_coords(screen, from->x, from->y); - to->inject_touch_event.pressure = - from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f; - to->inject_touch_event.buttons = - convert_mouse_buttons(SDL_BUTTON(from->button)); - - return true; + im->mp->ops->process_touch(im->mp, event); } static void @@ -686,15 +616,7 @@ input_manager_process_mouse_button(struct input_manager *im, return; } - struct control_msg msg; - if (!convert_mouse_button(event, im->screen, &msg)) { - return; - } - - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse button event'"); - return; - } + im->mp->ops->process_mouse_button(im->mp, event); // Pinch-to-zoom simulation. // @@ -708,7 +630,9 @@ 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 point mouse = msg.inject_touch_event.position.point; + struct point mouse = + screen_convert_window_to_frame_coords(im->screen, event->x, + event->y); struct point vfinger = inverse_point(mouse, im->screen->frame_size); enum android_motionevent_action action = down ? AMOTION_EVENT_ACTION_DOWN @@ -720,39 +644,10 @@ input_manager_process_mouse_button(struct input_manager *im, } } -static bool -convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, - struct control_msg *to) { - - // mouse_x and mouse_y are expressed in pixels relative to the window - int mouse_x; - int mouse_y; - SDL_GetMouseState(&mouse_x, &mouse_y); - - struct position position = { - .screen_size = screen->frame_size, - .point = screen_convert_window_to_frame_coords(screen, - mouse_x, mouse_y), - }; - - to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; - - to->inject_scroll_event.position = position; - to->inject_scroll_event.hscroll = from->x; - to->inject_scroll_event.vscroll = from->y; - - return true; -} - static void input_manager_process_mouse_wheel(struct input_manager *im, const SDL_MouseWheelEvent *event) { - struct control_msg msg; - if (convert_mouse_wheel(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse wheel event'"); - } - } + im->mp->ops->process_mouse_wheel(im->mp, event); } bool diff --git a/app/src/input_manager.h b/app/src/input_manager.h index cdd32295..bd9d7a1b 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -12,12 +12,14 @@ #include "scrcpy.h" #include "screen.h" #include "trait/key_processor.h" +#include "trait/mouse_processor.h" struct input_manager { struct controller *controller; struct screen *screen; struct sc_key_processor *kp; + struct sc_mouse_processor *mp; bool control; bool forward_all_clicks; @@ -41,6 +43,7 @@ struct input_manager { void input_manager_init(struct input_manager *im, struct controller *controller, struct screen *screen, struct sc_key_processor *kp, + struct sc_mouse_processor *mp, const struct scrcpy_options *options); bool diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c new file mode 100644 index 00000000..008da267 --- /dev/null +++ b/app/src/mouse_inject.c @@ -0,0 +1,211 @@ +#include "mouse_inject.h" + +#include +#include + +#include "android/input.h" +#include "control_msg.h" +#include "controller.h" +#include "util/log.h" + +/** Downcast mouse processor to sc_mouse_inject */ +#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor) + +static enum android_motionevent_buttons +convert_mouse_buttons(uint32_t state) { + enum android_motionevent_buttons buttons = 0; + if (state & SDL_BUTTON_LMASK) { + buttons |= AMOTION_EVENT_BUTTON_PRIMARY; + } + if (state & SDL_BUTTON_RMASK) { + buttons |= AMOTION_EVENT_BUTTON_SECONDARY; + } + if (state & SDL_BUTTON_MMASK) { + buttons |= AMOTION_EVENT_BUTTON_TERTIARY; + } + if (state & SDL_BUTTON_X1MASK) { + buttons |= AMOTION_EVENT_BUTTON_BACK; + } + if (state & SDL_BUTTON_X2MASK) { + buttons |= AMOTION_EVENT_BUTTON_FORWARD; + } + return buttons; +} + +#define MAP(FROM, TO) case FROM: *to = TO; return true +#define FAIL default: return false +static bool +convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { + switch (from) { + MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); + MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); + FAIL; + } +} + +static bool +convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { + switch (from) { + MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE); + MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN); + MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP); + FAIL; + } +} + +static bool +convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, + struct control_msg *to) { + to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; + to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; + to->inject_touch_event.position.screen_size = screen->frame_size; + to->inject_touch_event.position.point = + screen_convert_window_to_frame_coords(screen, from->x, from->y); + to->inject_touch_event.pressure = 1.f; + to->inject_touch_event.buttons = convert_mouse_buttons(from->state); + + return true; +} + +static bool +convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, + struct control_msg *to) { + to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + + if (!convert_touch_action(from->type, &to->inject_touch_event.action)) { + return false; + } + + to->inject_touch_event.pointer_id = from->fingerId; + to->inject_touch_event.position.screen_size = screen->frame_size; + + int dw; + int dh; + SDL_GL_GetDrawableSize(screen->window, &dw, &dh); + + // SDL touch event coordinates are normalized in the range [0; 1] + int32_t x = from->x * dw; + int32_t y = from->y * dh; + to->inject_touch_event.position.point = + screen_convert_drawable_to_frame_coords(screen, x, y); + + to->inject_touch_event.pressure = from->pressure; + to->inject_touch_event.buttons = 0; + return true; +} + +static bool +convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, + struct control_msg *to) { + to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + + if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) { + return false; + } + + to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; + to->inject_touch_event.position.screen_size = screen->frame_size; + to->inject_touch_event.position.point = + screen_convert_window_to_frame_coords(screen, from->x, from->y); + to->inject_touch_event.pressure = + from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f; + to->inject_touch_event.buttons = + convert_mouse_buttons(SDL_BUTTON(from->button)); + + return true; +} + +static bool +convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, + struct control_msg *to) { + + // mouse_x and mouse_y are expressed in pixels relative to the window + int mouse_x; + int mouse_y; + SDL_GetMouseState(&mouse_x, &mouse_y); + + struct position position = { + .screen_size = screen->frame_size, + .point = screen_convert_window_to_frame_coords(screen, + mouse_x, mouse_y), + }; + + to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; + + to->inject_scroll_event.position = position; + to->inject_scroll_event.hscroll = from->x; + to->inject_scroll_event.vscroll = from->y; + + return true; +} + +static void +sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, + const SDL_MouseMotionEvent *event) { + struct sc_mouse_inject *mi = DOWNCAST(mp); + + struct control_msg msg; + if (!convert_mouse_motion(event, mi->screen, &msg)) { + return; + } + + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject mouse motion event'"); + } +} + +static void +sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, + const SDL_TouchFingerEvent *event) { + struct sc_mouse_inject *mi = DOWNCAST(mp); + + struct control_msg msg; + if (convert_touch(event, mi->screen, &msg)) { + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject touch event'"); + } + } +} + +static void +sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp, + const SDL_MouseButtonEvent *event) { + struct sc_mouse_inject *mi = DOWNCAST(mp); + + struct control_msg msg; + if (convert_mouse_button(event, mi->screen, &msg)) { + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject mouse button event'"); + } + } +} + +static void +sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp, + const SDL_MouseWheelEvent *event) { + struct sc_mouse_inject *mi = DOWNCAST(mp); + + struct control_msg msg; + if (convert_mouse_wheel(event, mi->screen, &msg)) { + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject mouse wheel event'"); + } + } +} + +void +sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, + struct screen *screen) { + mi->controller = controller; + mi->screen = screen; + + static const struct sc_mouse_processor_ops ops = { + .process_mouse_motion = sc_mouse_processor_process_mouse_motion, + .process_touch = sc_mouse_processor_process_touch, + .process_mouse_button = sc_mouse_processor_process_mouse_button, + .process_mouse_wheel = sc_mouse_processor_process_mouse_wheel, + }; + + mi->mouse_processor.ops = &ops; +} diff --git a/app/src/mouse_inject.h b/app/src/mouse_inject.h new file mode 100644 index 00000000..d5220db9 --- /dev/null +++ b/app/src/mouse_inject.h @@ -0,0 +1,24 @@ +#ifndef SC_MOUSE_INJECT_H +#define SC_MOUSE_INJECT_H + +#include "common.h" + +#include + +#include "controller.h" +#include "scrcpy.h" +#include "screen.h" +#include "trait/mouse_processor.h" + +struct sc_mouse_inject { + struct sc_mouse_processor mouse_processor; // mouse processor trait + + struct controller *controller; + struct screen *screen; +}; + +void +sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, + struct screen *screen); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index def96bb7..50250e2c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -19,6 +19,7 @@ #include "file_handler.h" #include "input_manager.h" #include "keyboard_inject.h" +#include "mouse_inject.h" #include "recorder.h" #include "screen.h" #include "server.h" @@ -41,6 +42,7 @@ struct scrcpy { struct controller controller; struct file_handler file_handler; struct sc_keyboard_inject keyboard_inject; + struct sc_mouse_inject mouse_inject; struct input_manager input_manager; }; @@ -414,13 +416,17 @@ scrcpy(const struct scrcpy_options *options) { stream_started = true; struct sc_key_processor *kp = NULL; + struct sc_mouse_processor *mp = NULL; if (options->control) { sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, options); kp = &s->keyboard_inject.key_processor; + + sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen); + mp = &s->mouse_inject.mouse_processor; } - input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, + input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp, options); ret = event_loop(s, options); diff --git a/app/src/trait/mouse_processor.h b/app/src/trait/mouse_processor.h new file mode 100644 index 00000000..f3548574 --- /dev/null +++ b/app/src/trait/mouse_processor.h @@ -0,0 +1,39 @@ +#ifndef SC_MOUSE_PROCESSOR_H +#define SC_MOUSE_PROCESSOR_H + +#include "common.h" + +#include +#include + +#include + +/** + * Mouse processor trait. + * + * Component able to process and inject mouse events should implement this + * trait. + */ +struct sc_mouse_processor { + const struct sc_mouse_processor_ops *ops; +}; + +struct sc_mouse_processor_ops { + void + (*process_mouse_motion)(struct sc_mouse_processor *mp, + const SDL_MouseMotionEvent *event); + + void + (*process_touch)(struct sc_mouse_processor *mp, + const SDL_TouchFingerEvent *event); + + void + (*process_mouse_button)(struct sc_mouse_processor *mp, + const SDL_MouseButtonEvent *event); + + void + (*process_mouse_wheel)(struct sc_mouse_processor *mp, + const SDL_MouseWheelEvent *event); +}; + +#endif From 207082977a01b931c4451c1954c8197878c3ba10 Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Fri, 10 Sep 2021 18:57:35 +0800 Subject: [PATCH 0024/1133] Add support for USB HID keyboard over AOAv2 This provides a better input experience, by simulating a physical keyboard. It converts SDL keyboard events to proper HID events, and send them over AOAv2. This is a rewriting and bugfix of the origin code from @amosbird: The feature is enabled the command line option -K or --hid-keyboard, and is only available on Linux, over USB. Refs Refs PR #2632 Signed-off-by: Romain Vimont --- BUILD.md | 10 +- README.md | 39 +++++ app/meson.build | 16 +- app/scrcpy.1 | 8 + app/src/aoa_hid.c | 368 +++++++++++++++++++++++++++++++++++++++++ app/src/aoa_hid.h | 65 ++++++++ app/src/cli.c | 14 +- app/src/hid_keyboard.c | 302 +++++++++++++++++++++++++++++++++ app/src/hid_keyboard.h | 42 +++++ app/src/scrcpy.c | 77 ++++++++- app/src/scrcpy.h | 9 +- 11 files changed, 939 insertions(+), 11 deletions(-) create mode 100644 app/src/aoa_hid.c create mode 100644 app/src/aoa_hid.h create mode 100644 app/src/hid_keyboard.c create mode 100644 app/src/hid_keyboard.h diff --git a/BUILD.md b/BUILD.md index 69475f2c..5edb5a21 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,7 +14,8 @@ 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 + libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ + libusb-1.0-0 libusb-dev ``` Then clone the repo and execute the installation script @@ -88,11 +89,12 @@ Install the required packages from your package manager. ```bash # runtime dependencies -sudo apt install ffmpeg libsdl2-2.0-0 adb +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 sudo apt install openjdk-11-jdk @@ -114,7 +116,7 @@ pip3 install meson sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm # client build dependencies -sudo dnf install SDL2-devel ffms2-devel meson gcc make +sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make # server build dependencies sudo dnf install java-devel diff --git a/README.md b/README.md index 84dd5ff5..fdd74087 100644 --- a/README.md +++ b/README.md @@ -673,6 +673,39 @@ 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 @@ -692,6 +725,9 @@ 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 @@ -707,6 +743,9 @@ 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 diff --git a/app/meson.build b/app/meson.build index 46d3994f..f82d37b3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -42,6 +42,14 @@ if v4l2_support src += [ 'src/v4l2_sink.c' ] endif +aoa_hid_support = host_machine.system() == 'linux' +if aoa_hid_support + src += [ + 'src/aoa_hid.c', + 'src/hid_keyboard.c', + ] +endif + check_functions = [ 'strdup' ] @@ -62,8 +70,11 @@ if not get_option('crossbuild_windows') dependencies += dependency('libavdevice') endif -else + if aoa_hid_support + dependencies += dependency('libusb-1.0') + endif +else # cross-compile mingw32 build (from Linux to Windows) prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' @@ -140,6 +151,9 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == ' # enable V4L2 support (linux only) conf.set('HAVE_V4L2', v4l2_support) +# enable HID over AOA support (linux only) +conf.set('HAVE_AOA_HID', aoa_hid_support) + configure_file(configuration: conf, output: 'config.h') src_dir = include_directories('src') diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1b69a065..46db6e1d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -82,6 +82,14 @@ Start in fullscreen. .B \-h, \-\-help Print this help. +.TP +.B \-K, \-\-hid\-keyboard +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 \-\-legacy\-paste Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c new file mode 100644 index 00000000..7fc0f34c --- /dev/null +++ b/app/src/aoa_hid.c @@ -0,0 +1,368 @@ +#include "util/log.h" + +#include +#include + +#include "aoa_hid.h" + +// See . +#define ACCESSORY_REGISTER_HID 54 +#define ACCESSORY_SET_HID_REPORT_DESC 56 +#define ACCESSORY_SEND_HID_EVENT 57 +#define ACCESSORY_UNREGISTER_HID 55 + +#define DEFAULT_TIMEOUT 1000 + +static void +sc_hid_event_log(const struct sc_hid_event *event) { + // HID Event: [00] FF FF FF FF... + assert(event->size); + unsigned buffer_size = event->size * 3 + 1; + char *buffer = malloc(buffer_size); + if (!buffer) { + return; + } + 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); + 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; +} + +void +sc_hid_event_destroy(struct sc_hid_event *hid_event) { + free(hid_event->buffer); +} + +static inline void +log_libusb_error(enum libusb_error errcode) { + LOGW("libusb error: %s", libusb_strerror(errcode)); +} + +static bool +accept_device(libusb_device *device, const char *serial) { + // do not log any USB error in this function, it is expected that many USB + // devices available on the computer have permission restrictions + + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); + + if (!desc.iSerialNumber) { + return false; + } + + libusb_device_handle *handle; + int result = libusb_open(device, &handle); + if (result < 0) { + return false; + } + + char buffer[128]; + result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, + (unsigned char *) buffer, + sizeof(buffer)); + libusb_close(handle); + if (result < 0) { + return false; + } + + buffer[sizeof(buffer) - 1] = '\0'; // just in case + + // accept the device if its serial matches + return !strcmp(buffer, serial); +} + +static libusb_device * +sc_aoa_find_usb_device(const char *serial) { + if (!serial) { + return NULL; + } + + libusb_device **list; + libusb_device *result = NULL; + ssize_t count = libusb_get_device_list(NULL, &list); + if (count < 0) { + log_libusb_error((enum libusb_error) count); + return NULL; + } + + for (size_t i = 0; i < (size_t) count; ++i) { + libusb_device *device = list[i]; + + if (accept_device(device, serial)) { + result = libusb_ref_device(device); + break; + } + } + libusb_free_device_list(list, 1); + return result; +} + +static int +sc_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); + return result; + } + return 0; +} + +bool +sc_aoa_init(struct sc_aoa *aoa, const char *serial) { + cbuf_init(&aoa->queue); + + if (!sc_mutex_init(&aoa->mutex)) { + return false; + } + + if (!sc_cond_init(&aoa->event_cond)) { + sc_mutex_destroy(&aoa->mutex); + return false; + } + + if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) { + sc_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); + return false; + } + + aoa->usb_device = sc_aoa_find_usb_device(serial); + if (!aoa->usb_device) { + LOGW("USB device of serial %s not found", serial); + libusb_exit(aoa->usb_context); + sc_mutex_destroy(&aoa->mutex); + sc_cond_destroy(&aoa->event_cond); + return false; + } + + if (sc_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); + sc_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); + return false; + } + + aoa->stopped = false; + + return true; +} + +void +sc_aoa_destroy(struct sc_aoa *aoa) { + // Destroy remaining events + struct sc_hid_event event; + while (cbuf_take(&aoa->queue, &event)) { + sc_hid_event_destroy(&event); + } + + libusb_close(aoa->usb_handle); + libusb_unref_device(aoa->usb_device); + libusb_exit(aoa->usb_context); + sc_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); +} + +static bool +sc_aoa_register_hid(struct sc_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; + // + // value (arg0): accessory assigned ID for the HID device + // index (arg1): total length of the HID report descriptor + uint16_t value = accessory_id; + uint16_t index = report_desc_size; + unsigned char *buffer = NULL; + uint16_t length = 0; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, + DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + return false; + } + + return true; +} + +static bool +sc_aoa_set_hid_report_desc(struct sc_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; + uint8_t request = ACCESSORY_SET_HID_REPORT_DESC; + /** + * If the HID descriptor is longer than the endpoint zero max packet size, + * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC + * commands. The data for the descriptor must be sent sequentially + * if multiple packets are needed. + * + * + * libusb handles packet abstraction internally, so we don't need to care + * about bMaxPacketSize0 here. + * + * See + */ + // value (arg0): accessory assigned ID for the HID device + // index (arg1): offset of data (buffer) in descriptor + uint16_t value = accessory_id; + uint16_t index = 0; + // libusb_control_transfer expects a pointer to non-const + unsigned char *buffer = (unsigned char *) report_desc; + uint16_t length = report_desc_size; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, + DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + return false; + } + + return true; +} + +bool +sc_aoa_setup_hid(struct sc_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); + if (!ok) { + return false; + } + + ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc, + report_desc_size); + if (!ok) { + if (!sc_aoa_unregister_hid(aoa, accessory_id)) { + LOGW("Could not unregister HID"); + } + return false; + } + + return true; +} + +static bool +sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { + uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + uint8_t request = ACCESSORY_SEND_HID_EVENT; + // + // value (arg0): accessory assigned ID for the HID device + // index (arg1): 0 (unused) + uint16_t value = event->accessory_id; + uint16_t index = 0; + unsigned char *buffer = event->buffer; + uint16_t length = event->size; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, + DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + return false; + } + + return true; +} + +bool +sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { + uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + uint8_t request = ACCESSORY_UNREGISTER_HID; + // + // value (arg0): accessory assigned ID for the HID device + // index (arg1): 0 + uint16_t value = accessory_id; + uint16_t index = 0; + unsigned char *buffer = NULL; + uint16_t length = 0; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, + DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + return false; + } + + return true; +} + +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); + } + + sc_mutex_lock(&aoa->mutex); + bool was_empty = cbuf_is_empty(&aoa->queue); + bool res = cbuf_push(&aoa->queue, *event); + if (was_empty) { + sc_cond_signal(&aoa->event_cond); + } + sc_mutex_unlock(&aoa->mutex); + return res; +} + +static int +run_aoa_thread(void *data) { + struct sc_aoa *aoa = data; + + for (;;) { + sc_mutex_lock(&aoa->mutex); + while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) { + sc_cond_wait(&aoa->event_cond, &aoa->mutex); + } + if (aoa->stopped) { + // Stop immediately, do not process further events + sc_mutex_unlock(&aoa->mutex); + break; + } + struct sc_hid_event event; + bool non_empty = cbuf_take(&aoa->queue, &event); + assert(non_empty); + (void) non_empty; + sc_mutex_unlock(&aoa->mutex); + + bool ok = sc_aoa_send_hid_event(aoa, &event); + sc_hid_event_destroy(&event); + if (!ok) { + LOGW("Could not send HID event to USB device"); + } + } + return 0; +} + +bool +sc_aoa_start(struct sc_aoa *aoa) { + LOGD("Starting AOA thread"); + + bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa); + if (!ok) { + LOGC("Could not start AOA thread"); + return false; + } + + return true; +} + +void +sc_aoa_stop(struct sc_aoa *aoa) { + sc_mutex_lock(&aoa->mutex); + aoa->stopped = true; + sc_cond_signal(&aoa->event_cond); + sc_mutex_unlock(&aoa->mutex); +} + +void +sc_aoa_join(struct sc_aoa *aoa) { + sc_thread_join(&aoa->thread, NULL); +} diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h new file mode 100644 index 00000000..11b879ce --- /dev/null +++ b/app/src/aoa_hid.h @@ -0,0 +1,65 @@ +#ifndef SC_AOA_HID_H +#define SC_AOA_HID_H + +#include +#include + +#include + +#include "scrcpy.h" +#include "util/cbuf.h" +#include "util/thread.h" + +struct sc_hid_event { + uint16_t accessory_id; + unsigned char *buffer; + uint16_t size; +}; + +// 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); + +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 { + libusb_context *usb_context; + libusb_device *usb_device; + libusb_device_handle *usb_handle; + sc_thread thread; + sc_mutex mutex; + sc_cond event_cond; + bool stopped; + struct sc_hid_event_queue queue; +}; + +bool +sc_aoa_init(struct sc_aoa *aoa, const char *serial); + +void +sc_aoa_destroy(struct sc_aoa *aoa); + +bool +sc_aoa_start(struct sc_aoa *aoa); + +void +sc_aoa_stop(struct sc_aoa *aoa); + +void +sc_aoa_join(struct sc_aoa *aoa); + +bool +sc_aoa_setup_hid(struct sc_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); + +bool +sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event); + +#endif diff --git a/app/src/cli.c b/app/src/cli.c index d22096ca..a0a508af 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -76,6 +76,14 @@ scrcpy_print_usage(const char *arg0) { " -f, --fullscreen\n" " Start in fullscreen.\n" "\n" + " -K, --hid-keyboard\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" + " injection method.\n" + " It may only work over USB, and is currently only supported\n" + " on Linux.\n" + "\n" " -h, --help\n" " Print this help.\n" "\n" @@ -738,6 +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'}, {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, {"lock-video-orientation", optional_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, @@ -784,7 +793,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w", + while ((c = getopt_long(argc, argv, "b:c:fF:hKm:nNp:r:s:StTvV:w", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -817,6 +826,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 'h': args->help = true; break; + case 'K': + opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; + break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c new file mode 100644 index 00000000..2599e1d2 --- /dev/null +++ b/app/src/hid_keyboard.c @@ -0,0 +1,302 @@ +#include "hid_keyboard.h" + +#include +#include + +#include "util/log.h" + +/** Downcast key processor to hid_keyboard */ +#define DOWNCAST(KP) container_of(KP, struct sc_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_INDEX_MODIFIER 0 +#define HID_KEYBOARD_INDEX_KEYS 2 + +// USB HID protocol says 6 keys in an event is the requirement for BIOS +// keyboard support, though OS could support more keys via modifying the report +// desc. 6 should be enough for scrcpy. +#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 + +/** + * For HID over AOAv2, only report descriptor is needed. + * + * The specification is available here: + * + * + * In particular, read: + * - 6.2.2 Report Descriptor + * - Appendix B.1 Protocol 1 (Keyboard) + * - Appendix C: Keyboard Implementation + * + * Normally a basic HID keyboard uses 8 bytes: + * Modifier Reserved Key Key Key Key Key Key + * + * You can dump your device's report descriptor with: + * + * sudo usbhid-dump -m vid:pid -e descriptor + * + * (change vid:pid' to your device's vendor ID and product ID). + */ +static const unsigned char keyboard_report_desc[] = { + // Usage Page (Generic Desktop) + 0x05, 0x01, + // Usage (Keyboard) + 0x09, 0x06, + + // Collection (Application) + 0xA1, 0x01, + + // Usage Page (Key Codes) + 0x05, 0x07, + // Usage Minimum (224) + 0x19, 0xE0, + // Usage Maximum (231) + 0x29, 0xE7, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum (1) + 0x25, 0x01, + // Report Size (1) + 0x75, 0x01, + // Report Count (8) + 0x95, 0x08, + // Input (Data, Variable, Absolute): Modifier byte + 0x81, 0x02, + + // Report Size (8) + 0x75, 0x08, + // Report Count (1) + 0x95, 0x01, + // Input (Constant): Reserved byte + 0x81, 0x01, + + // Usage Page (LEDs) + 0x05, 0x08, + // Usage Minimum (1) + 0x19, 0x01, + // Usage Maximum (5) + 0x29, 0x05, + // Report Size (1) + 0x75, 0x01, + // Report Count (5) + 0x95, 0x05, + // Output (Data, Variable, Absolute): LED report + 0x91, 0x02, + + // Report Size (3) + 0x75, 0x03, + // Report Count (1) + 0x95, 0x01, + // Output (Constant): LED report padding + 0x91, 0x01, + + // Usage Page (Key Codes) + 0x05, 0x07, + // Usage Minimum (0) + 0x19, 0x00, + // Usage Maximum (101) + 0x29, SC_HID_KEYBOARD_KEYS - 1, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum(101) + 0x25, SC_HID_KEYBOARD_KEYS - 1, + // Report Size (8) + 0x75, 0x08, + // Report Count (6) + 0x95, HID_KEYBOARD_MAX_KEYS, + // Input (Data, Array): Keys + 0x81, 0x00, + + // End Collection + 0xC0 +}; + +static unsigned char +sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { + unsigned char modifiers = HID_MODIFIER_NONE; + if (mod & KMOD_LCTRL) { + modifiers |= HID_MODIFIER_LEFT_CONTROL; + } + if (mod & KMOD_LSHIFT) { + modifiers |= HID_MODIFIER_LEFT_SHIFT; + } + if (mod & KMOD_LALT) { + modifiers |= HID_MODIFIER_LEFT_ALT; + } + if (mod & KMOD_LGUI) { + modifiers |= HID_MODIFIER_LEFT_GUI; + } + if (mod & KMOD_RCTRL) { + modifiers |= HID_MODIFIER_RIGHT_CONTROL; + } + if (mod & KMOD_RSHIFT) { + modifiers |= HID_MODIFIER_RIGHT_SHIFT; + } + if (mod & KMOD_RALT) { + modifiers |= HID_MODIFIER_RIGHT_ALT; + } + if (mod & KMOD_RGUI) { + modifiers |= HID_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; + } + + buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; + buffer[1] = HID_RESERVED; + memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); + + sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer, + HID_KEYBOARD_EVENT_SIZE); + return true; +} + +static inline bool +scancode_is_modifier(SDL_Scancode scancode) { + return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI; +} + +static bool +convert_hid_keyboard_event(struct sc_hid_keyboard *kb, + struct sc_hid_event *hid_event, + const SDL_KeyboardEvent *event) { + 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 + 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 void +sc_key_processor_process_key(struct sc_key_processor *kp, + const SDL_KeyboardEvent *event) { + if (event->repeat) { + // In USB HID protocol, key repeat is handled by the host (Android), so + // just ignore key repeat here. + return; + } + + struct sc_hid_keyboard *kb = DOWNCAST(kp); + + struct sc_hid_event hid_event; + // Not all keys are supported, just ignore unsupported keys + if (convert_hid_keyboard_event(kb, &hid_event, event)) { + if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { + sc_hid_event_destroy(&hid_event); + LOGW("Could request HID event"); + } + } +} + +static void +sc_key_processor_process_text(struct sc_key_processor *kp, + const SDL_TextInputEvent *event) { + (void) kp; + (void) event; + + // Never forward text input via HID (all the keys are injected separately) +} + +bool +sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { + kb->aoa = aoa; + + bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID, + keyboard_report_desc, + ARRAY_LEN(keyboard_report_desc)); + if (!ok) { + LOGW("Register HID keyboard failed"); + return false; + } + + // Reset all states + memset(kb->keys, false, SC_HID_KEYBOARD_KEYS); + + static const struct sc_key_processor_ops ops = { + .process_key = sc_key_processor_process_key, + .process_text = sc_key_processor_process_text, + }; + + kb->key_processor.ops = &ops; + + return true; +} + +void +sc_hid_keyboard_destroy(struct sc_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); + if (!ok) { + LOGW("Could not unregister HID keyboard"); + } +} diff --git a/app/src/hid_keyboard.h b/app/src/hid_keyboard.h new file mode 100644 index 00000000..d8276cad --- /dev/null +++ b/app/src/hid_keyboard.h @@ -0,0 +1,42 @@ +#ifndef SC_HID_KEYBOARD_H +#define SC_HID_KEYBOARD_H + +#include "common.h" + +#include + +#include "aoa_hid.h" +#include "trait/key_processor.h" + +// See "SDL2/SDL_scancode.h". +// 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 + +/** + * HID keyboard events are sequence-based, every time keyboard state changes + * it sends an array of currently pressed keys, the host is responsible for + * compare events and determine which key becomes pressed and which key becomes + * released. In order to convert SDL_KeyboardEvent to HID events, we first use + * an array of keys to save each keys' state. And when a SDL_KeyboardEvent was + * emitted, we updated our state, and then we use a loop to generate HID + * events. The sequence of array elements is unimportant and when too much keys + * pressed at the same time (more than report count), we should generate + * phantom state. Don't forget that modifiers should be updated too, even for + * phantom state. + */ +struct sc_hid_keyboard { + struct sc_key_processor key_processor; // key processor trait + + struct sc_aoa *aoa; + bool keys[SC_HID_KEYBOARD_KEYS]; +}; + +bool +sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa); + +void +sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 50250e2c..c4041562 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -18,6 +18,9 @@ #include "events.h" #include "file_handler.h" #include "input_manager.h" +#ifdef HAVE_AOA_HID +# include "hid_keyboard.h" +#endif #include "keyboard_inject.h" #include "mouse_inject.h" #include "recorder.h" @@ -41,7 +44,15 @@ struct scrcpy { #endif struct controller controller; struct file_handler file_handler; - struct sc_keyboard_inject keyboard_inject; +#ifdef HAVE_AOA_HID + struct sc_aoa aoa; +#endif + union { + struct sc_keyboard_inject keyboard_inject; +#ifdef HAVE_AOA_HID + struct sc_hid_keyboard keyboard_hid; +#endif + }; struct sc_mouse_inject mouse_inject; struct input_manager input_manager; }; @@ -243,7 +254,7 @@ stream_on_eos(struct stream *stream, void *userdata) { } bool -scrcpy(const struct scrcpy_options *options) { +scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; struct scrcpy *s = &scrcpy; @@ -260,6 +271,9 @@ scrcpy(const struct scrcpy_options *options) { bool v4l2_sink_initialized = false; #endif bool stream_started = false; +#ifdef HAVE_AOA_HID + bool aoa_hid_initialized = false; +#endif bool controller_initialized = false; bool controller_started = false; bool screen_initialized = false; @@ -419,8 +433,50 @@ scrcpy(const struct scrcpy_options *options) { struct sc_mouse_processor *mp = NULL; if (options->control) { - sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, options); - kp = &s->keyboard_inject.key_processor; + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { +#ifdef HAVE_AOA_HID + bool aoa_hid_ok = false; + if (!sc_aoa_init(&s->aoa, options->serial)) { + goto aoa_hid_end; + } + + if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { + sc_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); + goto aoa_hid_end; + } + + aoa_hid_ok = true; + kp = &s->keyboard_hid.key_processor; + + aoa_hid_initialized = true; + +aoa_hid_end: + if (!aoa_hid_ok) { + LOGE("Failed to enable HID over AOA, " + "fallback to default keyboard injection method " + "(-K/--hid-keyboard 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)"); + options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; +#endif + } + + // keyboard_input_mode may have been reset if HID mode failed + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) { + sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, + options); + kp = &s->keyboard_inject.key_processor; + } sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen); mp = &s->mouse_inject.mouse_processor; @@ -439,6 +495,12 @@ scrcpy(const struct scrcpy_options *options) { end: // The stream is not stopped explicitly, because it will stop by itself on // end-of-stream +#ifdef HAVE_AOA_HID + if (aoa_hid_initialized) { + sc_hid_keyboard_destroy(&s->keyboard_hid); + sc_aoa_stop(&s->aoa); + } +#endif if (controller_started) { controller_stop(&s->controller); } @@ -466,6 +528,13 @@ end: } #endif +#ifdef HAVE_AOA_HID + if (aoa_hid_initialized) { + sc_aoa_join(&s->aoa); + sc_aoa_destroy(&s->aoa); + } +#endif + // Destroy the screen only after the stream is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8b76fb25..8cf4a917 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -33,6 +33,11 @@ enum sc_lock_video_orientation { 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 { @@ -68,6 +73,7 @@ struct scrcpy_options { 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; @@ -112,6 +118,7 @@ struct scrcpy_options { .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, \ @@ -151,6 +158,6 @@ struct scrcpy_options { } bool -scrcpy(const struct scrcpy_options *options); +scrcpy(struct scrcpy_options *options); #endif From eaf4afaad9c4217c04f053860d7e587ead8b49ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 14 Nov 2020 22:12:07 +0100 Subject: [PATCH 0025/1133] 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. --- app/src/sys/unix/process.c | 175 +++++++++++++++++++++++++++++-------- app/src/sys/win/process.c | 119 +++++++++++++++++++++++-- app/src/util/process.h | 16 ++++ 3 files changed, 266 insertions(+), 44 deletions(-) diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 8683a2da..451b6491 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -1,5 +1,6 @@ #include "util/process.h" +#include #include #include #include @@ -50,65 +51,151 @@ search_executable(const char *file) { } enum process_result -process_execute(const char *const argv[], pid_t *pid) { - int fd[2]; +process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, + int *pipe_stdout, int *pipe_stderr) { + int in[2]; + int out[2]; + int err[2]; + int internal[2]; // communication between parent and children - if (pipe(fd) == -1) { + if (pipe(internal) == -1) { perror("pipe"); return PROCESS_ERROR_GENERIC; } - - enum process_result ret = PROCESS_SUCCESS; + if (pipe_stdin) { + if (pipe(in) == -1) { + perror("pipe"); + close(internal[0]); + close(internal[1]); + return PROCESS_ERROR_GENERIC; + } + } + if (pipe_stdout) { + if (pipe(out) == -1) { + perror("pipe"); + // clean up + if (pipe_stdin) { + close(in[0]); + close(in[1]); + } + close(internal[0]); + close(internal[1]); + return PROCESS_ERROR_GENERIC; + } + } + if (pipe_stderr) { + if (pipe(err) == -1) { + perror("pipe"); + // clean up + if (pipe_stdout) { + close(out[0]); + close(out[1]); + } + if (pipe_stdin) { + close(in[0]); + close(in[1]); + } + close(internal[0]); + close(internal[1]); + return PROCESS_ERROR_GENERIC; + } + } *pid = fork(); if (*pid == -1) { perror("fork"); - ret = PROCESS_ERROR_GENERIC; - goto end; - } - - if (*pid > 0) { - // parent close write side - close(fd[1]); - fd[1] = -1; - // wait for EOF or receive errno from child - if (read(fd[0], &ret, sizeof(ret)) == -1) { - perror("read"); - ret = PROCESS_ERROR_GENERIC; - goto end; + // clean up + if (pipe_stderr) { + close(err[0]); + close(err[1]); + } + if (pipe_stdout) { + close(out[0]); + close(out[1]); + } + if (pipe_stdin) { + close(in[0]); + close(in[1]); + } + close(internal[0]); + close(internal[1]); + return PROCESS_ERROR_GENERIC; + } + + if (*pid == 0) { + if (pipe_stdin) { + if (in[0] != STDIN_FILENO) { + dup2(in[0], STDIN_FILENO); + close(in[0]); + } + close(in[1]); + } + if (pipe_stdout) { + if (out[1] != STDOUT_FILENO) { + dup2(out[1], STDOUT_FILENO); + close(out[1]); + } + close(out[0]); } - } else if (*pid == 0) { - // child close read side - close(fd[0]); - if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) { - execvp(argv[0], (char *const *)argv); - if (errno == ENOENT) { - ret = PROCESS_ERROR_MISSING_BINARY; - } else { - ret = PROCESS_ERROR_GENERIC; + if (pipe_stderr) { + if (err[1] != STDERR_FILENO) { + dup2(err[1], STDERR_FILENO); + close(err[1]); } + close(err[0]); + } + close(internal[0]); + enum process_result err; + if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { + execvp(argv[0], (char *const *) argv); perror("exec"); + err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY + : PROCESS_ERROR_GENERIC; } else { perror("fcntl"); - ret = PROCESS_ERROR_GENERIC; + err = PROCESS_ERROR_GENERIC; } - // send ret to the parent - if (write(fd[1], &ret, sizeof(ret)) == -1) { + // send err to the parent + if (write(internal[1], &err, sizeof(err)) == -1) { perror("write"); } - // close write side before exiting - close(fd[1]); + close(internal[1]); _exit(1); } -end: - if (fd[0] != -1) { - close(fd[0]); + // parent + assert(*pid > 0); + + close(internal[1]); + + enum process_result res = PROCESS_SUCCESS; + // wait for EOF or receive err from child + if (read(internal[0], &res, sizeof(res)) == -1) { + perror("read"); + res = PROCESS_ERROR_GENERIC; + } + + close(internal[0]); + + if (pipe_stdin) { + close(in[0]); + *pipe_stdin = in[1]; } - if (fd[1] != -1) { - close(fd[1]); + if (pipe_stdout) { + *pipe_stdout = out[0]; + close(out[1]); } - return ret; + if (pipe_stderr) { + *pipe_stderr = err[0]; + close(err[1]); + } + + return res; +} + +enum process_result +process_execute(const char *const argv[], pid_t *pid) { + return process_execute_redirect(argv, pid, NULL, NULL, NULL); } bool @@ -175,3 +262,15 @@ is_regular_file(const char *path) { } return S_ISREG(path_stat.st_mode); } + +ssize_t +read_pipe(int pipe, char *data, size_t len) { + return read(pipe, data, len); +} + +void +close_pipe(int pipe) { + if (close(pipe)) { + perror("close pipe"); + } +} diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index aafd5d34..9a846fad 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -23,38 +23,129 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { } enum process_result -process_execute(const char *const argv[], HANDLE *handle) { +process_execute_redirect(const char *const argv[], HANDLE *handle, + HANDLE *pipe_stdin, HANDLE *pipe_stdout, + HANDLE *pipe_stderr) { + enum process_result ret = PROCESS_ERROR_GENERIC; + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + HANDLE stdin_read_handle; + HANDLE stdout_write_handle; + HANDLE stderr_write_handle; + if (pipe_stdin) { + if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) { + perror("pipe"); + return PROCESS_ERROR_GENERIC; + } + if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) { + LOGE("SetHandleInformation stdin failed"); + goto error_close_stdin; + } + } + if (pipe_stdout) { + if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) { + perror("pipe"); + goto error_close_stdin; + } + if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) { + LOGE("SetHandleInformation stdout failed"); + goto error_close_stdout; + } + } + if (pipe_stderr) { + if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) { + perror("pipe"); + goto error_close_stdout; + } + if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) { + LOGE("SetHandleInformation stderr failed"); + goto error_close_stderr; + } + } + STARTUPINFOW si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); + if (pipe_stdin || pipe_stdout || pipe_stderr) { + si.dwFlags = STARTF_USESTDHANDLES; + if (pipe_stdin) { + si.hStdInput = stdin_read_handle; + } + if (pipe_stdout) { + si.hStdOutput = stdout_write_handle; + } + if (pipe_stderr) { + si.hStdError = stderr_write_handle; + } + } char *cmd = malloc(CMD_MAX_LEN); if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { *handle = NULL; - return PROCESS_ERROR_GENERIC; + goto error_close_stderr; } wchar_t *wide = utf8_to_wide_char(cmd); free(cmd); if (!wide) { LOGC("Could not allocate wide char string"); - return PROCESS_ERROR_GENERIC; + goto error_close_stderr; } - if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, + if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { free(wide); *handle = NULL; + if (GetLastError() == ERROR_FILE_NOT_FOUND) { - return PROCESS_ERROR_MISSING_BINARY; + ret = PROCESS_ERROR_MISSING_BINARY; } - return PROCESS_ERROR_GENERIC; + goto error_close_stderr; + } + + // These handles are used by the child process, close them for this process + if (pipe_stdin) { + CloseHandle(stdin_read_handle); + } + if (pipe_stdout) { + CloseHandle(stdout_write_handle); + } + if (pipe_stderr) { + CloseHandle(stderr_write_handle); } free(wide); *handle = pi.hProcess; + return PROCESS_SUCCESS; + +error_close_stderr: + if (pipe_stderr) { + CloseHandle(*pipe_stderr); + CloseHandle(stderr_write_handle); + } +error_close_stdout: + if (pipe_stdout) { + CloseHandle(*pipe_stdout); + CloseHandle(stdout_write_handle); + } +error_close_stdin: + if (pipe_stdin) { + CloseHandle(*pipe_stdin); + CloseHandle(stdin_read_handle); + } + + return ret; +} + +enum process_result +process_execute(const char *const argv[], HANDLE *handle) { + return process_execute_redirect(argv, handle, NULL, NULL, NULL); } bool @@ -116,3 +207,19 @@ is_regular_file(const char *path) { } return S_ISREG(path_stat.st_mode); } + +ssize_t +read_pipe(HANDLE pipe, char *data, size_t len) { + DWORD r; + if (!ReadFile(pipe, data, len, &r, NULL)) { + return -1; + } + return r; +} + +void +close_pipe(HANDLE pipe) { + if (!CloseHandle(pipe)) { + LOGW("Cannot close pipe"); + } +} diff --git a/app/src/util/process.h b/app/src/util/process.h index 6aca6bf5..a21374b6 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -18,6 +18,7 @@ # define NO_EXIT_CODE -1u // max value as unsigned typedef HANDLE process_t; typedef DWORD exit_code_t; + typedef HANDLE pipe_t; #else @@ -29,6 +30,7 @@ # define NO_EXIT_CODE -1 typedef pid_t process_t; typedef int exit_code_t; + typedef int pipe_t; #endif @@ -42,6 +44,14 @@ enum process_result { enum process_result process_execute(const char *const argv[], process_t *process); +enum process_result +process_execute_redirect(const char *const argv[], process_t *process, + pipe_t *pipe_stdin, pipe_t *pipe_stdout, + pipe_t *pipe_stderr); + +bool +process_terminate(process_t pid); + // kill the process bool process_terminate(process_t pid); @@ -83,4 +93,10 @@ get_local_file_path(const char *name); bool is_regular_file(const char *path); +ssize_t +read_pipe(pipe_t pipe, char *data, size_t len); + +void +close_pipe(pipe_t pipe); + #endif From 96b18dabaad06b7fe417ad4fbf52031d11dd6080 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Oct 2021 16:14:33 +0200 Subject: [PATCH 0026/1133] Expose adb execution with redirection Expose the redirection feature to the adb API. --- app/src/adb.c | 13 +++++++++++-- app/src/adb.h | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 5bb9df30..6b4d6fc0 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -109,7 +109,9 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { } process_t -adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { +adb_execute_redirect(const char *serial, const char *const adb_cmd[], + size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout, + pipe_t *pipe_stderr) { int i; process_t process; @@ -129,7 +131,9 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); argv[len + i] = NULL; - enum process_result r = process_execute(argv, &process); + enum process_result r = + process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout, + pipe_stderr); if (r != PROCESS_SUCCESS) { show_adb_err_msg(r, argv); process = PROCESS_NONE; @@ -139,6 +143,11 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { return process; } +process_t +adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { + return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL); +} + process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { diff --git a/app/src/adb.h b/app/src/adb.h index e27f34fa..d2f703a7 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -11,6 +11,11 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], size_t len); +process_t +adb_execute_redirect(const char *serial, const char *const adb_cmd[], + size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout, + pipe_t *pipe_stderr); + process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name); From 068148080975126ceac222c656ae65ee46553ef2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Oct 2021 16:20:46 +0200 Subject: [PATCH 0027/1133] Add read_pipe_all() Add a convenience function to read from a pipe until all requested data has been read. --- app/src/util/process.c | 15 +++++++++++++++ app/src/util/process.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/app/src/util/process.c b/app/src/util/process.c index a9af4d67..5d572c26 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -61,3 +61,18 @@ get_local_file_path(const char *name) { return file_path; } + +ssize_t +read_pipe_all(pipe_t pipe, char *data, size_t len) { + size_t copied = 0; + while (len > 0) { + ssize_t r = read_pipe(pipe, data, len); + if (r <= 0) { + return copied ? (ssize_t) copied : r; + } + len -= r; + data += r; + copied += r; + } + return copied; +} diff --git a/app/src/util/process.h b/app/src/util/process.h index a21374b6..d6471a16 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -96,6 +96,9 @@ is_regular_file(const char *path); ssize_t read_pipe(pipe_t pipe, char *data, size_t len); +ssize_t +read_pipe_all(pipe_t pipe, char *data, size_t len); + void close_pipe(pipe_t pipe); From d55015e4cfd419582a4c7710125571241a377480 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Oct 2021 16:30:07 +0200 Subject: [PATCH 0028/1133] Expose function to get the device serial Expose adb_get_serialno() to retrieve the device serial via the command "adb getserialno". --- app/src/adb.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ app/src/adb.h | 4 ++++ 2 files changed, 48 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 6b4d6fc0..7f7e28d8 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -233,3 +233,47 @@ adb_install(const char *serial, const char *local) { return proc; } + +static ssize_t +adb_execute_for_output(const char *serial, const char *const adb_cmd[], + size_t adb_cmd_len, char *buf, size_t buf_len, + const char *name) { + pipe_t pipe_stdout; + process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL, + &pipe_stdout, NULL); + + ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len); + close_pipe(pipe_stdout); + + if (!process_check_success(proc, name, true)) { + return -1; + } + + return r; +} + +static size_t +truncate_first_line(char *data, size_t len) { + data[len - 1] = '\0'; + char *eol = strpbrk(data, "\r\n"); + if (eol) { + *eol = '\0'; + len = eol - data; + } + return len; +} + +char * +adb_get_serialno(void) { + char buf[128]; + + const char *const adb_cmd[] = {"get-serialno"}; + ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd), + buf, sizeof(buf), "get-serialno"); + if (r <= 0) { + return NULL; + } + + truncate_first_line(buf, r); + return strdup(buf); +} diff --git a/app/src/adb.h b/app/src/adb.h index d2f703a7..34182fd3 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -36,4 +36,8 @@ adb_push(const char *serial, const char *local, const char *remote); process_t adb_install(const char *serial, const char *local); +// Return the result of "adb get-serialno". +char * +adb_get_serialno(void); + #endif From 511356710d7aced5f363d6a56b7beacba59e140f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 17 Oct 2021 16:58:31 +0200 Subject: [PATCH 0029/1133] 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. --- app/src/scrcpy.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c4041562..bfea6642 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -436,7 +436,23 @@ scrcpy(struct scrcpy_options *options) { if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { #ifdef HAVE_AOA_HID bool aoa_hid_ok = false; - if (!sc_aoa_init(&s->aoa, options->serial)) { + + char *serialno = NULL; + + const char *serial = options->serial; + if (!serial) { + serialno = adb_get_serialno(); + if (!serialno) { + LOGE("Could not get device serial"); + goto aoa_hid_end; + } + serial = serialno; + LOGI("Device serial: %s", serial); + } + + bool ok = sc_aoa_init(&s->aoa, serial); + free(serialno); + if (!ok) { goto aoa_hid_end; } From c96874b257db964c0172051f568534d99bf553b8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 21 Oct 2021 20:44:19 +0200 Subject: [PATCH 0030/1133] Synchronize HID keyboard state on first event When an AOA HID keyboard is registered, CAPSLOCK and NUMLOCK are both disabled, regardless of the state of the computer keyboard. To synchronize the state, on first key event, inject CAPSLOCK and/or NUMLOCK if necessary. --- app/src/hid_keyboard.c | 48 ++++++++++++++++++++++++++++++++++++++++++ app/src/hid_keyboard.h | 2 ++ 2 files changed, 50 insertions(+) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 2599e1d2..425516af 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -237,6 +237,45 @@ end: 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 sc_key_processor_process_key(struct sc_key_processor *kp, const SDL_KeyboardEvent *event) { @@ -251,6 +290,13 @@ sc_key_processor_process_key(struct sc_key_processor *kp, struct sc_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; + } + } if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could request HID event"); @@ -282,6 +328,8 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { // Reset all states memset(kb->keys, false, SC_HID_KEYBOARD_KEYS); + kb->mod_lock_synchronized = false; + static const struct sc_key_processor_ops ops = { .process_key = sc_key_processor_process_key, .process_text = sc_key_processor_process_text, diff --git a/app/src/hid_keyboard.h b/app/src/hid_keyboard.h index d8276cad..7173a898 100644 --- a/app/src/hid_keyboard.h +++ b/app/src/hid_keyboard.h @@ -31,6 +31,8 @@ struct sc_hid_keyboard { struct sc_aoa *aoa; bool keys[SC_HID_KEYBOARD_KEYS]; + + bool mod_lock_synchronized; }; bool From e4163321f00bb3830c6049bdb6c1515e7cc668a0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 21 Oct 2021 21:33:15 +0200 Subject: [PATCH 0031/1133] Delay HID events on Ctrl+v When Ctrl+v is pressed, a control is sent to the device to set the device clipboard before injecting Ctrl+v. With the InputManager method, it is guaranteed that the device synchronization is executed before handling Ctrl+v, since the commands are executed on the device in sequence. However, HID are injected from the computer, so there is no such guarantee. As a consequence, on Android, Ctrl+v triggers a paste with the old clipboard content. To workaround the issue, wait a bit (2 milliseconds) from the AOA thread before injecting the event, to leave enough time for the clipboard to be set before injecting Ctrl+v. --- app/src/aoa_hid.c | 17 +++++++++++++++++ app/src/aoa_hid.h | 2 ++ app/src/hid_keyboard.c | 13 +++++++++++++ 3 files changed, 32 insertions(+) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index 7fc0f34c..4c0b2bda 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -35,6 +35,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, hid_event->accessory_id = accessory_id; hid_event->buffer = buffer; hid_event->size = buffer_size; + hid_event->delay = 0; } void @@ -330,6 +331,22 @@ run_aoa_thread(void *data) { 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); diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h index 11b879ce..11cc57b8 100644 --- a/app/src/aoa_hid.h +++ b/app/src/aoa_hid.h @@ -9,11 +9,13 @@ #include "scrcpy.h" #include "util/cbuf.h" #include "util/thread.h" +#include "util/tick.h" struct sc_hid_event { uint16_t accessory_id; unsigned char *buffer; uint16_t size; + sc_tick delay; }; // Takes ownership of buffer diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 425516af..c6fba21c 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -297,6 +297,19 @@ sc_key_processor_process_key(struct sc_key_processor *kp, kb->mod_lock_synchronized = true; } } + + SDL_Keycode keycode = event->keysym.sym; + bool down = event->type == SDL_KEYDOWN; + bool ctrl = event->keysym.mod & KMOD_CTRL; + bool shift = event->keysym.mod & KMOD_SHIFT; + if (ctrl && !shift && keycode == SDLK_v && down) { + // Ctrl+v is pressed, so clipboard synchronization has been + // requested. Wait a bit so that the clipboard is set before + // injecting Ctrl+v via HID, otherwise it would paste the old + // clipboard content. + hid_event.delay = SC_TICK_FROM_MS(2); + } + if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could request HID event"); From 5222f213f4a588658a07441b5f91df152e671139 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 21 Oct 2021 21:38:01 +0200 Subject: [PATCH 0032/1133] Update FAQ to mention HID keyboard --- FAQ.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/FAQ.md b/FAQ.md index f74845a5..a1c2fb76 100644 --- a/FAQ.md +++ b/FAQ.md @@ -118,13 +118,17 @@ In developer options, enable: ### Special characters do not work -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]. +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). [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 From eb6afe76698e3d974bae4615c0cba2e2744183de Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Oct 2021 22:49:45 +0200 Subject: [PATCH 0033/1133] Move net_init() and net_cleanup() upwards These two functions are global, define them at the top of the implementation file. This is consistent with the header file. --- app/src/util/net.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index 2b5a0e5e..4b5a4874 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -19,6 +19,26 @@ typedef struct in_addr IN_ADDR; #endif +bool +net_init(void) { +#ifdef __WINDOWS__ + WSADATA wsa; + int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; + if (res < 0) { + LOGC("WSAStartup failed with error %d", res); + return false; + } +#endif + return true; +} + +void +net_cleanup(void) { +#ifdef __WINDOWS__ + WSACleanup(); +#endif +} + static void net_perror(const char *s) { #ifdef _WIN32 @@ -133,26 +153,6 @@ net_shutdown(socket_t socket, int how) { return !shutdown(socket, how); } -bool -net_init(void) { -#ifdef __WINDOWS__ - WSADATA wsa; - int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; - if (res < 0) { - LOGC("WSAStartup failed with error %d", res); - return false; - } -#endif - return true; -} - -void -net_cleanup(void) { -#ifdef __WINDOWS__ - WSACleanup(); -#endif -} - bool net_close(socket_t socket) { #ifdef __WINDOWS__ From 3adff37c2d33670937a4f3d94995a04b89d64f27 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Oct 2021 22:49:45 +0200 Subject: [PATCH 0034/1133] Use sc_ prefix for sockets Rename: - socket_t to sc_socket - INVALID_SOCKET to SC_INVALID_SOCKET --- app/src/controller.c | 2 +- app/src/controller.h | 4 ++-- app/src/receiver.c | 2 +- app/src/receiver.h | 4 ++-- app/src/server.c | 51 ++++++++++++++++++++++---------------------- app/src/server.h | 6 +++--- app/src/stream.c | 2 +- app/src/stream.h | 4 ++-- app/src/util/net.c | 38 ++++++++++++++++----------------- app/src/util/net.h | 38 ++++++++++++++++++--------------- 10 files changed, 78 insertions(+), 73 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index b85ac02d..e486ea72 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -5,7 +5,7 @@ #include "util/log.h" bool -controller_init(struct controller *controller, socket_t control_socket) { +controller_init(struct controller *controller, sc_socket control_socket) { cbuf_init(&controller->queue); bool ok = receiver_init(&controller->receiver, control_socket); diff --git a/app/src/controller.h b/app/src/controller.h index c53d0a61..e7004131 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -14,7 +14,7 @@ struct control_msg_queue CBUF(struct control_msg, 64); struct controller { - socket_t control_socket; + sc_socket control_socket; sc_thread thread; sc_mutex mutex; sc_cond msg_cond; @@ -24,7 +24,7 @@ struct controller { }; bool -controller_init(struct controller *controller, socket_t control_socket); +controller_init(struct controller *controller, sc_socket control_socket); void controller_destroy(struct controller *controller); diff --git a/app/src/receiver.c b/app/src/receiver.c index 337d2a17..b5cf9b39 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -7,7 +7,7 @@ #include "util/log.h" bool -receiver_init(struct receiver *receiver, socket_t control_socket) { +receiver_init(struct receiver *receiver, sc_socket control_socket) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { return false; diff --git a/app/src/receiver.h b/app/src/receiver.h index 36523b62..99f128a4 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -11,13 +11,13 @@ // receive events from the device // managed by the controller struct receiver { - socket_t control_socket; + sc_socket control_socket; sc_thread thread; sc_mutex mutex; }; bool -receiver_init(struct receiver *receiver, socket_t control_socket); +receiver_init(struct receiver *receiver, sc_socket control_socket); void receiver_destroy(struct receiver *receiver); diff --git a/app/src/server.c b/app/src/server.c index 4c1a43f5..f786f62d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -109,7 +109,7 @@ disable_tunnel(struct server *server) { return disable_tunnel_reverse(server->serial); } -static socket_t +static sc_socket listen_on_port(uint16_t port) { #define IPV4_LOCALHOST 0x7F000001 return net_listen(IPV4_LOCALHOST, port, 1); @@ -132,7 +132,7 @@ 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 != INVALID_SOCKET) { + if (server->server_socket != SC_INVALID_SOCKET) { // success server->local_port = port; return true; @@ -289,11 +289,11 @@ execute_server(struct server *server, const struct server_params *params) { return adb_execute(server->serial, cmd, ARRAY_LEN(cmd)); } -static socket_t +static sc_socket connect_and_read_byte(uint16_t port) { - socket_t socket = net_connect(IPV4_LOCALHOST, port); - if (socket == INVALID_SOCKET) { - return INVALID_SOCKET; + sc_socket socket = net_connect(IPV4_LOCALHOST, port); + if (socket == SC_INVALID_SOCKET) { + return SC_INVALID_SOCKET; } char byte; @@ -302,17 +302,17 @@ 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 INVALID_SOCKET; + return SC_INVALID_SOCKET; } return socket; } -static socket_t +static sc_socket connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { do { LOGD("Remaining connection attempts: %d", (int) attempts); - socket_t socket = connect_and_read_byte(port); - if (socket != INVALID_SOCKET) { + sc_socket socket = connect_and_read_byte(port); + if (socket != SC_INVALID_SOCKET) { // it worked! return socket; } @@ -320,12 +320,12 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { SDL_Delay(delay); } } while (--attempts > 0); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } static void -close_socket(socket_t socket) { - assert(socket != INVALID_SOCKET); +close_socket(sc_socket socket) { + assert(socket != SC_INVALID_SOCKET); net_shutdown(socket, SHUT_RDWR); if (!net_close(socket)) { LOGW("Could not close socket"); @@ -352,9 +352,9 @@ server_init(struct server *server) { server->process_terminated = false; - server->server_socket = INVALID_SOCKET; - server->video_socket = INVALID_SOCKET; - server->control_socket = INVALID_SOCKET; + server->server_socket = SC_INVALID_SOCKET; + server->video_socket = SC_INVALID_SOCKET; + server->control_socket = SC_INVALID_SOCKET; server->local_port = 0; @@ -376,7 +376,7 @@ run_wait_server(void *data) { // no need for synchronization, server_socket is initialized before this // thread was created - if (server->server_socket != INVALID_SOCKET + if (server->server_socket != SC_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()). @@ -444,7 +444,8 @@ error: } static bool -device_read_info(socket_t device_socket, char *device_name, struct size *size) { +device_read_info(sc_socket device_socket, char *device_name, + struct size *size) { unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; ssize_t r = net_recv_all(device_socket, buf, sizeof(buf)); if (r < DEVICE_NAME_FIELD_LENGTH + 4) { @@ -467,12 +468,12 @@ bool server_connect_to(struct server *server, char *device_name, struct size *size) { if (!server->tunnel_forward) { server->video_socket = net_accept(server->server_socket); - if (server->video_socket == INVALID_SOCKET) { + if (server->video_socket == SC_INVALID_SOCKET) { return false; } server->control_socket = net_accept(server->server_socket); - if (server->control_socket == INVALID_SOCKET) { + if (server->control_socket == SC_INVALID_SOCKET) { // the video_socket will be cleaned up on destroy return false; } @@ -488,14 +489,14 @@ server_connect_to(struct server *server, char *device_name, struct size *size) { uint32_t delay = 100; // ms server->video_socket = connect_to_server(server->local_port, attempts, delay); - if (server->video_socket == INVALID_SOCKET) { + if (server->video_socket == SC_INVALID_SOCKET) { return false; } // we know that the device is listening, we don't need several attempts server->control_socket = net_connect(IPV4_LOCALHOST, server->local_port); - if (server->control_socket == INVALID_SOCKET) { + if (server->control_socket == SC_INVALID_SOCKET) { return false; } } @@ -510,14 +511,14 @@ server_connect_to(struct server *server, char *device_name, struct size *size) { void server_stop(struct server *server) { - if (server->server_socket != INVALID_SOCKET + if (server->server_socket != SC_INVALID_SOCKET && !atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(server->server_socket); } - if (server->video_socket != INVALID_SOCKET) { + if (server->video_socket != SC_INVALID_SOCKET) { close_socket(server->video_socket); } - if (server->control_socket != INVALID_SOCKET) { + if (server->control_socket != SC_INVALID_SOCKET) { close_socket(server->control_socket); } diff --git a/app/src/server.h b/app/src/server.h index c249b374..b6d2255a 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -24,9 +24,9 @@ struct server { 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; + 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" diff --git a/app/src/stream.c b/app/src/stream.c index adc6277f..4c770250 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -260,7 +260,7 @@ end: } void -stream_init(struct stream *stream, socket_t socket, +stream_init(struct stream *stream, sc_socket socket, const struct stream_callbacks *cbs, void *cbs_userdata) { stream->socket = socket; stream->pending = NULL; diff --git a/app/src/stream.h b/app/src/stream.h index d7047c95..362bc4a7 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -14,7 +14,7 @@ #define STREAM_MAX_SINKS 2 struct stream { - socket_t socket; + sc_socket 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, socket_t socket, +stream_init(struct stream *stream, sc_socket socket, const struct stream_callbacks *cbs, void *cbs_userdata); void diff --git a/app/src/util/net.c b/app/src/util/net.c index 4b5a4874..eb98ea59 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -55,12 +55,12 @@ net_perror(const char *s) { #endif } -socket_t +sc_socket net_connect(uint32_t addr, uint16_t port) { - socket_t sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == INVALID_SOCKET) { + sc_socket sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == SC_INVALID_SOCKET) { net_perror("socket"); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } SOCKADDR_IN sin; @@ -71,18 +71,18 @@ net_connect(uint32_t addr, uint16_t port) { if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("connect"); net_close(sock); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } return sock; } -socket_t +sc_socket net_listen(uint32_t addr, uint16_t port, int backlog) { - socket_t sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == INVALID_SOCKET) { + sc_socket sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == SC_INVALID_SOCKET) { net_perror("socket"); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } int reuse = 1; @@ -99,42 +99,42 @@ net_listen(uint32_t addr, uint16_t port, int backlog) { if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("bind"); net_close(sock); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } if (listen(sock, backlog) == SOCKET_ERROR) { net_perror("listen"); net_close(sock); - return INVALID_SOCKET; + return SC_INVALID_SOCKET; } return sock; } -socket_t -net_accept(socket_t server_socket) { +sc_socket +net_accept(sc_socket 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) { +net_recv(sc_socket 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) { +net_recv_all(sc_socket 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) { +net_send(sc_socket 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) { +net_send_all(sc_socket socket, const void *buf, size_t len) { size_t copied = 0; while (len > 0) { ssize_t w = send(socket, buf, len, 0); @@ -149,12 +149,12 @@ net_send_all(socket_t socket, const void *buf, size_t len) { } bool -net_shutdown(socket_t socket, int how) { +net_shutdown(sc_socket socket, int how) { return !shutdown(socket, how); } bool -net_close(socket_t socket) { +net_close(sc_socket socket) { #ifdef __WINDOWS__ return !closesocket(socket); #else diff --git a/app/src/util/net.h b/app/src/util/net.h index d3b1f941..31920fd6 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -8,15 +8,19 @@ #include #ifdef __WINDOWS__ + # include - #define SHUT_RD SD_RECEIVE - #define SHUT_WR SD_SEND - #define SHUT_RDWR SD_BOTH - typedef SOCKET socket_t; -#else +# define SHUT_RD SD_RECEIVE +# define SHUT_WR SD_SEND +# define SHUT_RDWR SD_BOTH +# define SC_INVALID_SOCKET INVALID_SOCKET + typedef SOCKET sc_socket; + +#else // not __WINDOWS__ + # include -# define INVALID_SOCKET -1 - typedef int socket_t; +# define SC_INVALID_SOCKET -1 + typedef int sc_socket; #endif bool @@ -25,33 +29,33 @@ net_init(void); void net_cleanup(void); -socket_t +sc_socket net_connect(uint32_t addr, uint16_t port); -socket_t +sc_socket net_listen(uint32_t addr, uint16_t port, int backlog); -socket_t -net_accept(socket_t server_socket); +sc_socket +net_accept(sc_socket server_socket); // the _all versions wait/retry until len bytes have been written/read ssize_t -net_recv(socket_t socket, void *buf, size_t len); +net_recv(sc_socket socket, void *buf, size_t len); ssize_t -net_recv_all(socket_t socket, void *buf, size_t len); +net_recv_all(sc_socket socket, void *buf, size_t len); ssize_t -net_send(socket_t socket, const void *buf, size_t len); +net_send(sc_socket socket, const void *buf, size_t len); ssize_t -net_send_all(socket_t socket, const void *buf, size_t len); +net_send_all(sc_socket socket, const void *buf, size_t len); // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) bool -net_shutdown(socket_t socket, int how); +net_shutdown(sc_socket socket, int how); bool -net_close(socket_t socket); +net_close(sc_socket socket); #endif From 3eac212af1206ae798436ff018f3d2d77840e759 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Oct 2021 22:49:45 +0200 Subject: [PATCH 0035/1133] Use net_send() from net_send_all() This will make net_send_all() continue to work even if net_send() behavior is changed. --- app/src/util/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index eb98ea59..678d6e67 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -137,7 +137,7 @@ ssize_t net_send_all(sc_socket socket, const void *buf, size_t len) { size_t copied = 0; while (len > 0) { - ssize_t w = send(socket, buf, len, 0); + ssize_t w = net_send(socket, buf, len); if (w == -1) { return copied ? (ssize_t) copied : -1; } From e5ea13770b9ddefbbb147b8cabcff9387f677f83 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Oct 2021 22:49:45 +0200 Subject: [PATCH 0036/1133] Add socket wrapper This paves the way to store an additional "closed" flag on Windows to interrupt and close properly. --- app/src/util/net.c | 76 ++++++++++++++++++++++++++++++++++++++-------- app/src/util/net.h | 6 ++-- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index 678d6e67..1e74214b 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -7,6 +7,7 @@ #ifdef __WINDOWS__ typedef int socklen_t; + typedef SOCKET sc_raw_socket; #else # include # include @@ -17,6 +18,7 @@ typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr SOCKADDR; typedef struct in_addr IN_ADDR; + typedef int sc_raw_socket; #endif bool @@ -39,6 +41,40 @@ 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; + + 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 @@ -57,7 +93,8 @@ net_perror(const char *s) { sc_socket net_connect(uint32_t addr, uint16_t port) { - sc_socket sock = socket(AF_INET, SOCK_STREAM, 0); + 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; @@ -68,7 +105,7 @@ net_connect(uint32_t addr, uint16_t port) { sin.sin_addr.s_addr = htonl(addr); sin.sin_port = htons(port); - if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { + if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("connect"); net_close(sock); return SC_INVALID_SOCKET; @@ -79,14 +116,15 @@ net_connect(uint32_t addr, uint16_t port) { sc_socket net_listen(uint32_t addr, uint16_t port, int backlog) { - sc_socket sock = socket(AF_INET, SOCK_STREAM, 0); + 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(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, + if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) { net_perror("setsockopt(SO_REUSEADDR)"); } @@ -96,13 +134,13 @@ net_listen(uint32_t addr, uint16_t port, int backlog) { 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) { + if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("bind"); net_close(sock); return SC_INVALID_SOCKET; } - if (listen(sock, backlog) == SOCKET_ERROR) { + if (listen(raw_sock, backlog) == SOCKET_ERROR) { net_perror("listen"); net_close(sock); return SC_INVALID_SOCKET; @@ -113,24 +151,32 @@ net_listen(uint32_t addr, uint16_t port, int backlog) { 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); - return accept(server_socket, (SOCKADDR *) &csin, &sinsize); + 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) { - return recv(socket, buf, len, 0); + 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) { - return recv(socket, buf, len, MSG_WAITALL); + 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) { - return send(socket, buf, len, 0); + sc_raw_socket raw_sock = unwrap(socket); + return send(raw_sock, buf, len, 0); } ssize_t @@ -150,14 +196,18 @@ net_send_all(sc_socket socket, const void *buf, size_t len) { bool net_shutdown(sc_socket socket, int how) { - return !shutdown(socket, how); + sc_raw_socket raw_sock = unwrap(socket); + return !shutdown(raw_sock, how); } bool net_close(sc_socket socket) { + sc_raw_socket raw_sock = unwrap(socket); + #ifdef __WINDOWS__ - return !closesocket(socket); + free(socket); + return !closesocket(raw_sock); #else - return !close(socket); + return !close(raw_sock); #endif } diff --git a/app/src/util/net.h b/app/src/util/net.h index 31920fd6..f40f0bb5 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -13,8 +13,10 @@ # define SHUT_RD SD_RECEIVE # define SHUT_WR SD_SEND # define SHUT_RDWR SD_BOTH -# define SC_INVALID_SOCKET INVALID_SOCKET - typedef SOCKET sc_socket; +# define SC_INVALID_SOCKET NULL + typedef struct sc_socket_windows { + SOCKET socket; + } *sc_socket; #else // not __WINDOWS__ From ac23bec14450a8f38ead09860564e42737ac047e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Oct 2021 22:49:45 +0200 Subject: [PATCH 0037/1133] Expose socket interruption On Linux, socket functions are unblocked by shutdown(), but on Windows they are unblocked by closesocket(). Expose net_interrupt() and net_close() to abstract these differences: - net_interrupt() calls shutdown() on Linux and closesocket() on Windows (if not already called); - net_close() calls close() on Linux and closesocket() on Windows (if not already called). This simplifies the server code, and prevents a data race on close (reported by TSAN) on Linux (but does not fix it on Windows): WARNING: ThreadSanitizer: data race (pid=836124) Write of size 8 at 0x7ba0000000d0 by main thread: #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8) #1 net_close ../app/src/util/net.c:211 (scrcpy+0x1c76b) #2 close_socket ../app/src/server.c:330 (scrcpy+0x19442) #3 server_stop ../app/src/server.c:522 (scrcpy+0x19e33) #4 scrcpy ../app/src/scrcpy.c:532 (scrcpy+0x156fc) #5 main ../app/src/main.c:92 (scrcpy+0x622a) Previous read of size 8 at 0x7ba0000000d0 by thread T6: #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6) #1 net_recv ../app/src/util/net.c:167 (scrcpy+0x1c5a7) #2 run_receiver ../app/src/receiver.c:76 (scrcpy+0x12819) #3 (libSDL2-2.0.so.0+0x84f40) --- app/src/server.c | 68 ++++++++++++++++++++++++---------------------- app/src/server.h | 1 - app/src/util/net.c | 23 ++++++++++++++-- app/src/util/net.h | 12 ++++---- 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index f786f62d..76d2910b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -323,21 +323,10 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { return SC_INVALID_SOCKET; } -static void -close_socket(sc_socket socket) { - assert(socket != SC_INVALID_SOCKET); - net_shutdown(socket, SHUT_RDWR); - if (!net_close(socket)) { - LOGW("Could not close socket"); - } -} - bool server_init(struct server *server) { server->serial = NULL; server->process = PROCESS_NONE; - atomic_flag_clear_explicit(&server->server_socket_closed, - memory_order_relaxed); bool ok = sc_mutex_init(&server->mutex); if (!ok) { @@ -376,12 +365,11 @@ run_wait_server(void *data) { // no need for synchronization, server_socket is initialized before this // thread was created - if (server->server_socket != SC_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); + if (server->server_socket != SC_INVALID_SOCKET) { + // Unblock any accept() + net_interrupt(server->server_socket); } + LOGD("Server terminated"); return 0; } @@ -430,14 +418,8 @@ server_start(struct server *server, const struct server_params *params) { 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); - } + // The server socket (if any) will be closed on server_destroy() + disable_tunnel(server); return false; @@ -479,11 +461,11 @@ server_connect_to(struct server *server, char *device_name, struct size *size) { } // we don't need the server socket anymore - 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() + if (!net_close(server->server_socket)) { + LOGW("Could not close server socket on connect"); } + // Do not attempt to close it again on server_destroy() + server->server_socket = SC_INVALID_SOCKET; } else { uint32_t attempts = 100; uint32_t delay = 100; // ms @@ -511,15 +493,20 @@ server_connect_to(struct server *server, char *device_name, struct size *size) { void server_stop(struct server *server) { - if (server->server_socket != SC_INVALID_SOCKET - && !atomic_flag_test_and_set(&server->server_socket_closed)) { - close_socket(server->server_socket); + if (server->server_socket != SC_INVALID_SOCKET) { + if (!net_interrupt(server->server_socket)) { + LOGW("Could not interrupt server socket"); + } } if (server->video_socket != SC_INVALID_SOCKET) { - close_socket(server->video_socket); + if (!net_interrupt(server->video_socket)) { + LOGW("Could not interrupt video socket"); + } } if (server->control_socket != SC_INVALID_SOCKET) { - close_socket(server->control_socket); + if (!net_interrupt(server->control_socket)) { + LOGW("Could not interrupt control socket"); + } } assert(server->process != PROCESS_NONE); @@ -556,6 +543,21 @@ server_stop(struct server *server) { 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"); + } + } free(server->serial); sc_cond_destroy(&server->process_terminated_cond); sc_mutex_destroy(&server->mutex); diff --git a/app/src/server.h b/app/src/server.h index b6d2255a..141075f7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -18,7 +18,6 @@ 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; diff --git a/app/src/util/net.c b/app/src/util/net.c index 1e74214b..cfc60433 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -1,5 +1,6 @@ #include "net.h" +#include #include #include @@ -55,6 +56,7 @@ wrap(sc_raw_socket sock) { } socket->socket = sock; + socket->closed = (atomic_flag) ATOMIC_FLAG_INIT; return socket; #else @@ -195,18 +197,33 @@ net_send_all(sc_socket socket, const void *buf, size_t len) { } bool -net_shutdown(sc_socket socket, int how) { +net_interrupt(sc_socket socket) { + assert(socket != SC_INVALID_SOCKET); + sc_raw_socket raw_sock = unwrap(socket); - return !shutdown(raw_sock, how); + +#ifdef __WINDOWS__ + if (!atomic_flag_test_and_set(&socket->closed)) { + return !closesocket(raw_sock); + } + return true; +#else + return !shutdown(raw_sock, SHUT_RDWR); +#endif } +#include 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 !closesocket(raw_sock); + return ret; #else return !close(raw_sock); #endif diff --git a/app/src/util/net.h b/app/src/util/net.h index f40f0bb5..c742c00e 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -10,12 +10,11 @@ #ifdef __WINDOWS__ # include -# define SHUT_RD SD_RECEIVE -# define SHUT_WR SD_SEND -# define SHUT_RDWR SD_BOTH +# include # define SC_INVALID_SOCKET NULL typedef struct sc_socket_windows { SOCKET socket; + atomic_flag closed; } *sc_socket; #else // not __WINDOWS__ @@ -53,10 +52,13 @@ net_send(sc_socket socket, const void *buf, size_t len); ssize_t net_send_all(sc_socket socket, const void *buf, size_t len); -// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both) +// Shutdown the socket (or close on Windows) so that any blocking send() or +// recv() are interrupted. bool -net_shutdown(sc_socket socket, int how); +net_interrupt(sc_socket socket); +// Close the socket. +// A socket must always be closed, even if net_interrupt() has been called. bool net_close(sc_socket socket); From e4d5c1ce363919e399958cf6342f63724ce4dd51 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Oct 2021 18:43:47 +0200 Subject: [PATCH 0038/1133] Move scrcpy option structs to options.h This will allow to define symbols in options.c without all the dependencies of scrcpy.c. --- app/src/aoa_hid.h | 1 - app/src/cli.c | 2 +- app/src/cli.h | 2 +- app/src/input_manager.h | 2 +- app/src/keyboard_inject.h | 2 +- app/src/main.c | 4 +- app/src/mouse_inject.h | 1 - app/src/options.h | 160 ++++++++++++++++++++++++++++++++++++++ app/src/recorder.h | 2 +- app/src/scrcpy.h | 153 +----------------------------------- app/src/screen.c | 2 +- app/src/server.h | 2 +- app/src/util/log.h | 2 +- app/tests/test_cli.c | 2 +- 14 files changed, 172 insertions(+), 165 deletions(-) create mode 100644 app/src/options.h diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h index 11cc57b8..24cef502 100644 --- a/app/src/aoa_hid.h +++ b/app/src/aoa_hid.h @@ -6,7 +6,6 @@ #include -#include "scrcpy.h" #include "util/cbuf.h" #include "util/thread.h" #include "util/tick.h" diff --git a/app/src/cli.c b/app/src/cli.c index a0a508af..cab25772 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -6,7 +6,7 @@ #include #include -#include "scrcpy.h" +#include "options.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/cli.h b/app/src/cli.h index 419f156f..b9361a9c 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -5,7 +5,7 @@ #include -#include "scrcpy.h" +#include "options.h" struct scrcpy_cli_args { struct scrcpy_options opts; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index bd9d7a1b..f018f98a 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -9,7 +9,7 @@ #include "controller.h" #include "fps_counter.h" -#include "scrcpy.h" +#include "options.h" #include "screen.h" #include "trait/key_processor.h" #include "trait/mouse_processor.h" diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h index e59de46d..f4ebe40e 100644 --- a/app/src/keyboard_inject.h +++ b/app/src/keyboard_inject.h @@ -6,7 +6,7 @@ #include #include "controller.h" -#include "scrcpy.h" +#include "options.h" #include "trait/key_processor.h" struct sc_keyboard_inject { diff --git a/app/src/main.c b/app/src/main.c index 2afa3c4e..51c13fd5 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -1,5 +1,3 @@ -#include "scrcpy.h" - #include "common.h" #include @@ -13,6 +11,8 @@ #include #include "cli.h" +#include "options.h" +#include "scrcpy.h" #include "util/log.h" static void diff --git a/app/src/mouse_inject.h b/app/src/mouse_inject.h index d5220db9..7dcf7e83 100644 --- a/app/src/mouse_inject.h +++ b/app/src/mouse_inject.h @@ -6,7 +6,6 @@ #include #include "controller.h" -#include "scrcpy.h" #include "screen.h" #include "trait/mouse_processor.h" diff --git a/app/src/options.h b/app/src/options.h new file mode 100644 index 00000000..04b8f6d2 --- /dev/null +++ b/app/src/options.h @@ -0,0 +1,160 @@ +#ifndef SCRCPY_OPTIONS_H +#define SCRCPY_OPTIONS_H + +#include "common.h" + +#include +#include +#include + +#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, \ +} + +#endif diff --git a/app/src/recorder.h b/app/src/recorder.h index 96caaf5f..f14523ff 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -7,7 +7,7 @@ #include #include "coords.h" -#include "scrcpy.h" +#include "options.h" #include "trait/packet_sink.h" #include "util/queue.h" #include "util/thread.h" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8cf4a917..cdcecda7 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -4,158 +4,7 @@ #include "common.h" #include -#include -#include - -#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, \ -} +#include "options.h" bool scrcpy(struct scrcpy_options *options); diff --git a/app/src/screen.c b/app/src/screen.c index 8a2748a9..7f442143 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -6,7 +6,7 @@ #include "events.h" #include "icon.h" -#include "scrcpy.h" +#include "options.h" #include "video_buffer.h" #include "util/log.h" diff --git a/app/src/server.h b/app/src/server.h index 141075f7..75594522 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -9,7 +9,7 @@ #include "adb.h" #include "coords.h" -#include "scrcpy.h" +#include "options.h" #include "util/log.h" #include "util/net.h" #include "util/thread.h" diff --git a/app/src/util/log.h b/app/src/util/log.h index 30934b5c..4157d6e5 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -5,7 +5,7 @@ #include -#include "scrcpy.h" +#include "options.h" #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 94740a9a..1682a72d 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -4,7 +4,7 @@ #include #include "cli.h" -#include "scrcpy.h" +#include "options.h" static void test_flag_version(void) { struct scrcpy_cli_args args = { From 27fa23846d5e82d0709001e045f35ca6572bcb5d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Oct 2021 18:43:47 +0200 Subject: [PATCH 0039/1133] Define default options as const struct This is more readable than a macro, and we could ifdef some fields. --- app/meson.build | 2 ++ app/src/main.c | 2 +- app/src/options.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ app/src/options.h | 51 +------------------------------------------ app/tests/test_cli.c | 8 +++---- 5 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 app/src/options.c diff --git a/app/meson.build b/app/meson.build index f82d37b3..2ced151b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -16,6 +16,7 @@ src = [ 'src/keyboard_inject.c', 'src/mouse_inject.c', 'src/opengl.c', + 'src/options.c', 'src/receiver.c', 'src/recorder.c', 'src/scrcpy.c', @@ -184,6 +185,7 @@ if get_option('buildtype') == 'debug' ['test_cli', [ 'tests/test_cli.c', 'src/cli.c', + 'src/options.c', 'src/util/str_util.c', ]], ['test_clock', [ diff --git a/app/src/main.c b/app/src/main.c index 51c13fd5..831b98fa 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -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, }; diff --git a/app/src/options.c b/app/src/options.c new file mode 100644 index 00000000..a1722158 --- /dev/null +++ b/app/src/options.c @@ -0,0 +1,52 @@ +#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, + .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, +}; diff --git a/app/src/options.h b/app/src/options.h index 04b8f6d2..23583f37 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -106,55 +106,6 @@ struct scrcpy_options { 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, \ -} +extern const struct scrcpy_options scrcpy_options_default; #endif diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 1682a72d..05bacbf8 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -8,7 +8,7 @@ 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, }; From 34eb10ea0b736ff9e66716bc70d5f8e87411a0ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Oct 2021 18:43:47 +0200 Subject: [PATCH 0040/1133] Define v4l2 option field only if HAVE_V4L2 --- app/src/options.c | 2 ++ app/src/options.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/options.c b/app/src/options.c index a1722158..82f25342 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -9,7 +9,9 @@ const struct scrcpy_options scrcpy_options_default = { .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, diff --git a/app/src/options.h b/app/src/options.h index 23583f37..434225b9 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -70,7 +70,9 @@ struct scrcpy_options { 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; From 06131ef634a1204da6c4f663cc5f9d71f2b456f1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 29 Oct 2021 12:21:34 +0200 Subject: [PATCH 0041/1133] Fix typo in clock comments --- app/src/clock.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/clock.h b/app/src/clock.h index eb7fa594..886d1f4d 100644 --- a/app/src/clock.h +++ b/app/src/clock.h @@ -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 compute their centroid ("average + * sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average * point"). The slope of the estimated affine function is that of the line * passing through these two points. * From 8bf28e9f5340d2a54dbc9b2c9d924b7ee4fa8d31 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 11:23:51 +0200 Subject: [PATCH 0042/1133] Upgrade Android SDK to 31 --- server/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/build.gradle b/server/build.gradle index 7cd7dbd7..ef936d1b 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId "com.genymobile.scrcpy" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 11900 versionName "1.19" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" From 0c9666b733ef16e1c250d59773120d2635318e5f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 11:23:51 +0200 Subject: [PATCH 0043/1133] Upgrade Android checkstyle to 9.0.1 Adapt checkstyle.xml to match the latest version, and remove a line break between imports which trigger a checkstyle volation. --- config/android-checkstyle.gradle | 2 +- config/checkstyle/checkstyle.xml | 35 +++++-------------- .../scrcpy/ControlMessageReaderTest.java | 1 - 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/config/android-checkstyle.gradle b/config/android-checkstyle.gradle index f998530e..29c67b19 100644 --- a/config/android-checkstyle.gradle +++ b/config/android-checkstyle.gradle @@ -2,7 +2,7 @@ apply plugin: 'checkstyle' check.dependsOn 'checkstyle' checkstyle { - toolVersion = '6.19' + toolVersion = '9.0.1' } task checkstyle(type: Checkstyle) { diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 812d060b..edda3919 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -37,6 +37,14 @@ page at http://checkstyle.sourceforge.net/config.html --> + + + + + + + + @@ -72,13 +80,6 @@ page at http://checkstyle.sourceforge.net/config.html --> - - - - - - - @@ -152,26 +153,6 @@ page at http://checkstyle.sourceforge.net/config.html --> - - - - - - - - - - - - - - - - - - - - diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index da568486..7f3d3f61 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -2,7 +2,6 @@ package com.genymobile.scrcpy; import android.view.KeyEvent; import android.view.MotionEvent; - import org.junit.Assert; import org.junit.Test; From db484d82dbb41f3aeca6a02e4b818ad54af03a47 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 11:32:48 +0200 Subject: [PATCH 0044/1133] Upgrade gradle build tools to 7.0.2 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c977c398..6ed7e4c6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:7.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b44297..29e41345 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 4c4381de4cf2677376d1727d6fa040937c2c5d03 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 15:20:39 +0200 Subject: [PATCH 0045/1133] Use sc_ prefix for size, position and point --- app/src/control_msg.c | 2 +- app/src/control_msg.h | 4 +- app/src/coords.h | 10 ++--- app/src/input_manager.c | 14 +++---- app/src/mouse_inject.c | 2 +- app/src/recorder.c | 2 +- app/src/recorder.h | 4 +- app/src/scrcpy.c | 2 +- app/src/screen.c | 82 ++++++++++++++++++++--------------------- app/src/screen.h | 12 +++--- app/src/server.c | 5 ++- app/src/server.h | 3 +- app/src/v4l2_sink.c | 2 +- app/src/v4l2_sink.h | 4 +- 14 files changed, 75 insertions(+), 73 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 1257010e..a6a020bd 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = { }; static void -write_position(uint8_t *buf, const struct position *position) { +write_position(uint8_t *buf, const struct sc_position *position) { buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[4], position->point.y); buffer_write16be(&buf[8], position->screen_size.width); diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 920a493a..16492849 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -57,11 +57,11 @@ struct control_msg { enum android_motionevent_action action; enum android_motionevent_buttons buttons; uint64_t pointer_id; - struct position position; + struct sc_position position; float pressure; } inject_touch_event; struct { - struct position position; + struct sc_position position; int32_t hscroll; int32_t vscroll; } inject_scroll_event; diff --git a/app/src/coords.h b/app/src/coords.h index 7be6836d..cdabb782 100644 --- a/app/src/coords.h +++ b/app/src/coords.h @@ -3,22 +3,22 @@ #include -struct size { +struct sc_size { uint16_t width; uint16_t height; }; -struct point { +struct sc_point { int32_t x; int32_t y; }; -struct position { +struct sc_position { // The video screen size may be different from the real device screen size, // so store to which size the absolute position apply, to scale it // accordingly. - struct size screen_size; - struct point point; + struct sc_size screen_size; + struct sc_point point; }; #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index bba3c998..b84f3bea 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -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 point point) { + struct sc_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 point -inverse_point(struct point point, struct size size) { +static struct sc_point +inverse_point(struct sc_point point, struct sc_size size) { point.x = size.width - point.x; point.y = size.height - point.y; return point; @@ -545,10 +545,10 @@ input_manager_process_mouse_motion(struct input_manager *im, im->mp->ops->process_mouse_motion(im->mp, event); if (im->vfinger_down) { - struct point mouse = + struct sc_point mouse = screen_convert_window_to_frame_coords(im->screen, event->x, event->y); - struct point vfinger = inverse_point(mouse, im->screen->frame_size); + struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } } @@ -630,10 +630,10 @@ input_manager_process_mouse_button(struct input_manager *im, #define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) if ((down && !im->vfinger_down && CTRL_PRESSED) || (!down && im->vfinger_down)) { - struct point mouse = + struct sc_point mouse = screen_convert_window_to_frame_coords(im->screen, event->x, event->y); - struct point vfinger = inverse_point(mouse, im->screen->frame_size); + struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); enum android_motionevent_action action = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 008da267..1d5fe230 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, int mouse_y; SDL_GetMouseState(&mouse_x, &mouse_y); - struct position position = { + struct sc_position position = { .screen_size = screen->frame_size, .point = screen_convert_window_to_frame_coords(screen, mouse_x, mouse_y), diff --git a/app/src/recorder.c b/app/src/recorder.c index 3b5fe070..a690e2de 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -372,7 +372,7 @@ bool recorder_init(struct recorder *recorder, const char *filename, enum sc_record_format format, - struct size declared_frame_size) { + struct sc_size declared_frame_size) { recorder->filename = strdup(filename); if (!recorder->filename) { LOGE("Could not strdup filename"); diff --git a/app/src/recorder.h b/app/src/recorder.h index f14523ff..27ea5526 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -25,7 +25,7 @@ struct recorder { char *filename; enum sc_record_format format; AVFormatContext *ctx; - struct size declared_frame_size; + struct sc_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 size declared_frame_size); + enum sc_record_format format, struct sc_size declared_frame_size); void recorder_destroy(struct recorder *recorder); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index bfea6642..ea71d4a1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -309,7 +309,7 @@ scrcpy(struct scrcpy_options *options) { } char device_name[DEVICE_NAME_FIELD_LENGTH]; - struct size frame_size; + struct sc_size frame_size; if (!server_connect_to(&s->server, device_name, &frame_size)) { goto end; diff --git a/app/src/screen.c b/app/src/screen.c index 7f442143..f82f5003 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -14,9 +14,9 @@ #define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink) -static inline struct size -get_rotated_size(struct size size, int rotation) { - struct size rotated_size; +static inline struct sc_size +get_rotated_size(struct sc_size size, int rotation) { + struct sc_size rotated_size; if (rotation & 1) { rotated_size.width = size.height; rotated_size.height = size.width; @@ -27,26 +27,26 @@ get_rotated_size(struct size size, int rotation) { return rotated_size; } -// get the window size in a struct size -static struct size +// get the window size in a struct sc_size +static struct sc_size get_window_size(const struct screen *screen) { int width; int height; SDL_GetWindowSize(screen->window, &width, &height); - struct size size; + struct sc_size size; size.width = width; size.height = height; return size; } -static struct point +static struct sc_point get_window_position(const struct screen *screen) { int x; int y; SDL_GetWindowPosition(screen->window, &x, &y); - struct point point; + struct sc_point point; point.x = x; point.y = y; return point; @@ -54,7 +54,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 size new_size) { +set_window_size(struct screen *screen, struct sc_size new_size) { assert(!screen->fullscreen); assert(!screen->maximized); SDL_SetWindowSize(screen->window, new_size.width, new_size.height); @@ -62,7 +62,7 @@ set_window_size(struct screen *screen, struct size new_size) { // get the preferred display bounds (i.e. the screen bounds with some margins) static bool -get_preferred_display_bounds(struct size *bounds) { +get_preferred_display_bounds(struct sc_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 +80,7 @@ get_preferred_display_bounds(struct size *bounds) { } static bool -is_optimal_size(struct size current_size, struct size content_size) { +is_optimal_size(struct sc_size current_size, struct sc_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 +94,16 @@ is_optimal_size(struct size current_size, struct 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 size -get_optimal_size(struct size current_size, struct size content_size) { +static struct sc_size +get_optimal_size(struct sc_size current_size, struct sc_size content_size) { if (content_size.width == 0 || content_size.height == 0) { // avoid division by 0 return current_size; } - struct size window_size; + struct sc_size window_size; - struct size display_size; + struct sc_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 +135,10 @@ get_optimal_size(struct size current_size, struct 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 size -get_initial_optimal_size(struct size content_size, uint16_t req_width, +static inline struct sc_size +get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, uint16_t req_height) { - struct size window_size; + struct sc_size window_size; if (!req_width && !req_height) { window_size = get_optimal_size(content_size, content_size); } else { @@ -166,9 +166,9 @@ screen_update_content_rect(struct screen *screen) { int dh; SDL_GL_GetDrawableSize(screen->window, &dw, &dh); - struct size content_size = screen->content_size; + struct sc_size content_size = screen->content_size; // The drawable size is the window size * the HiDPI scale - struct size drawable_size = {dw, dh}; + struct sc_size drawable_size = {dw, dh}; SDL_Rect *rect = &screen->rect; @@ -200,7 +200,7 @@ screen_update_content_rect(struct screen *screen) { static inline SDL_Texture * create_texture(struct screen *screen) { SDL_Renderer *renderer = screen->renderer; - struct size size = screen->frame_size; + struct sc_size size = screen->frame_size; SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, size.width, size.height); @@ -330,13 +330,13 @@ screen_init(struct screen *screen, const struct screen_params *params) { if (screen->rotation) { LOGI("Initial display rotation set to %u", screen->rotation); } - struct size content_size = + struct sc_size content_size = get_rotated_size(screen->frame_size, screen->rotation); screen->content_size = content_size; - struct size window_size = get_initial_optimal_size(content_size, - params->window_width, - params->window_height); + struct sc_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 | SDL_WINDOW_ALLOW_HIGHDPI; @@ -508,10 +508,10 @@ screen_destroy(struct screen *screen) { } static void -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 = { +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 = { .width = (uint32_t) window_size.width * new_content_size.width / old_content_size.width, .height = (uint32_t) window_size.height * new_content_size.height @@ -522,7 +522,7 @@ resize_for_content(struct screen *screen, struct size old_content_size, } static void -set_content_size(struct screen *screen, struct size new_content_size) { +set_content_size(struct screen *screen, struct sc_size new_content_size) { if (!screen->fullscreen && !screen->maximized) { resize_for_content(screen, screen->content_size, new_content_size); } else if (!screen->resize_pending) { @@ -553,7 +553,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { return; } - struct size new_content_size = + struct sc_size new_content_size = get_rotated_size(screen->frame_size, rotation); set_content_size(screen, new_content_size); @@ -566,7 +566,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 size new_frame_size) { +prepare_for_frame(struct screen *screen, struct sc_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 @@ -574,7 +574,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) { screen->frame_size = new_frame_size; - struct size new_content_size = + struct sc_size new_content_size = get_rotated_size(new_frame_size, screen->rotation); set_content_size(screen, new_content_size); @@ -615,7 +615,7 @@ screen_update_frame(struct screen *screen) { fps_counter_add_rendered_frame(&screen->fps_counter); - struct size new_frame_size = {frame->width, frame->height}; + struct sc_size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { return false; } @@ -682,10 +682,10 @@ screen_resize_to_fit(struct screen *screen) { return; } - struct point point = get_window_position(screen); - struct size window_size = get_window_size(screen); + struct sc_point point = get_window_position(screen); + struct sc_size window_size = get_window_size(screen); - struct size optimal_size = + struct sc_size optimal_size = get_optimal_size(window_size, screen->content_size); // Center the window related to the device screen @@ -711,7 +711,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) { screen->maximized = false; } - struct size content_size = screen->content_size; + struct sc_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); @@ -766,7 +766,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { return false; } -struct point +struct sc_point screen_convert_drawable_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { unsigned rotation = screen->rotation; @@ -780,7 +780,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen, y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; // rotate - struct point result; + struct sc_point result; switch (rotation) { case 0: result.x = x; @@ -803,7 +803,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen, return result; } -struct point +struct sc_point screen_convert_window_to_frame_coords(struct screen *screen, int32_t x, int32_t y) { screen_hidpi_scale_coords(screen, &x, &y); diff --git a/app/src/screen.h b/app/src/screen.h index 86aa1183..0804e50e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -27,13 +27,13 @@ struct screen { SDL_Renderer *renderer; SDL_Texture *texture; struct sc_opengl gl; - struct size frame_size; - struct size content_size; // rotated frame_size + struct sc_size frame_size; + struct sc_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 size windowed_content_size; + struct sc_size windowed_content_size; // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) unsigned rotation; @@ -49,7 +49,7 @@ struct screen { struct screen_params { const char *window_title; - struct size frame_size; + struct sc_size frame_size; bool always_on_top; int16_t window_x; @@ -120,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 point +struct sc_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 point +struct sc_point screen_convert_drawable_to_frame_coords(struct screen *screen, int32_t x, int32_t y); diff --git a/app/src/server.c b/app/src/server.c index 76d2910b..50d7f836 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -427,7 +427,7 @@ error: static bool device_read_info(sc_socket device_socket, char *device_name, - struct size *size) { + struct sc_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) { @@ -447,7 +447,8 @@ device_read_info(sc_socket device_socket, char *device_name, } bool -server_connect_to(struct server *server, char *device_name, struct size *size) { +server_connect_to(struct server *server, char *device_name, + struct sc_size *size) { if (!server->tunnel_forward) { server->video_socket = net_accept(server->server_socket); if (server->video_socket == SC_INVALID_SOCKET) { diff --git a/app/src/server.h b/app/src/server.h index 75594522..b291c0a5 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -62,7 +62,8 @@ server_start(struct server *server, const struct server_params *params); // block until the communication with the server is established // device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes bool -server_connect_to(struct server *server, char *device_name, struct size *size); +server_connect_to(struct server *server, char *device_name, + struct sc_size *size); // disconnect and kill the server process void diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 95e20541..48d90364 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -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 size frame_size, sc_tick buffering_time) { + struct sc_size frame_size, sc_tick buffering_time) { vs->device_name = strdup(device_name); if (!vs->device_name) { LOGE("Could not strdup v4l2 device name"); diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index 6773cd26..8737a607 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -18,7 +18,7 @@ struct sc_v4l2_sink { AVCodecContext *encoder_ctx; char *device_name; - struct size frame_size; + struct sc_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 size frame_size, sc_tick buffering_time); + struct sc_size frame_size, sc_tick buffering_time); void sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); From dae091e3ab58bcc592b166a55998addef23be01a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 20:21:00 +0200 Subject: [PATCH 0046/1133] Handle SDL_PushEvent() errors Pushing an event to the main thread may fail. If this happens, log an error, and try to recover when possible. --- app/src/scrcpy.c | 11 +++++++++-- app/src/screen.c | 21 +++++++++++++++++++-- app/src/screen.h | 2 ++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ea71d4a1..d2e35bb2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -62,7 +62,10 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { if (ctrl_type == CTRL_C_EVENT) { SDL_Event event; event.type = SDL_QUIT; - SDL_PushEvent(&event); + int ret = SDL_PushEvent(&event); + if (ret < 0) { + LOGW("Could not post SDL_QUIT event: %s", SDL_GetError()); + } return TRUE; } return FALSE; @@ -250,7 +253,11 @@ stream_on_eos(struct stream *stream, void *userdata) { SDL_Event stop_event; stop_event.type = EVENT_STREAM_STOPPED; - SDL_PushEvent(&stop_event); + int ret = SDL_PushEvent(&stop_event); + if (ret < 0) { + LOGE("Could not post stream stopped event: %s", SDL_GetError()); + // XXX What could we do? + } } bool diff --git a/app/src/screen.c b/app/src/screen.c index f82f5003..e2d15180 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -282,17 +282,33 @@ 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 + // this new frame instead, unless the previous event failed + need_new_event = screen->event_failed; } 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 - SDL_PushEvent(&new_frame_event); + 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; + } } } @@ -302,6 +318,7 @@ 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, diff --git a/app/src/screen.h b/app/src/screen.h index 0804e50e..51946dbb 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -44,6 +44,8 @@ struct screen { bool maximized; bool mipmaps; + bool event_failed; // in case SDL_PushEvent() returned an error + AVFrame *frame; }; From d2d18466d4bac6cb2d2d9f40ee07519e3342f204 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 22:36:56 +0200 Subject: [PATCH 0047/1133] Factorize SDL event push Add a macro and inline function to call SDL_PushEvent() and log on error (without actually handling the error, because there is nothing we could do). --- app/src/scrcpy.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d2e35bb2..05f8cba2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -57,15 +57,22 @@ struct scrcpy { 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) { - SDL_Event event; - event.type = SDL_QUIT; - int ret = SDL_PushEvent(&event); - if (ret < 0) { - LOGW("Could not post SDL_QUIT event: %s", SDL_GetError()); - } + PUSH_EVENT(SDL_QUIT); return TRUE; } return FALSE; @@ -251,13 +258,7 @@ stream_on_eos(struct stream *stream, void *userdata) { (void) stream; (void) userdata; - SDL_Event stop_event; - stop_event.type = EVENT_STREAM_STOPPED; - int ret = SDL_PushEvent(&stop_event); - if (ret < 0) { - LOGE("Could not post stream stopped event: %s", SDL_GetError()); - // XXX What could we do? - } + PUSH_EVENT(EVENT_STREAM_STOPPED); } bool From a57c7d3a2b6949635994fc0ba344ec4817624d3c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 10:35:03 +0100 Subject: [PATCH 0048/1133] Extract SDL hints Set all SDL hints in a separate function. --- app/src/scrcpy.c | 52 +++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 05f8cba2..707051d5 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -79,29 +79,8 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { } #endif // _WIN32 -// init SDL and set appropriate hints -static bool -sdl_init_and_configure(bool display, const char *render_driver, - bool disable_screensaver) { - uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; - if (SDL_Init(flags)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); - return false; - } - - atexit(SDL_Quit); - -#ifdef _WIN32 - // Clean up properly on Ctrl+C on Windows - bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); - if (!ok) { - LOGW("Could not set Ctrl+C handler"); - } -#endif // _WIN32 - - if (!display) { - return true; - } +static void +sdl_set_hints(const char *render_driver) { if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) { LOGW("Could not set render driver"); @@ -130,6 +109,33 @@ sdl_init_and_configure(bool display, const char *render_driver, if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) { LOGW("Could not disable minimize on focus loss"); } +} + +// init SDL and set appropriate hints +static bool +sdl_init_and_configure(bool display, const char *render_driver, + bool disable_screensaver) { + uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; + if (SDL_Init(flags)) { + LOGC("Could not initialize SDL: %s", SDL_GetError()); + return false; + } + + atexit(SDL_Quit); + +#ifdef _WIN32 + // Clean up properly on Ctrl+C on Windows + bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); + if (!ok) { + LOGW("Could not set Ctrl+C handler"); + } +#endif // _WIN32 + + if (!display) { + return true; + } + + sdl_set_hints(render_driver); if (disable_screensaver) { LOGD("Screensaver disabled"); From ac539e13123e0638fab29f26b29b626d5d11d13f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 10:35:58 +0100 Subject: [PATCH 0049/1133] Set SDL hints before initialization In theory, some SDL hints should be initialized before calling SDL_Init(). --- app/src/scrcpy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 707051d5..0c2dc5b3 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -115,6 +115,10 @@ sdl_set_hints(const char *render_driver) { static bool sdl_init_and_configure(bool display, const char *render_driver, bool disable_screensaver) { + if (display) { + sdl_set_hints(render_driver); + } + uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; if (SDL_Init(flags)) { LOGC("Could not initialize SDL: %s", SDL_GetError()); @@ -135,8 +139,6 @@ sdl_init_and_configure(bool display, const char *render_driver, return true; } - sdl_set_hints(render_driver); - if (disable_screensaver) { LOGD("Screensaver disabled"); SDL_DisableScreenSaver(); From 688477ff6521af87f925da80d9519b334bbb6736 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 12:03:35 +0100 Subject: [PATCH 0050/1133] Move SDL initialization Inline SDL initialization in scrcpy(). This will allow to split minimal initialization and video initialization. --- app/src/scrcpy.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0c2dc5b3..042cf43c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -111,22 +111,8 @@ sdl_set_hints(const char *render_driver) { } } -// init SDL and set appropriate hints -static bool -sdl_init_and_configure(bool display, const char *render_driver, - bool disable_screensaver) { - if (display) { - sdl_set_hints(render_driver); - } - - uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; - if (SDL_Init(flags)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); - return false; - } - - atexit(SDL_Quit); - +static void +sdl_configure(bool display, bool disable_screensaver) { #ifdef _WIN32 // Clean up properly on Ctrl+C on Windows bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); @@ -136,7 +122,7 @@ sdl_init_and_configure(bool display, const char *render_driver, #endif // _WIN32 if (!display) { - return true; + return; } if (disable_screensaver) { @@ -146,8 +132,6 @@ sdl_init_and_configure(bool display, const char *render_driver, LOGD("Screensaver enabled"); SDL_EnableScreenSaver(); } - - return true; } static bool @@ -319,11 +303,20 @@ scrcpy(struct scrcpy_options *options) { server_started = true; - if (!sdl_init_and_configure(options->display, options->render_driver, - options->disable_screensaver)) { + if (options->display) { + sdl_set_hints(options->render_driver); + } + + uint32_t flags = options->display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; + if (SDL_Init(flags)) { + LOGC("Could not initialize SDL: %s", SDL_GetError()); goto end; } + atexit(SDL_Quit); + + sdl_configure(options->display, options->disable_screensaver); + char device_name[DEVICE_NAME_FIELD_LENGTH]; struct sc_size frame_size; From caf594c90ef1b71ed844b2a9b42c3b3371215d6f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 12:11:34 +0100 Subject: [PATCH 0051/1133] Split SDL initialization Initialize SDL_INIT_EVENTS first (very quick) and SDL_INIT_VIDEO after the server (quite long). --- app/src/scrcpy.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 042cf43c..2235f727 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -258,6 +258,14 @@ scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; struct scrcpy *s = &scrcpy; + // Minimal SDL initialization + if (SDL_Init(SDL_INIT_EVENTS)) { + LOGC("Could not initialize SDL: %s", SDL_GetError()); + return false; + } + + atexit(SDL_Quit); + if (!server_init(&s->server)) { return false; } @@ -307,14 +315,12 @@ scrcpy(struct scrcpy_options *options) { sdl_set_hints(options->render_driver); } - uint32_t flags = options->display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; - if (SDL_Init(flags)) { + // Initialize SDL video in addition if display is enabled + if (options->display && SDL_Init(SDL_INIT_VIDEO)) { LOGC("Could not initialize SDL: %s", SDL_GetError()); goto end; } - atexit(SDL_Quit); - sdl_configure(options->display, options->disable_screensaver); char device_name[DEVICE_NAME_FIELD_LENGTH]; From 13c4aa1a3bbcedb212c4b6ddf3b3855d02243ea0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 12:40:51 +0100 Subject: [PATCH 0052/1133] Disable synthetic mouse events from touch events Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is better not to generate them in the first place. --- app/src/compat.h | 5 +++++ app/src/scrcpy.c | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 8e2d18f4..9f66ce95 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -43,6 +43,11 @@ # define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP #endif +#if SDL_VERSION_ATLEAST(2, 0, 6) +// +# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS +#endif + #if SDL_VERSION_ATLEAST(2, 0, 8) // # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2235f727..2efc043a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -98,6 +98,15 @@ sdl_set_hints(const char *render_driver) { } #endif +#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS + // Disable synthetic mouse events from touch events + // Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is + // better not to generate them in the first place. + if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) { + LOGW("Could not disable synthetic mouse events"); + } +#endif + #ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR // Disable compositor bypassing on X11 if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) { From 58ea238fb2c5ddf69e1b2d598ccf58b6a9fe8270 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 12:20:45 +0100 Subject: [PATCH 0053/1133] Remove unnecessary variable Test directly if a record filename is provided, without an intermediate boolean variable. --- app/src/scrcpy.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2efc043a..c7033a26 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -295,7 +295,6 @@ scrcpy(struct scrcpy_options *options) { bool controller_started = false; bool screen_initialized = false; - bool record = !!options->record_filename; struct server_params params = { .serial = options->serial, .log_level = options->log_level, @@ -358,7 +357,7 @@ scrcpy(struct scrcpy_options *options) { } struct recorder *rec = NULL; - if (record) { + if (options->record_filename) { if (!recorder_init(&s->recorder, options->record_filename, options->record_format, From 790f04e91faa14c5ebd2b987530d1e3db080e682 Mon Sep 17 00:00:00 2001 From: Kaleidot725 Date: Sat, 30 Oct 2021 18:29:52 +0900 Subject: [PATCH 0054/1133] Update README.jp.md to v1.19 PR #2740 Signed-off-by: Romain Vimont --- README.jp.md | 130 ++++++++++++++++++++++++++++++++++++++++----------- README.md | 2 +- 2 files changed, 103 insertions(+), 29 deletions(-) diff --git a/README.jp.md b/README.jp.md index e42c528e..a97ef765 100644 --- a/README.jp.md +++ b/README.jp.md @@ -1,6 +1,6 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ -# scrcpy (v1.17) +# scrcpy (v1.19) このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux_ 、 _Windows_ そして _macOS_ 上で動作します。 @@ -103,18 +103,21 @@ scoop install adb # まだ入手していない場合 brew install scrcpy ``` -`PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合: +`PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。 ```bash -# Homebrew >= 2.6.0 -brew install --cask android-platform-tools +brew install android-platform-tools +``` + +`adb`は[MacPorts]からでもインストールできます。 -# Homebrew < 2.6.0 -brew cask install android-platform-tools +```bash +sudo port install scrcpy ``` -また、[アプリケーションをビルド][BUILD]することも可能です。 +[MacPorts]: https://www.macports.org/ +また、[アプリケーションをビルド][BUILD]することも可能です。 ## 実行 @@ -184,10 +187,11 @@ scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440 ミラーリングの向きをロックするには: ```bash -scrcpy --lock-video-orientation 0 # 自然な向き -scrcpy --lock-video-orientation 1 # 90°反時計回り -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 90°時計回り +scrcpy --lock-video-orientation # 現在の向き +scrcpy --lock-video-orientation=0 # 自然な向き +scrcpy --lock-video-orientation=1 # 90°反時計回り +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 90°時計回り ``` この設定は録画の向きに影響します。 @@ -210,7 +214,9 @@ scrcpy --encoder OMX.qcom.video.encoder.avc scrcpy --encoder _ ``` -### 録画 +### キャプチャ + +#### 録画 ミラーリング中に画面の録画をすることが可能です: @@ -233,6 +239,77 @@ scrcpy -Nr file.mkv [パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation +#### v4l2loopback + +Linuxでは、ビデオストリームをv4l2ループバックデバイスに送信することができます。 +v4l2loopbackのデバイスにビデオストリームを送信することで、Androidデバイスをウェブカメラのようにv4l2対応ツールで開くこともできます。 + +`v4l2loopback` モジュールのインストールが必要です。 + +```bash +sudo apt install v4l2loopback-dkms +``` + +v4l2デバイスを作成する。 + +```bash +sudo modprobe v4l2loopback +``` + +これにより、新しいビデオデバイスが `/dev/videoN` に作成されます。(`N` は整数) +(複数のデバイスや特定のIDのデバイスを作成するために、より多くの[オプション](https://github.com/umlaeute/v4l2loopback#options)が利用可能です。 +多くの[オプション]()が利用可能で複数のデバイスや特定のIDのデバイスを作成できます。 + + +有効なデバイスを一覧表示する: + +```bash +# v4l-utilsパッケージが必要 +v4l2-ctl --list-devices + +# シンプルですが十分これで確認できます +ls /dev/video* +``` + +v4l2シンクを使用してscrcpyを起動する。 + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # ミラーリングウィンドウを無効化する +scrcpy --v4l2-sink=/dev/videoN -N # 短縮版 +``` + +(`N` をデバイス ID に置き換えて、`ls /dev/video*` で確認してください) +有効にすると、v4l2対応のツールでビデオストリームを開けます。 + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLCではバッファリングの遅延が発生する場合があります +``` + +例えばですが [OBS]の中にこの映像を取り込めことができます。 + +[OBS]: https://obsproject.com/ + + +#### Buffering + +バッファリングを追加することも可能です。これによりレイテンシーは増加しますが、ジッターは減少します。(参照 +[#2464]) + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +このオプションでディスプレイバッファリングを設定できます。 + +```bash +scrcpy --display-buffer=50 # ディスプレイに50msのバッファリングを追加する +``` + +V4L2の場合はこちらのオプションで設定できます。 + +```bash +scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink +``` ### 接続 @@ -457,16 +534,6 @@ scrcpy -Sw ``` -#### 期限切れフレームをレンダリングする - -初期状態では、待ち時間を最小限にするために、_scrcpy_ は最後にデコードされたフレームをレンダリングし、前のフレームを削除します。 - -全フレームのレンダリングを強制するには(待ち時間が長くなる可能性があります): - -```bash -scrcpy --render-expired-frames -``` - #### タッチを表示 プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。 @@ -586,14 +653,14 @@ APKをインストールするには、(`.apk`で終わる)APKファイルを _s #### デバイスにファイルを送る -デバイスの`/sdcard/`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 +デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 見た目のフィードバックはありません。コンソールにログが出力されます。 転送先ディレクトリを起動時に変更することができます: ```bash -scrcpy --push-target /sdcard/foo/bar/ +scrcpy --push-target=/sdcard/Movies/ ``` @@ -634,7 +701,7 @@ _[Super]は通常WindowsもしくはCmdキー | ウィンドウサイズを変更して黒い境界線を削除 | MOD+w \| _ダブルクリック¹_ | `HOME`をクリック | MOD+h \| _真ん中クリック_ | `BACK`をクリック | MOD+b \| _右クリック²_ - | `APP_SWITCH`をクリック | MOD+s + | `APP_SWITCH`をクリック | MOD+s \| _4クリック³_ | `MENU` (画面のアンロック)をクリック | MOD+m | `VOLUME_UP`をクリック | MOD+ _(上)_ | `VOLUME_DOWN`をクリック | MOD+ _(下)_ @@ -643,7 +710,8 @@ _[Super]は通常WindowsもしくはCmdキー | デバイス画面をオフにする(ミラーリングしたまま) | MOD+o | デバイス画面をオンにする | MOD+Shift+o | デバイス画面を回転する | MOD+r - | 通知パネルを展開する | MOD+n + | 通知パネルを展開する | MOD+n \| _5ボタンクリック³_ + | 設定パネルを展開する | MOD+n+n \| _5ダブルクリック³_ | 通知パネルを折りたたむ | MOD+Shift+n | クリップボードへのコピー³ | MOD+c | クリップボードへのカット³ | MOD+x @@ -654,10 +722,16 @@ _[Super]は通常WindowsもしくはCmdキー _¹黒い境界線を削除するため、境界線上でダブルクリック_ _²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._ -_³Android 7以上のみ._ +_³4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._ +_⁴Android 7以上のみ._ -全てのCtrl+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 +キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。 + + 1. MOD キーを押し、押したままにする. + 2. その後に nキーを2回押す. + 3. 最後に MODキーを離す. +全てのCtrl+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 ## カスタムパス diff --git a/README.md b/README.md index 81410b80..bd91be07 100644 --- a/README.md +++ b/README.md @@ -889,7 +889,7 @@ This README is available in other languages: - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Italiano (Italiano, `it`) - v1.19](README.it.md) -- [日本語 (Japanese, `jp`) - v1.17](README.jp.md) +- [日本語 (Japanese, `jp`) - v1.19](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md) From 676fa73d2c19d4828921640588e42d1911ad2147 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 19:07:35 +0200 Subject: [PATCH 0055/1133] Wrap device name and size in a struct As a benefit, this avoids to take care of the device name length on the caller side. --- app/src/scrcpy.c | 15 +++++++-------- app/src/server.c | 21 +++++++++------------ app/src/server.h | 11 +++++++---- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c7033a26..c8adb5a1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -331,10 +331,9 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); - char device_name[DEVICE_NAME_FIELD_LENGTH]; - struct sc_size frame_size; + struct server_info info; - if (!server_connect_to(&s->server, device_name, &frame_size)) { + if (!server_connect_to(&s->server, &info)) { goto end; } @@ -361,7 +360,7 @@ scrcpy(struct scrcpy_options *options) { if (!recorder_init(&s->recorder, options->record_filename, options->record_format, - frame_size)) { + info.frame_size)) { goto end; } rec = &s->recorder; @@ -407,11 +406,11 @@ scrcpy(struct scrcpy_options *options) { if (options->display) { const char *window_title = - options->window_title ? options->window_title : device_name; + options->window_title ? options->window_title : info.device_name; struct screen_params screen_params = { .window_title = window_title, - .frame_size = frame_size, + .frame_size = info.frame_size, .always_on_top = options->always_on_top, .window_x = options->window_x, .window_y = options->window_y, @@ -434,8 +433,8 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_V4L2 if (options->v4l2_device) { - if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size, - options->v4l2_buffer)) { + if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, + info.frame_size, options->v4l2_buffer)) { goto end; } diff --git a/app/src/server.c b/app/src/server.c index 50d7f836..4a4d3ec4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -426,8 +426,7 @@ error: } static bool -device_read_info(sc_socket device_socket, char *device_name, - struct sc_size *size) { +device_read_info(sc_socket device_socket, struct server_info *info) { 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) { @@ -436,19 +435,17 @@ device_read_info(sc_socket device_socket, char *device_name, } // in case the client sends garbage buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; - // strcpy is safe here, since name contains at least - // DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH - strcpy(device_name, (char *) buf); - size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) - | buf[DEVICE_NAME_FIELD_LENGTH + 1]; - size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) - | buf[DEVICE_NAME_FIELD_LENGTH + 3]; + memcpy(info->device_name, (char *) buf, sizeof(info->device_name)); + + info->frame_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) + | buf[DEVICE_NAME_FIELD_LENGTH + 3]; return true; } bool -server_connect_to(struct server *server, char *device_name, - struct sc_size *size) { +server_connect_to(struct server *server, struct server_info *info) { if (!server->tunnel_forward) { server->video_socket = net_accept(server->server_socket); if (server->video_socket == SC_INVALID_SOCKET) { @@ -489,7 +486,7 @@ server_connect_to(struct server *server, char *device_name, server->tunnel_enabled = false; // The sockets will be closed on stop if device_read_info() fails - return device_read_info(server->video_socket, device_name, size); + return device_read_info(server->video_socket, info); } void diff --git a/app/src/server.h b/app/src/server.h index b291c0a5..242b0525 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -14,6 +14,12 @@ #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; @@ -58,12 +64,9 @@ server_init(struct server *server); bool 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, char *device_name, - struct sc_size *size); +server_connect_to(struct server *server, struct server_info *info); // disconnect and kill the server process void From 48fcfa96ab659a830ca4bec6fa4d70ddacf855d5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Nov 2021 19:26:29 +0100 Subject: [PATCH 0056/1133] Add missing include "common.h" --- app/src/util/tick.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/util/tick.h b/app/src/util/tick.h index 472a18a7..47d02529 100644 --- a/app/src/util/tick.h +++ b/app/src/util/tick.h @@ -1,6 +1,8 @@ #ifndef SC_TICK_H #define SC_TICK_H +#include "common.h" + #include typedef int64_t sc_tick; From f65c3fde69e216ecc3912601006cd4e19853269f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 18:07:17 +0100 Subject: [PATCH 0057/1133] Fix typos in help --- app/src/cli.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index cab25772..02b89aa0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -66,12 +66,12 @@ scrcpy_print_usage(const char *arg0) { "\n" " --force-adb-forward\n" " Do not attempt to use \"adb reverse\" to connect to the\n" - " the device.\n" + " device.\n" "\n" " --forward-all-clicks\n" " By default, right-click triggers BACK (or POWER on) and\n" " middle-click triggers HOME. This option disables these\n" - " shortcuts and forward the clicks to the device instead.\n" + " shortcuts and forwards the clicks to the device instead.\n" "\n" " -f, --fullscreen\n" " Start in fullscreen.\n" @@ -142,7 +142,7 @@ scrcpy_print_usage(const char *arg0) { "\n" " --push-target path\n" " Set the target directory for pushing files to the device by\n" - " drag & drop. It is passed as-is to \"adb push\".\n" + " drag & drop. It is passed as is to \"adb push\".\n" " Default is \"/sdcard/Download/\".\n" "\n" " -r, --record file.mp4\n" @@ -162,14 +162,14 @@ scrcpy_print_usage(const char *arg0) { "\n" " --rotation value\n" " Set the initial display rotation.\n" - " Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n" + " Possible values are 0, 1, 2 and 3. Each increment adds a 90\n" " degrees rotation counterclockwise.\n" "\n" " -s, --serial serial\n" " The device serial number. Mandatory only if several devices\n" " are connected to adb.\n" "\n" - " --shortcut-mod key[+...]][,...]\n" + " --shortcut-mod key[+...][,...]\n" " Specify the modifiers to use for scrcpy shortcuts.\n" " Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n" " \"lsuper\" and \"rsuper\".\n" From fc64445555ecb29e145f45dd152f33f71d676dd8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 18:46:52 +0100 Subject: [PATCH 0058/1133] Remove extra space in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdd74087..28d492f8 100644 --- a/README.md +++ b/README.md @@ -830,7 +830,7 @@ _[Super] is typically the Windows or Cmd key._ | Turn device screen on | MOD+Shift+o | Rotate device screen | MOD+r | Expand notification panel | MOD+n \| _5th-click³_ - | Expand settings panel | MOD+n+n \| _Double-5th-click³_ + | Expand settings panel | MOD+n+n \| _Double-5th-click³_ | Collapse panels | MOD+Shift+n | Copy to clipboard⁴ | MOD+c | Cut to clipboard⁴ | MOD+x From d72c7076f750f593abee4d041e3a3bd8d5a89bc6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 18:48:27 +0100 Subject: [PATCH 0059/1133] Mention drag & drop APK in README For consistency with --help and manpage. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 28d492f8..70080105 100644 --- a/README.md +++ b/README.md @@ -838,6 +838,7 @@ _[Super] is typically the Windows or Cmd key._ | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom | Ctrl+_click-and-move_ + | Drag & drop APK file | Install APK from computer _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ From f489f7fcadac5325cdda7eac52e32c77ac207678 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 18:44:07 +0100 Subject: [PATCH 0060/1133] Mention drag & drop for non-APK files in help --- README.md | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 3 +++ 3 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 70080105..f86fff9f 100644 --- a/README.md +++ b/README.md @@ -839,6 +839,7 @@ _[Super] is typically the Windows or Cmd key._ | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom | Ctrl+_click-and-move_ | Drag & drop APK file | Install APK from computer + | Drag & drop non-APK file | [Push file to device](#push-file-to-device) _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 46db6e1d..9ad405c5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -364,6 +364,10 @@ Pinch-to-zoom from the center of the screen .B Drag & drop APK file Install APK from computer +.TP +.B Drag & drop non-APK file +Push file to device (see \fB\-\-push\-target\fR) + .SH Environment variables diff --git a/app/src/cli.c b/app/src/cli.c index 02b89aa0..ea7cb1a0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -328,6 +328,9 @@ scrcpy_print_usage(const char *arg0) { "\n" " Drag & drop APK file\n" " Install APK from computer\n" + "\n" + " Drag & drop non-APK file\n" + " Push file to device (see --push-target)\n" "\n", arg0); } From 30d40f4e786beaaeabde1f3c324b7680ef433be6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 19:27:53 +0100 Subject: [PATCH 0061/1133] Document --power-off-on-close The option was not documented. Refs #824 --- README.md | 8 ++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 3 +++ 3 files changed, 15 insertions(+) diff --git a/README.md b/README.md index f86fff9f..a0ab271c 100644 --- a/README.md +++ b/README.md @@ -582,6 +582,14 @@ scrcpy --turn-screen-off --stay-awake scrcpy -Sw ``` +#### Power off on close + +To turn the device screen off when closing scrcpy: + +```bash +scrcpy --power-off-on-close +``` + #### Show touches diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 9ad405c5..399fd172 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -136,6 +136,10 @@ Set the TCP port (range) used by the client to listen. Default is 27183:27199. +.TP +.B \-\-power\-off\-on\-close +Turn the device screen off when closing scrcpy. + .TP .B \-\-prefer\-text Inject alpha characters and space as text events instead of key events. diff --git a/app/src/cli.c b/app/src/cli.c index ea7cb1a0..a0bf451d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -133,6 +133,9 @@ scrcpy_print_usage(const char *arg0) { " Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n" "\n" + " --power-off-on-close\n" + " Turn the device screen off when closing scrcpy.\n" + "\n" " --prefer-text\n" " Inject alpha characters and space as text events instead of\n" " key events.\n" From b62df7ee91f30e0ca5c7e7787f649b51f9384156 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 20:54:45 +0100 Subject: [PATCH 0062/1133] Remove deprecated -c option The short option -c is deprecated since v1.11. Only the long version (--crop) remains. --- app/src/cli.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index a0bf451d..dbf6940b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -799,7 +799,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hKm:nNp:r:s:StTvV:w", + while ((c = getopt_long(argc, argv, "b:fF:hKm:nNp:r:s:StTvV:w", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -807,9 +807,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return false; } break; - case 'c': - LOGW("Deprecated option -c. Use --crop instead."); - // fall through case OPT_CROP: opts->crop = optarg; break; From 570a003c39edc6e289695b0e97c064dcb8ba0b34 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 21:35:14 +0100 Subject: [PATCH 0063/1133] Remove deprecated -T option The short option -T is deprecated since v1.11. Only the long version (--always-on-top) remains. --- app/src/cli.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index dbf6940b..45b87fb2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -799,7 +799,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:fF:hKm:nNp:r:s:StTvV:w", + while ((c = getopt_long(argc, argv, "b:fF:hKm:nNp:r:s:StvV:w", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -871,9 +871,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 't': opts->show_touches = true; break; - case 'T': - LOGW("Deprecated option -T. Use --always-on-top instead."); - // fall through case OPT_ALWAYS_ON_TOP: opts->always_on_top = true; break; From 6dba1922c1fa00765ca6cdec264c53939364cda7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Nov 2021 19:51:47 +0100 Subject: [PATCH 0064/1133] Add string buffer util This will help to build strings incrementally. --- app/meson.build | 5 +++ app/src/util/strbuf.c | 87 +++++++++++++++++++++++++++++++++++++++++ app/src/util/strbuf.h | 73 ++++++++++++++++++++++++++++++++++ app/tests/test_strbuf.c | 47 ++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 app/src/util/strbuf.c create mode 100644 app/src/util/strbuf.h create mode 100644 app/tests/test_strbuf.c diff --git a/app/meson.build b/app/meson.build index 2ced151b..f08d4cab 100644 --- a/app/meson.build +++ b/app/meson.build @@ -27,6 +27,7 @@ src = [ 'src/util/log.c', 'src/util/net.c', 'src/util/process.c', + 'src/util/strbuf.c', 'src/util/str_util.c', 'src/util/thread.c', 'src/util/tick.c', @@ -204,6 +205,10 @@ if get_option('buildtype') == 'debug' ['test_queue', [ 'tests/test_queue.c', ]], + ['test_strbuf', [ + 'tests/test_strbuf.c', + 'src/util/strbuf.c', + ]], ['test_strutil', [ 'tests/test_strutil.c', 'src/util/str_util.c', diff --git a/app/src/util/strbuf.c b/app/src/util/strbuf.c new file mode 100644 index 00000000..b2b6f494 --- /dev/null +++ b/app/src/util/strbuf.c @@ -0,0 +1,87 @@ +#include "strbuf.h" + +#include +#include +#include + +#include + +bool +sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) { + buf->s = malloc(init_cap + 1); // +1 for '\0' + if (!buf->s) { + return false; + } + + buf->len = 0; + buf->cap = init_cap; + return true; +} + +static bool +sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) { + if (buf->len + len > buf->cap) { + size_t new_cap = buf->cap * 3 / 2 + len; + char *s = realloc(buf->s, new_cap + 1); // +1 for '\0' + if (!s) { + // Leave the old buf->s + return false; + } + buf->s = s; + buf->cap = new_cap; + } + return true; +} + +bool +sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) { + assert(s); + assert(*s); + assert(strlen(s) >= len); + if (!sc_strbuf_reserve(buf, len)) { + return false; + } + + memcpy(&buf->s[buf->len], s, len); + buf->len += len; + buf->s[buf->len] = '\0'; + + return true; +} + +bool +sc_strbuf_append_char(struct sc_strbuf *buf, const char c) { + if (!sc_strbuf_reserve(buf, 1)) { + return false; + } + + buf->s[buf->len] = c; + buf->len ++; + buf->s[buf->len] = '\0'; + + return true; +} + +bool +sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) { + if (!sc_strbuf_reserve(buf, n)) { + return false; + } + + memset(&buf->s[buf->len], c, n); + buf->len += n; + buf->s[buf->len] = '\0'; + + return true; +} + +void +sc_strbuf_shrink(struct sc_strbuf *buf) { + assert(buf->len <= buf->cap); + if (buf->len != buf->cap) { + char *s = realloc(buf->s, buf->len + 1); // +1 for '\0' + assert(s); // decreasing the size may not fail + buf->s = s; + buf->cap = buf->len; + } +} diff --git a/app/src/util/strbuf.h b/app/src/util/strbuf.h new file mode 100644 index 00000000..1878df2f --- /dev/null +++ b/app/src/util/strbuf.h @@ -0,0 +1,73 @@ +#ifndef SC_STRBUF_H +#define SC_STRBUF_H + +#include "common.h" + +#include +#include +#include + +struct sc_strbuf { + char *s; + size_t len; + size_t cap; +}; + +/** + * Initialize the string buffer + * + * `buf->s` must be manually freed by the caller. + */ +bool +sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap); + +/** + * Append a string + * + * Append `len` characters from `s` to the buffer. + */ +bool +sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len); + +/** + * Append a char + * + * Append a single character to the buffer. + */ +bool +sc_strbuf_append_char(struct sc_strbuf *buf, const char c); + +/** + * Append a char `n` times + * + * Append the same characters `n` times to the buffer. + */ +bool +sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n); + +/** + * Append a NUL-terminated string + */ +static inline bool +sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) { + return sc_strbuf_append(buf, s, strlen(s)); +} + +/** + * Append a static string + * + * Append a string whose size is known at compile time (for + * example a string literal). + */ +#define sc_strbuf_append_staticstr(BUF, S) \ + sc_strbuf_append(BUF, S, sizeof(S) - 1) + +/** + * Shrink the buffer capacity to its current length + * + * This resizes `buf->s` to fit the content. + */ +void +sc_strbuf_shrink(struct sc_strbuf *buf); + +#endif diff --git a/app/tests/test_strbuf.c b/app/tests/test_strbuf.c new file mode 100644 index 00000000..97417677 --- /dev/null +++ b/app/tests/test_strbuf.c @@ -0,0 +1,47 @@ +#include "common.h" + +#include +#include +#include + +#include "util/strbuf.h" + +static void test_strbuf_simple(void) { + struct sc_strbuf buf; + bool ok = sc_strbuf_init(&buf, 10); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "Hello"); + assert(ok); + + ok = sc_strbuf_append_char(&buf, ' '); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "world"); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "!\n"); + assert(ok); + + ok = sc_strbuf_append_staticstr(&buf, "This is a test"); + assert(ok); + + ok = sc_strbuf_append_n(&buf, '.', 3); + assert(ok); + + assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); + + sc_strbuf_shrink(&buf); + assert(buf.len == buf.cap); + assert(!strcmp(buf.s, "Hello world!\nThis is a test...")); + + free(buf.s); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_strbuf_simple(); + return 0; +} From 9ec3406568a444fab5b2416944479fa6248ef409 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Nov 2021 20:26:35 +0100 Subject: [PATCH 0065/1133] Add line wrapper Add a tool to wrap lines at words boundaries (spaces) to fit in a specified number of columns, left-indented by a specified number of spaces. --- app/meson.build | 3 ++ app/src/util/str_util.c | 80 ++++++++++++++++++++++++++++++++++++++++ app/src/util/str_util.h | 8 ++++ app/tests/test_strutil.c | 39 ++++++++++++++++++++ 4 files changed, 130 insertions(+) diff --git a/app/meson.build b/app/meson.build index f08d4cab..0e33c084 100644 --- a/app/meson.build +++ b/app/meson.build @@ -187,6 +187,7 @@ if get_option('buildtype') == 'debug' 'tests/test_cli.c', 'src/cli.c', 'src/options.c', + 'src/util/strbuf.c', 'src/util/str_util.c', ]], ['test_clock', [ @@ -196,6 +197,7 @@ if get_option('buildtype') == 'debug' ['test_control_msg_serialize', [ 'tests/test_control_msg_serialize.c', 'src/control_msg.c', + 'src/util/strbuf.c', 'src/util/str_util.c', ]], ['test_device_msg_deserialize', [ @@ -211,6 +213,7 @@ if get_option('buildtype') == 'debug' ]], ['test_strutil', [ 'tests/test_strutil.c', + 'src/util/strbuf.c', 'src/util/str_util.c', ]], ] diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index 287c08de..b4e7cac4 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -1,9 +1,11 @@ #include "str_util.h" +#include #include #include #include #include +#include "util/strbuf.h" #ifdef _WIN32 # include @@ -209,3 +211,81 @@ utf8_from_wide_char(const wchar_t *ws) { } #endif + +char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) { + assert(indent < columns); + + struct sc_strbuf buf; + + // The output string should not be much longer than the input string (just + // a few '\n' added), so this initial capacity should hopefully almost + // always avoid internal realloc() in string buffer + size_t cap = strlen(input) * 3 / 2; + + if (!sc_strbuf_init(&buf, cap)) { + return false; + } + +#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error +#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error +#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error +#define APPEND_INDENT() if (indent) APPEND_N(' ', indent) + + APPEND_INDENT(); + + // The last separator encountered, it must be inserted only conditionnaly, + // depending on the next token + char pending = 0; + + // col tracks the current column in the current line + size_t col = indent; + while (*input) { + size_t sep_idx = strcspn(input, "\n "); + size_t new_col = col + sep_idx; + if (pending == ' ') { + // The pending space counts + ++new_col; + } + bool wrap = new_col > columns; + + char sep = input[sep_idx]; + if (sep == ' ') + sep = ' '; + + if (wrap) { + APPEND_CHAR('\n'); + APPEND_INDENT(); + col = indent; + } else if (pending) { + APPEND_CHAR(pending); + ++col; + if (pending == '\n') + { + APPEND_INDENT(); + col = indent; + } + } + + if (sep_idx) { + APPEND(input, sep_idx); + col += sep_idx; + } + + pending = sep; + + input += sep_idx; + if (*input != '\0') { + // Skip the separator + ++input; + } + } + + if (pending) + APPEND_CHAR(pending); + + return buf.s; + +error: + free(buf.s); + return NULL; +} diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index 361d2bdd..344522c9 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -62,4 +62,12 @@ char * utf8_from_wide_char(const wchar_t *s); #endif +/** + * Wrap input lines to fit in `columns` columns + * + * Break input lines at word boundaries (spaces) so that they fit in `columns` + * columns, left-indented by `indent` spaces. + */ +char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); + #endif diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index dfd99658..2d23176e 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -299,6 +299,44 @@ static void test_strlist_contains(void) { assert(strlist_contains("xyz", '\0', "xyz")); } +static void test_wrap_lines(void) { + const char *s = "This is a text to test line wrapping. The lines must be " + "wrapped at a space or a line break.\n" + "\n" + "This rectangle must remains a rectangle because it is " + "drawn in lines having lengths lower than the specified " + "number of columns:\n" + " +----+\n" + " | |\n" + " +----+\n"; + + // |---- 1 1 2 2| + // |0 5 0 5 0 3| <-- 24 columns + const char *expected = " This is a text to\n" + " test line wrapping.\n" + " The lines must be\n" + " wrapped at a space\n" + " or a line break.\n" + " \n" + " This rectangle must\n" + " remains a rectangle\n" + " because it is drawn\n" + " in lines having\n" + " lengths lower than\n" + " the specified number\n" + " of columns:\n" + " +----+\n" + " | |\n" + " +----+\n"; + + char *formatted = sc_str_wrap_lines(s, 24, 4); + assert(formatted); + + assert(!strcmp(formatted, expected)); + + free(formatted); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -317,5 +355,6 @@ int main(int argc, char *argv[]) { test_parse_integers(); test_parse_integer_with_suffix(); test_strlist_contains(); + test_wrap_lines(); return 0; } From f59e9e3cb03bb9cdb8d69767ed0ffa1b686f6d87 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 13:31:26 +0100 Subject: [PATCH 0066/1133] Structure command line options help It will allow to correctly print the help for the current terminal size (even if for now it is hardcoded to 80 columns). --- app/src/cli.c | 633 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 404 insertions(+), 229 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 45b87fb2..a4e7335d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -8,244 +8,419 @@ #include "options.h" #include "util/log.h" +#include "util/strbuf.h" #include "util/str_util.h" #define STR_IMPL_(x) #x #define STR(x) STR_IMPL_(x) -void -scrcpy_print_usage(const char *arg0) { - fprintf(stderr, - "Usage: %s [options]\n" - "\n" - "Options:\n" - "\n" - " --always-on-top\n" - " Make scrcpy window always on top (above other windows).\n" - "\n" - " -b, --bit-rate value\n" - " Encode the video at the given bit-rate, expressed in bits/s.\n" - " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" - " Default is " STR(DEFAULT_BIT_RATE) ".\n" - "\n" - " --codec-options key[:type]=value[,...]\n" - " Set a list of comma-separated key:type=value options for the\n" - " device encoder.\n" - " The possible values for 'type' are 'int' (default), 'long',\n" - " 'float' and 'string'.\n" - " The list of possible codec options is available in the\n" - " Android documentation:\n" - " \n" - "\n" - " --crop width:height:x:y\n" - " Crop the device screen on the server.\n" - " The values are expressed in the device natural orientation\n" - " (typically, portrait for a phone, landscape for a tablet).\n" - " Any --max-size value is computed on the cropped size.\n" - "\n" - " --disable-screensaver\n" - " Disable screensaver while scrcpy is running.\n" - "\n" - " --display id\n" - " Specify the display id to mirror.\n" - "\n" - " The list of possible display ids can be listed by:\n" - " adb shell dumpsys display\n" - " (search \"mDisplayId=\" in the output)\n" - "\n" - " Default is 0.\n" - "\n" - " --display-buffer ms\n" - " Add a buffering delay (in milliseconds) before displaying.\n" - " This increases latency to compensate for jitter.\n" - "\n" - " Default is 0 (no buffering).\n" - "\n" - " --encoder name\n" - " Use a specific MediaCodec encoder (must be a H.264 encoder).\n" - "\n" - " --force-adb-forward\n" - " Do not attempt to use \"adb reverse\" to connect to the\n" - " device.\n" - "\n" - " --forward-all-clicks\n" - " By default, right-click triggers BACK (or POWER on) and\n" - " middle-click triggers HOME. This option disables these\n" - " shortcuts and forwards the clicks to the device instead.\n" - "\n" - " -f, --fullscreen\n" - " Start in fullscreen.\n" - "\n" - " -K, --hid-keyboard\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" - " injection method.\n" - " It may only work over USB, and is currently only supported\n" - " on Linux.\n" - "\n" - " -h, --help\n" - " Print this help.\n" - "\n" - " --legacy-paste\n" - " Inject computer clipboard text as a sequence of key events\n" - " on Ctrl+v (like MOD+Shift+v).\n" - " This is a workaround for some devices not behaving as\n" - " expected when setting the device clipboard programmatically.\n" - "\n" - " --lock-video-orientation[=value]\n" - " Lock video orientation to value.\n" - " Possible values are \"unlocked\", \"initial\" (locked to the\n" - " initial orientation), 0, 1, 2 and 3.\n" - " Natural device orientation is 0, and each increment adds a\n" - " 90 degrees rotation counterclockwise.\n" - " Default is \"unlocked\".\n" - " Passing the option without argument is equivalent to passing\n" - " \"initial\".\n" - "\n" - " --max-fps value\n" - " Limit the frame rate of screen capture (officially supported\n" - " since Android 10, but may work on earlier versions).\n" - "\n" - " -m, --max-size value\n" - " Limit both the width and height of the video to value. The\n" - " other dimension is computed so that the device aspect-ratio\n" - " is preserved.\n" - " Default is 0 (unlimited).\n" - "\n" - " -n, --no-control\n" - " Disable device control (mirror the device in read-only).\n" - "\n" - " -N, --no-display\n" - " Do not display device (only when screen recording is\n" - " enabled).\n" - "\n" - " --no-key-repeat\n" - " Do not forward repeated key events when a key is held down.\n" - "\n" - " --no-mipmaps\n" - " If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n" - " mipmaps are automatically generated to improve downscaling\n" - " quality. This option disables the generation of mipmaps.\n" - "\n" - " -p, --port port[:port]\n" - " Set the TCP port (range) used by the client to listen.\n" - " Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" - STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n" - "\n" - " --power-off-on-close\n" - " Turn the device screen off when closing scrcpy.\n" - "\n" - " --prefer-text\n" - " Inject alpha characters and space as text events instead of\n" - " key events.\n" - " This avoids issues when combining multiple keys to enter a\n" - " special character, but breaks the expected behavior of alpha\n" - " keys in games (typically WASD).\n" - "\n" - " --push-target path\n" - " Set the target directory for pushing files to the device by\n" - " drag & drop. It is passed as is to \"adb push\".\n" - " Default is \"/sdcard/Download/\".\n" - "\n" - " -r, --record file.mp4\n" - " Record screen to file.\n" - " The format is determined by the --record-format option if\n" - " set, or by the file extension (.mp4 or .mkv).\n" - "\n" - " --record-format format\n" - " Force recording format (either mp4 or mkv).\n" - "\n" - " --render-driver name\n" - " Request SDL to use the given render driver (this is just a\n" - " hint).\n" - " Supported names are currently \"direct3d\", \"opengl\",\n" - " \"opengles2\", \"opengles\", \"metal\" and \"software\".\n" - " \n" - "\n" - " --rotation value\n" - " Set the initial display rotation.\n" - " Possible values are 0, 1, 2 and 3. Each increment adds a 90\n" - " degrees rotation counterclockwise.\n" - "\n" - " -s, --serial serial\n" - " The device serial number. Mandatory only if several devices\n" - " are connected to adb.\n" - "\n" - " --shortcut-mod key[+...][,...]\n" - " Specify the modifiers to use for scrcpy shortcuts.\n" - " Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n" - " \"lsuper\" and \"rsuper\".\n" - "\n" - " A shortcut can consist in several keys, separated by '+'.\n" - " Several shortcuts can be specified, separated by ','.\n" - "\n" - " For example, to use either LCtrl+LAlt or LSuper for scrcpy\n" - " shortcuts, pass \"lctrl+lalt,lsuper\".\n" - "\n" - " Default is \"lalt,lsuper\" (left-Alt or left-Super).\n" - "\n" - " -S, --turn-screen-off\n" - " Turn the device screen off immediately.\n" - "\n" - " -t, --show-touches\n" - " Enable \"show touches\" on start, restore the initial value\n" - " on exit.\n" - " It only shows physical touches (not clicks from scrcpy).\n" - "\n" +struct sc_option { + char shortopt; + const char *longopt; + // no argument: argdesc == NULL && !optional_arg + // optional argument: argdesc != NULL && optional_arg + // required argument: argdesc != NULL && !optional_arg + const char *argdesc; + bool optional_arg; + const char *text; +}; + +static const struct sc_option options[] = { + { + .longopt = "always-on-top", + .text = "Make scrcpy window always on top (above other windows).", + }, + { + .shortopt = 'b', + .longopt = "bit-rate", + .argdesc = "value", + .text = "Encode the video at the gitven bit-rate, expressed in bits/s. " + "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" + "Default is " STR(DEFAULT_BIT_RATE) ".", + }, + { + .longopt = "codec-options", + .argdesc = "key[:type]=value[,...]", + .text = "Set a list of comma-separated key:type=value options for the " + "device encoder.\n" + "The possible values for 'type' are 'int' (default), 'long', " + "'float' and 'string'.\n" + "The list of possible codec options is available in the " + "Android documentation: " + "", + }, + { + .longopt = "crop", + .argdesc = "width:height:x:y", + .text = "Crop the device screen on the server.\n" + "The values are expressed in the device natural orientation " + "(typically, portrait for a phone, landscape for a tablet). " + "Any --max-size value is cmoputed on the cropped size.", + }, + { + .longopt = "disable-screensaver", + .text = "Disable screensaver while scrcpy is running.", + }, + { + .longopt = "display", + .argdesc = "id", + .text = "Specify the display id to mirror.\n" + "The list of possible display ids can be listed by:\n" + " adb shell dumpsys display\n" + "(search \"mDisplayId=\" in the output)\n" + "Default is 0.", + }, + { + .longopt = "display-buffer", + .argdesc = "ms", + .text = "Add a buffering delay (in milliseconds) before displaying. " + "This increases latency to compensate for jitter.\n" + "Default is 0 (no buffering).", + }, + { + .longopt = "encoder", + .argdesc = "name", + .text = "Use a specific MediaCodec encoder (must be a H.264 encoder).", + }, + { + .longopt = "force-adb-forward", + .text = "Do not attempt to use \"adb reverse\" to connect to the " + "device.", + }, + { + .longopt = "forward-all-clicks", + .text = "By default, right-click triggers BACK (or POWER on) and " + "middle-click triggers HOME. This option disables these " + "shortcuts and forwards the clicks to the device instead.", + }, + { + .shortopt = 'f', + .longopt = "fullscreen", + .text = "Start in fullscreen.", + }, + { + .shortopt = 'K', + .longopt = "hid-keyboard", + .text = "Simulate a physical keyboard by using HID over AOAv2.\n" + "It provides a better experience for IME users, and allows to " + "generate non-ASCII characters, contrary to the default " + "injection method.\n" + "It may only work over USB, and is currently only supported " + "on Linux.", + }, + { + .shortopt = 'h', + .longopt = "help", + .text = "Print this help.", + }, + { + .longopt = "legacy-paste", + .text = "Inject computer clipboard text as a sequence of key events " + "on Ctrl+v (like MOD+Shift+v).\n" + "This is a workaround for some devices not behaving as " + "expected when setting the device clipboard programmatically.", + }, + { + .longopt = "lock-video-orientation", + .argdesc = "value", + .optional_arg = true, + .text = "Lock video orientation to value.\n" + "Possible values are \"unlocked\", \"initial\" (locked to the " + "initial orientation), 0, 1, 2 and 3. Natural device " + "orientation is 0, and each increment adds a 90 degrees " + "rotation counterclockwise.\n" + "Default is \"unlocked\".\n" + "Passing the option without argument is equivalent to passing " + "\"initial\".", + }, + { + .longopt = "max-fps", + .argdesc = "value", + .text = "Limit the frame rate of screen capture (officially supported " + "since Android 10, but may work on earlier versions).", + }, + { + .shortopt = 'm', + .longopt = "max-size", + .argdesc = "value", + .text = "Limit both the width and height of the video to value. The " + "other dimension is computed so that the device aspect-ratio " + "is preserved.\n" + "Default is 0 (unlimited).", + }, + { + .shortopt = 'n', + .longopt = "no-control", + .text = "Disable device control (mirror the device in read-only).", + }, + { + .shortopt = 'N', + .longopt = "no-display", + .text = "Do not display device (only when screen recording " #ifdef HAVE_V4L2 - " --v4l2-sink /dev/videoN\n" - " Output to v4l2loopback device.\n" - " It requires to lock the video orientation (see\n" - " --lock-video-orientation).\n" - "\n" - " --v4l2-buffer ms\n" - " Add a buffering delay (in milliseconds) before pushing\n" - " frames. This increases latency to compensate for jitter.\n" - "\n" - " This option is similar to --display-buffer, but specific to\n" - " V4L2 sink.\n" - "\n" - " Default is 0 (no buffering).\n" - "\n" + "or V4L2 sink " +#endif + "is enabled).", + }, + { + .longopt = "no-key-repeat", + .text = "Do not forward repeated key events when a key is held down.", + }, + { + .longopt = "no-mipmaps", + .text = "If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then " + "mipmaps are automatically generated to improve downscaling " + "quality. This option disables the generation of mipmaps.", + }, + { + .shortopt = 'p', + .longopt = "port", + .argdesc = "port[:port]", + .text = "Set the TCP port (range) used by the client to listen.\n" + "Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" + STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".", + }, + { + .longopt = "power-off-on-close", + .text = "Turn the device screen off when closing scrcpy.", + }, + { + .longopt = "prefer-text", + .text = "Inject alpha characters and space as text events instead of" + "key events.\n" + "This avoids issues when combining multiple keys to enter a " + "special character, but breaks the expected behavior of alpha " + "keys in games (typically WASD).", + }, + { + .longopt = "push-target", + .argdesc = "path", + .text = "Set the target directory for pushing files to the device by " + "drag & drop. It is passed as is to \"adb push\".\n" + "Default is \"/sdcard/Download/\".", + }, + { + .shortopt = 'r', + .longopt = "record", + .argdesc = "file.mp4", + .text = "Record screen to file.\n" + "The format is determined by the --record-format option if " + "set, or by the file extension (.mp4 or .mkv).", + }, + { + .longopt = "record-format", + .argdesc = "format", + .text = "Force recording format (either mp4 or mkv).", + }, + { + .longopt = "render-driver", + .argdesc = "name", + .text = "Request SDL to use the given render driver (this is just a " + "hint).\n" + "Supported names are currently \"direct3d\", \"opengl\", " + "\"opengles2\", \"opengles\", \"metal\" and \"software\".\n" + "", + }, + { + .longopt = "rotation", + .argdesc = "value", + .text = "Set the initial display rotation.\n" + "Possible values are 0, 1, 2 and 3. Each increment adds a 90 " + "degrees rotation counterclockwise.", + }, + { + .shortopt = 's', + .longopt = "serial", + .argdesc = "serial", + .text = "The device serial number. Mandatory only if several devices " + "are connected to adb.", + }, + { + .longopt = "shortcut-mod", + .argdesc = "key[+...][,...]", + .text = "Specify the modifiers to use for scrcpy shortcuts.\n" + "Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", " + "\"lsuper\" and \"rsuper\".\n" + "A shortcut can consist in several keys, separated by '+'. " + "Several shortcuts can be specified, separated by ','.\n" + "For example, to use either LCtrl+LAlt or LSuper for scrcpy " + "shortcuts, pass \"lctrl+lalt,lsuper\".\n" + "Default is \"lalt,lsuper\" (left-Alt or left-Super).", + }, + { + .shortopt = 'S', + .longopt = "turn-screen-off", + .text = "Turn the device screen off immediately.", + }, + { + .shortopt = 't', + .longopt = "show-touches", + .text = "Enable \"show touches\" on start, restore the initial value " + "on exit.\n" + "It only shows physical touches (not clicks from scrcpy).", + }, +#ifdef HAVE_V4L2 + { + .longopt = "v4l2-sink", + .argdesc = "/dev/videoN", + .text = "Output to v4l2loopback device.\n" + "It requires to lock the video orientation (see " + "--lock-video-orientation).", + }, + { + .longopt = "v4l2-buffer", + .argdesc = "ms", + .text = "Add a buffering delay (in milliseconds) before pushing " + "frames. This increases latency to compensate for jitter.\n" + "This option is similar to --display-buffer, but specific to " + "V4L2 sink.\n" + "Default is 0 (no buffering).", + }, #endif - " -V, --verbosity value\n" - " Set the log level (verbose, debug, info, warn or error).\n" + { + .shortopt = 'V', + .longopt = "verbosity", + .argdesc = "value", + .text = "Set the log level (verbose, debug, info, warn or error).\n" #ifndef NDEBUG - " Default is debug.\n" + "Default is debug.", #else - " Default is info.\n" + "Default is info.", #endif - "\n" - " -v, --version\n" - " Print the version of scrcpy.\n" - "\n" - " -w, --stay-awake\n" - " Keep the device on while scrcpy is running, when the device\n" - " is plugged in.\n" - "\n" - " --window-borderless\n" - " Disable window decorations (display borderless window).\n" - "\n" - " --window-title text\n" - " Set a custom window title.\n" - "\n" - " --window-x value\n" - " Set the initial window horizontal position.\n" - " Default is \"auto\".\n" - "\n" - " --window-y value\n" - " Set the initial window vertical position.\n" - " Default is \"auto\".\n" - "\n" - " --window-width value\n" - " Set the initial window width.\n" - " Default is 0 (automatic).\n" - "\n" - " --window-height value\n" - " Set the initial window height.\n" - " Default is 0 (automatic).\n" - "\n" + }, + { + .shortopt = 'v', + .longopt = "version", + .text = "Print the version of scrcpy.", + }, + { + .shortopt = 'w', + .longopt = "stay-awake", + .text = "Keep the device on while scrcpy is running, when the device " + "is plugged in.", + }, + { + .longopt = "window-borderless", + .text = "Disable window decorations (display borderless window)." + }, + { + .longopt = "window-title", + .argdesc = "text", + .text = "Set a custom window title.", + }, + { + .longopt = "window-x", + .argdesc = "value", + .text = "Set the initial window horizontal position.\n" + "Default is \"auto\".", + }, + { + .longopt = "window-y", + .argdesc = "value", + .text = "Set the initial window vertical position.\n" + "Default is \"auto\".", + }, + { + .longopt = "window-width", + .argdesc = "value", + .text = "Set the initial window width.\n" + "Default is 0 (automatic).", + }, + { + .longopt = "window-height", + .argdesc = "value", + .text = "Set the initial window height.\n" + "Default is 0 (automatic).", + }, +}; + +static void +print_option_usage_header(const struct sc_option *opt) { + struct sc_strbuf buf; + if (!sc_strbuf_init(&buf, 64)) { + goto error; + } + + bool ok = true; + (void) ok; // only used for assertions + + if (opt->shortopt) { + ok = sc_strbuf_append_char(&buf, '-'); + assert(ok); + + ok = sc_strbuf_append_char(&buf, opt->shortopt); + assert(ok); + + if (opt->longopt) { + ok = sc_strbuf_append_staticstr(&buf, ", "); + assert(ok); + } + } + + if (opt->longopt) { + ok = sc_strbuf_append_staticstr(&buf, "--"); + assert(ok); + + if (!sc_strbuf_append_str(&buf, opt->longopt)) { + goto error; + } + } + + if (opt->argdesc) { + if (opt->optional_arg && !sc_strbuf_append_char(&buf, '[')) { + goto error; + } + + if (!sc_strbuf_append_char(&buf, '=')) { + goto error; + } + + if (!sc_strbuf_append_str(&buf, opt->argdesc)) { + goto error; + } + + if (opt->optional_arg && !sc_strbuf_append_char(&buf, ']')) { + goto error; + } + } + + fprintf(stderr, "\n %s\n", buf.s); + free(buf.s); + return; + +error: + fprintf(stderr, "\n"); +} + +static void +print_option_usage(const struct sc_option *opt, unsigned cols) { + assert(cols > 8); // sc_str_wrap_lines() requires indent < columns + assert(opt->text); + + print_option_usage_header(opt); + + char *text = sc_str_wrap_lines(opt->text, cols, 8); + if (!text) { + fprintf(stderr, "\n"); + return; + } + + fprintf(stderr, "%s\n", text); + free(text); +} + +void +scrcpy_print_usage(const char *arg0) { + const unsigned cols = 80; // For now, use a hardcoded value + + fprintf(stderr, "Usage: %s [options]\n\n" + "Options:\n", arg0); + for (size_t i = 0; i < ARRAY_LEN(options); ++i) { + print_option_usage(&options[i], cols); + } + + // Print shortcuts section + fprintf(stderr, "\n" "Shortcuts:\n" "\n" " In the following list, MOD is the shortcut modifier. By default,\n" @@ -334,7 +509,7 @@ scrcpy_print_usage(const char *arg0) { "\n" " Drag & drop non-APK file\n" " Push file to device (see --push-target)\n" - "\n", arg0); + "\n"); } static bool From 74ab0a4df814064877d0f84b6c03c82c2039aba7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 18:53:06 +0100 Subject: [PATCH 0067/1133] Structure shortcuts help --- app/src/cli.c | 253 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 163 insertions(+), 90 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index a4e7335d..28474a9d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "options.h" @@ -25,6 +26,12 @@ struct sc_option { const char *text; }; +#define MAX_EQUIVALENT_SHORTCUTS 3 +struct sc_shortcut { + const char *shortcuts[MAX_EQUIVALENT_SHORTCUTS + 1]; + const char *text; +}; + static const struct sc_option options[] = { { .longopt = "always-on-top", @@ -334,6 +341,118 @@ static const struct sc_option options[] = { }, }; +static const struct sc_shortcut shortcuts[] = { + { + .shortcuts = { "MOD+f" }, + .text = "Switch fullscreen mode", + }, + { + .shortcuts = { "MOD+Left" }, + .text = "Rotate display left", + }, + { + .shortcuts = { "MOD+Right" }, + .text = "Rotate display right", + }, + { + .shortcuts = { "MOD+g" }, + .text = "Resize window to 1:1 (pixel-perfect)", + }, + { + .shortcuts = { "MOD+w", "Double-click on black borders" }, + .text = "Resize window to remove black borders", + }, + { + .shortcuts = { "MOD+h", "Middle-click" }, + .text = "Click on HOME", + }, + { + .shortcuts = { + "MOD+b", + "MOD+Backspace", + "Right-click (when screen is on)", + }, + .text = "Click on BACK", + }, + { + .shortcuts = { "MOD+s" }, + .text = "Click on APP_SWITCH", + }, + { + .shortcuts = { "MOD+m" }, + .text = "Click on MENU", + }, + { + .shortcuts = { "MOD+Up" }, + .text = "Click on VOLUME_UP", + }, + { + .shortcuts = { "MOD+Down" }, + .text = "Click on VOLUME_DOWN", + }, + { + .shortcuts = { "MOD+p" }, + .text = "Click on POWER (turn screen on/off)", + }, + { + .shortcuts = { "Right-click (when screen is off)" }, + .text = "Power on", + }, + { + .shortcuts = { "MOD+o" }, + .text = "Turn device screen off (keep mirroring)", + }, + { + .shortcuts = { "MOD+Shift+o" }, + .text = "Turn device screen on", + }, + { + .shortcuts = { "MOD+r" }, + .text = "Rotate device screen", + }, + { + .shortcuts = { "MOD+n" }, + .text = "Expand notification panel", + }, + { + .shortcuts = { "MOD+Shift+n" }, + .text = "Collapse notification panel", + }, + { + .shortcuts = { "MOD+c" }, + .text = "Copy to clipboard (inject COPY keycode, Android >= 7 only)", + }, + { + .shortcuts = { "MOD+x" }, + .text = "Cut to clipboard (inject CUT keycode, Android >= 7 only)", + }, + { + .shortcuts = { "MOD+v" }, + .text = "Copy computer clipboard to device, then paste (inject PASTE " + "keycode, Android >= 7 only)", + }, + { + .shortcuts = { "MOD+Shift+v" }, + .text = "Inject computer clipboard text as a sequence of key events", + }, + { + .shortcuts = { "MOD+i" }, + .text = "Enable/disable FPS counter (print frames/second in logs)", + }, + { + .shortcuts = { "Ctrl+click-and-move" }, + .text = "Pinch-to-zoom from the center of the screen", + }, + { + .shortcuts = { "Drag & drop APK file" }, + .text = "Install APK from computer", + }, + { + .shortcuts = { "Drag & drop non-APK file" }, + .text = "Push file to device (see --push-target)", + }, +}; + static void print_option_usage_header(const struct sc_option *opt) { struct sc_strbuf buf; @@ -409,6 +528,45 @@ print_option_usage(const struct sc_option *opt, unsigned cols) { free(text); } +static void +print_shortcuts_intro(unsigned cols) { + char *intro = sc_str_wrap_lines( + "In the following list, MOD is the shortcut modifier. By default, it's " + "(left) Alt or (left) Super, but it can be configured by " + "--shortcut-mod (see above).", cols, 4); + if (!intro) { + fprintf(stderr, "\n"); + return; + } + + fprintf(stderr, "%s\n", intro); + free(intro); +} + +static void +print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { + assert(cols > 8); // sc_str_wrap_lines() requires indent < columns + assert(shortcut->shortcuts[0]); // At least one shortcut + assert(shortcut->text); + + fprintf(stderr, "\n"); + + unsigned i = 0; + while (shortcut->shortcuts[i]) { + fprintf(stderr, " %s\n", shortcut->shortcuts[i]); + ++i; + }; + + char *text = sc_str_wrap_lines(shortcut->text, cols, 8); + if (!text) { + fprintf(stderr, "\n"); + return; + } + + fprintf(stderr, "%s\n", text); + free(text); +} + void scrcpy_print_usage(const char *arg0) { const unsigned cols = 80; // For now, use a hardcoded value @@ -420,96 +578,11 @@ scrcpy_print_usage(const char *arg0) { } // Print shortcuts section - fprintf(stderr, "\n" - "Shortcuts:\n" - "\n" - " In the following list, MOD is the shortcut modifier. By default,\n" - " it's (left) Alt or (left) Super, but it can be configured by\n" - " --shortcut-mod (see above).\n" - "\n" - " MOD+f\n" - " Switch fullscreen mode\n" - "\n" - " MOD+Left\n" - " Rotate display left\n" - "\n" - " MOD+Right\n" - " Rotate display right\n" - "\n" - " MOD+g\n" - " Resize window to 1:1 (pixel-perfect)\n" - "\n" - " MOD+w\n" - " Double-click on black borders\n" - " Resize window to remove black borders\n" - "\n" - " MOD+h\n" - " Middle-click\n" - " Click on HOME\n" - "\n" - " MOD+b\n" - " MOD+Backspace\n" - " Right-click (when screen is on)\n" - " Click on BACK\n" - "\n" - " MOD+s\n" - " Click on APP_SWITCH\n" - "\n" - " MOD+m\n" - " Click on MENU\n" - "\n" - " MOD+Up\n" - " Click on VOLUME_UP\n" - "\n" - " MOD+Down\n" - " Click on VOLUME_DOWN\n" - "\n" - " MOD+p\n" - " Click on POWER (turn screen on/off)\n" - "\n" - " Right-click (when screen is off)\n" - " Power on\n" - "\n" - " MOD+o\n" - " Turn device screen off (keep mirroring)\n" - "\n" - " MOD+Shift+o\n" - " Turn device screen on\n" - "\n" - " MOD+r\n" - " Rotate device screen\n" - "\n" - " MOD+n\n" - " Expand notification panel\n" - "\n" - " MOD+Shift+n\n" - " Collapse notification panel\n" - "\n" - " MOD+c\n" - " Copy to clipboard (inject COPY keycode, Android >= 7 only)\n" - "\n" - " MOD+x\n" - " Cut to clipboard (inject CUT keycode, Android >= 7 only)\n" - "\n" - " MOD+v\n" - " Copy computer clipboard to device, then paste (inject PASTE\n" - " keycode, Android >= 7 only)\n" - "\n" - " MOD+Shift+v\n" - " Inject computer clipboard text as a sequence of key events\n" - "\n" - " MOD+i\n" - " Enable/disable FPS counter (print frames/second in logs)\n" - "\n" - " Ctrl+click-and-move\n" - " Pinch-to-zoom from the center of the screen\n" - "\n" - " Drag & drop APK file\n" - " Install APK from computer\n" - "\n" - " Drag & drop non-APK file\n" - " Push file to device (see --push-target)\n" - "\n"); + fprintf(stderr, "\nShortcuts:\n\n"); + print_shortcuts_intro(cols); + for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { + print_shortcut(&shortcuts[i], cols); + } } static bool From 3f51a2ab43db7bfd0a09923e2a9d4604e556fce9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Nov 2021 19:24:13 +0100 Subject: [PATCH 0068/1133] Generate getopt params from option structures Use the option descriptions to generate the optstring and longopts parameters for the getopt_long() command. That way, the options are completely described in a single place. --- app/src/cli.c | 288 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 193 insertions(+), 95 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 28474a9d..f94504c6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -15,15 +15,47 @@ #define STR_IMPL_(x) #x #define STR(x) STR_IMPL_(x) +#define OPT_RENDER_EXPIRED_FRAMES 1000 +#define OPT_WINDOW_TITLE 1001 +#define OPT_PUSH_TARGET 1002 +#define OPT_ALWAYS_ON_TOP 1003 +#define OPT_CROP 1004 +#define OPT_RECORD_FORMAT 1005 +#define OPT_PREFER_TEXT 1006 +#define OPT_WINDOW_X 1007 +#define OPT_WINDOW_Y 1008 +#define OPT_WINDOW_WIDTH 1009 +#define OPT_WINDOW_HEIGHT 1010 +#define OPT_WINDOW_BORDERLESS 1011 +#define OPT_MAX_FPS 1012 +#define OPT_LOCK_VIDEO_ORIENTATION 1013 +#define OPT_DISPLAY_ID 1014 +#define OPT_ROTATION 1015 +#define OPT_RENDER_DRIVER 1016 +#define OPT_NO_MIPMAPS 1017 +#define OPT_CODEC_OPTIONS 1018 +#define OPT_FORCE_ADB_FORWARD 1019 +#define OPT_DISABLE_SCREENSAVER 1020 +#define OPT_SHORTCUT_MOD 1021 +#define OPT_NO_KEY_REPEAT 1022 +#define OPT_FORWARD_ALL_CLICKS 1023 +#define OPT_LEGACY_PASTE 1024 +#define OPT_ENCODER_NAME 1025 +#define OPT_POWER_OFF_ON_CLOSE 1026 +#define OPT_V4L2_SINK 1027 +#define OPT_DISPLAY_BUFFER 1028 +#define OPT_V4L2_BUFFER 1029 + struct sc_option { char shortopt; + int longopt_id; // either shortopt or longopt_id is non-zero const char *longopt; // no argument: argdesc == NULL && !optional_arg // optional argument: argdesc != NULL && optional_arg // required argument: argdesc != NULL && !optional_arg const char *argdesc; bool optional_arg; - const char *text; + const char *text; // if NULL, the option does not appear in the help }; #define MAX_EQUIVALENT_SHORTCUTS 3 @@ -32,8 +64,14 @@ struct sc_shortcut { const char *text; }; +struct sc_getopt_adapter { + char *optstring; + struct option *longopts; +}; + static const struct sc_option options[] = { { + .longopt_id = OPT_ALWAYS_ON_TOP, .longopt = "always-on-top", .text = "Make scrcpy window always on top (above other windows).", }, @@ -46,6 +84,7 @@ static const struct sc_option options[] = { "Default is " STR(DEFAULT_BIT_RATE) ".", }, { + .longopt_id = OPT_CODEC_OPTIONS, .longopt = "codec-options", .argdesc = "key[:type]=value[,...]", .text = "Set a list of comma-separated key:type=value options for the " @@ -57,6 +96,7 @@ static const struct sc_option options[] = { "", }, { + .longopt_id = OPT_CROP, .longopt = "crop", .argdesc = "width:height:x:y", .text = "Crop the device screen on the server.\n" @@ -65,10 +105,12 @@ static const struct sc_option options[] = { "Any --max-size value is cmoputed on the cropped size.", }, { + .longopt_id = OPT_DISABLE_SCREENSAVER, .longopt = "disable-screensaver", .text = "Disable screensaver while scrcpy is running.", }, { + .longopt_id = OPT_DISPLAY_ID, .longopt = "display", .argdesc = "id", .text = "Specify the display id to mirror.\n" @@ -78,6 +120,7 @@ static const struct sc_option options[] = { "Default is 0.", }, { + .longopt_id = OPT_DISPLAY_BUFFER, .longopt = "display-buffer", .argdesc = "ms", .text = "Add a buffering delay (in milliseconds) before displaying. " @@ -85,16 +128,19 @@ static const struct sc_option options[] = { "Default is 0 (no buffering).", }, { + .longopt_id = OPT_ENCODER_NAME, .longopt = "encoder", .argdesc = "name", .text = "Use a specific MediaCodec encoder (must be a H.264 encoder).", }, { + .longopt_id = OPT_FORCE_ADB_FORWARD, .longopt = "force-adb-forward", .text = "Do not attempt to use \"adb reverse\" to connect to the " "device.", }, { + .longopt_id = OPT_FORWARD_ALL_CLICKS, .longopt = "forward-all-clicks", .text = "By default, right-click triggers BACK (or POWER on) and " "middle-click triggers HOME. This option disables these " @@ -121,6 +167,7 @@ static const struct sc_option options[] = { .text = "Print this help.", }, { + .longopt_id = OPT_LEGACY_PASTE, .longopt = "legacy-paste", .text = "Inject computer clipboard text as a sequence of key events " "on Ctrl+v (like MOD+Shift+v).\n" @@ -128,6 +175,7 @@ static const struct sc_option options[] = { "expected when setting the device clipboard programmatically.", }, { + .longopt_id = OPT_LOCK_VIDEO_ORIENTATION, .longopt = "lock-video-orientation", .argdesc = "value", .optional_arg = true, @@ -141,6 +189,7 @@ static const struct sc_option options[] = { "\"initial\".", }, { + .longopt_id = OPT_MAX_FPS, .longopt = "max-fps", .argdesc = "value", .text = "Limit the frame rate of screen capture (officially supported " @@ -170,10 +219,12 @@ static const struct sc_option options[] = { "is enabled).", }, { + .longopt_id = OPT_NO_KEY_REPEAT, .longopt = "no-key-repeat", .text = "Do not forward repeated key events when a key is held down.", }, { + .longopt_id = OPT_NO_MIPMAPS, .longopt = "no-mipmaps", .text = "If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then " "mipmaps are automatically generated to improve downscaling " @@ -188,10 +239,12 @@ static const struct sc_option options[] = { STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".", }, { + .longopt_id = OPT_POWER_OFF_ON_CLOSE, .longopt = "power-off-on-close", .text = "Turn the device screen off when closing scrcpy.", }, { + .longopt_id = OPT_PREFER_TEXT, .longopt = "prefer-text", .text = "Inject alpha characters and space as text events instead of" "key events.\n" @@ -200,6 +253,7 @@ static const struct sc_option options[] = { "keys in games (typically WASD).", }, { + .longopt_id = OPT_PUSH_TARGET, .longopt = "push-target", .argdesc = "path", .text = "Set the target directory for pushing files to the device by " @@ -215,11 +269,13 @@ static const struct sc_option options[] = { "set, or by the file extension (.mp4 or .mkv).", }, { + .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", .text = "Force recording format (either mp4 or mkv).", }, { + .longopt_id = OPT_RENDER_DRIVER, .longopt = "render-driver", .argdesc = "name", .text = "Request SDL to use the given render driver (this is just a " @@ -229,6 +285,12 @@ static const struct sc_option options[] = { "", }, { + // deprecated + .longopt_id = OPT_RENDER_EXPIRED_FRAMES, + .longopt = "render-expired-frames", + }, + { + .longopt_id = OPT_ROTATION, .longopt = "rotation", .argdesc = "value", .text = "Set the initial display rotation.\n" @@ -243,6 +305,7 @@ static const struct sc_option options[] = { "are connected to adb.", }, { + .longopt_id = OPT_SHORTCUT_MOD, .longopt = "shortcut-mod", .argdesc = "key[+...][,...]", .text = "Specify the modifiers to use for scrcpy shortcuts.\n" @@ -268,6 +331,7 @@ static const struct sc_option options[] = { }, #ifdef HAVE_V4L2 { + .longopt_id = OPT_V4L2_SINK, .longopt = "v4l2-sink", .argdesc = "/dev/videoN", .text = "Output to v4l2loopback device.\n" @@ -275,6 +339,7 @@ static const struct sc_option options[] = { "--lock-video-orientation).", }, { + .longopt_id = OPT_V4L2_BUFFER, .longopt = "v4l2-buffer", .argdesc = "ms", .text = "Add a buffering delay (in milliseconds) before pushing " @@ -307,33 +372,39 @@ static const struct sc_option options[] = { "is plugged in.", }, { + .longopt_id = OPT_WINDOW_BORDERLESS, .longopt = "window-borderless", .text = "Disable window decorations (display borderless window)." }, { + .longopt_id = OPT_WINDOW_TITLE, .longopt = "window-title", .argdesc = "text", .text = "Set a custom window title.", }, { + .longopt_id = OPT_WINDOW_X, .longopt = "window-x", .argdesc = "value", .text = "Set the initial window horizontal position.\n" "Default is \"auto\".", }, { + .longopt_id = OPT_WINDOW_Y, .longopt = "window-y", .argdesc = "value", .text = "Set the initial window vertical position.\n" "Default is \"auto\".", }, { + .longopt_id = OPT_WINDOW_WIDTH, .longopt = "window-width", .argdesc = "value", .text = "Set the initial window width.\n" "Default is 0 (automatic).", }, { + .longopt_id = OPT_WINDOW_HEIGHT, .longopt = "window-height", .argdesc = "value", .text = "Set the initial window height.\n" @@ -453,6 +524,102 @@ static const struct sc_shortcut shortcuts[] = { }, }; +static char * +sc_getopt_adapter_create_optstring(void) { + struct sc_strbuf buf; + if (!sc_strbuf_init(&buf, 64)) { + return false; + } + + for (size_t i = 0; i < ARRAY_LEN(options); ++i) { + const struct sc_option *opt = &options[i]; + if (opt->shortopt) { + if (!sc_strbuf_append_char(&buf, opt->shortopt)) { + goto error; + } + // If there is an argument, add ':' + if (opt->argdesc) { + if (!sc_strbuf_append_char(&buf, ':')) { + goto error; + } + // If the argument is optional, add another ':' + if (opt->optional_arg && !sc_strbuf_append_char(&buf, ':')) { + goto error; + } + } + } + } + + return buf.s; + +error: + free(buf.s); + return NULL; +} + +static struct option * +sc_getopt_adapter_create_longopts(void) { + struct option *longopts = + malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts)); + if (!longopts) { + return NULL; + } + + size_t out_idx = 0; + for (size_t i = 0; i < ARRAY_LEN(options); ++i) { + const struct sc_option *in = &options[i]; + if (!in->longopt) { + // The longopts array must only contain long options + continue; + } + struct option *out = &longopts[out_idx++]; + + out->name = in->longopt; + + if (!in->argdesc) { + assert(!in->optional_arg); + out->has_arg = no_argument; + } else if (in->optional_arg) { + out->has_arg = optional_argument; + } else { + out->has_arg = required_argument; + } + + out->flag = NULL; + + // Either shortopt or longopt_id is set, but not both + assert(!!in->shortopt ^ !!in->longopt_id); + out->val = in->shortopt ? in->shortopt : in->longopt_id; + } + + // The array must be terminated by a NULL item + longopts[out_idx] = (struct option) {0}; + + return longopts; +} + +static bool +sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) { + adapter->optstring = sc_getopt_adapter_create_optstring(); + if (!adapter->optstring) { + return false; + } + + adapter->longopts = sc_getopt_adapter_create_longopts(); + if (!adapter->longopts) { + free(adapter->optstring); + return false; + } + + return true; +}; + +static void +sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) { + free(adapter->optstring); + free(adapter->longopts); +} + static void print_option_usage_header(const struct sc_option *opt) { struct sc_strbuf buf; @@ -514,7 +681,11 @@ error: static void print_option_usage(const struct sc_option *opt, unsigned cols) { assert(cols > 8); // sc_str_wrap_lines() requires indent < columns - assert(opt->text); + + if (!opt->text) { + // Option not documented in help (for example because it is deprecated) + return; + } print_option_usage_header(opt); @@ -951,104 +1122,15 @@ guess_record_format(const char *filename) { return 0; } -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 -#define OPT_LOCK_VIDEO_ORIENTATION 1013 -#define OPT_DISPLAY_ID 1014 -#define OPT_ROTATION 1015 -#define OPT_RENDER_DRIVER 1016 -#define OPT_NO_MIPMAPS 1017 -#define OPT_CODEC_OPTIONS 1018 -#define OPT_FORCE_ADB_FORWARD 1019 -#define OPT_DISABLE_SCREENSAVER 1020 -#define OPT_SHORTCUT_MOD 1021 -#define OPT_NO_KEY_REPEAT 1022 -#define OPT_FORWARD_ALL_CLICKS 1023 -#define OPT_LEGACY_PASTE 1024 -#define OPT_ENCODER_NAME 1025 -#define OPT_POWER_OFF_ON_CLOSE 1026 -#define OPT_V4L2_SINK 1027 -#define OPT_DISPLAY_BUFFER 1028 -#define OPT_V4L2_BUFFER 1029 - -bool -scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { - static const struct option long_options[] = { - {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, - {"bit-rate", required_argument, NULL, 'b'}, - {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, - {"crop", required_argument, NULL, OPT_CROP}, - {"disable-screensaver", no_argument, NULL, - OPT_DISABLE_SCREENSAVER}, - {"display", required_argument, NULL, OPT_DISPLAY_ID}, - {"display-buffer", required_argument, NULL, OPT_DISPLAY_BUFFER}, - {"encoder", required_argument, NULL, OPT_ENCODER_NAME}, - {"force-adb-forward", no_argument, NULL, - OPT_FORCE_ADB_FORWARD}, - {"forward-all-clicks", no_argument, NULL, - OPT_FORWARD_ALL_CLICKS}, - {"fullscreen", no_argument, NULL, 'f'}, - {"help", no_argument, NULL, 'h'}, - {"hid-keyboard", no_argument, NULL, 'K'}, - {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, - {"lock-video-orientation", optional_argument, NULL, - OPT_LOCK_VIDEO_ORIENTATION}, - {"max-fps", required_argument, NULL, OPT_MAX_FPS}, - {"max-size", required_argument, NULL, 'm'}, - {"no-control", no_argument, NULL, 'n'}, - {"no-display", no_argument, NULL, 'N'}, - {"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT}, - {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, - {"port", required_argument, NULL, 'p'}, - {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, - {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, - {"record", required_argument, NULL, 'r'}, - {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"render-driver", required_argument, NULL, OPT_RENDER_DRIVER}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, - {"rotation", required_argument, NULL, OPT_ROTATION}, - {"serial", required_argument, NULL, 's'}, - {"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD}, - {"show-touches", no_argument, NULL, 't'}, - {"stay-awake", no_argument, NULL, 'w'}, - {"turn-screen-off", no_argument, NULL, 'S'}, -#ifdef HAVE_V4L2 - {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, - {"v4l2-buffer", required_argument, NULL, OPT_V4L2_BUFFER}, -#endif - {"verbosity", required_argument, NULL, 'V'}, - {"version", no_argument, NULL, 'v'}, - {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, - {"window-x", required_argument, NULL, OPT_WINDOW_X}, - {"window-y", required_argument, NULL, OPT_WINDOW_Y}, - {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, - {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, - {"window-borderless", no_argument, NULL, - OPT_WINDOW_BORDERLESS}, - {"power-off-on-close", no_argument, NULL, - OPT_POWER_OFF_ON_CLOSE}, - {NULL, 0, NULL, 0 }, - }; - +static bool +parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], + const char *optstring, const struct option *longopts) { struct scrcpy_options *opts = &args->opts; optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:fF:hKm:nNp:r:s:StvV:w", - long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &opts->bit_rate)) { @@ -1288,3 +1370,19 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return true; } + +bool +scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { + struct sc_getopt_adapter adapter; + if (!sc_getopt_adapter_init(&adapter)) { + LOGW("Could not create getopt adapter"); + return false; + } + + bool ret = parse_args_with_getopt(args, argc, argv, adapter.optstring, + adapter.longopts); + + sc_getopt_adapter_destroy(&adapter); + + return ret; +} From 38332f683c6744725d8f197f16fe513d4a287ee9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 15:22:39 +0100 Subject: [PATCH 0069/1133] Add util function to get terminal size --- app/meson.build | 2 ++ app/src/util/term.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ app/src/util/term.h | 21 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 app/src/util/term.c create mode 100644 app/src/util/term.h diff --git a/app/meson.build b/app/meson.build index 0e33c084..d13e421e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -29,6 +29,7 @@ src = [ 'src/util/process.c', 'src/util/strbuf.c', 'src/util/str_util.c', + 'src/util/term.c', 'src/util/thread.c', 'src/util/tick.c', ] @@ -189,6 +190,7 @@ if get_option('buildtype') == 'debug' 'src/options.c', 'src/util/strbuf.c', 'src/util/str_util.c', + 'src/util/term.c', ]], ['test_clock', [ 'tests/test_clock.c', diff --git a/app/src/util/term.c b/app/src/util/term.c new file mode 100644 index 00000000..ff6bc4b1 --- /dev/null +++ b/app/src/util/term.c @@ -0,0 +1,51 @@ +#include "term.h" + +#include + +#ifdef _WIN32 +# include +#else +# include +# include +#endif + +bool +sc_term_get_size(unsigned *rows, unsigned *cols) { +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + + bool ok = + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + if (!ok) { + return false; + } + + if (rows) { + assert(csbi.srWindow.Bottom >= csbi.srWindow.Top); + *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } + + if (cols) { + assert(csbi.srWindow.Right >= csbi.srWindow.Left); + *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + } + + return true; +#else + struct winsize ws; + int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + if (r == -1) { + return false; + } + + if (rows) { + *rows = ws.ws_row; + } + + if (cols) { + *cols = ws.ws_col; + } + + return true; +#endif +} diff --git a/app/src/util/term.h b/app/src/util/term.h new file mode 100644 index 00000000..0211bcb4 --- /dev/null +++ b/app/src/util/term.h @@ -0,0 +1,21 @@ +#ifndef SC_TERM_H +#define SC_TERM_H + +#include "common.h" + +#include + +/** + * Return the terminal dimensions + * + * Return false if the dimensions could not be retrieved. + * + * Otherwise, return true, and: + * - if `rows` is not NULL, then the number of rows is written to `*rows`. + * - if `columns` is not NULL, then the number of columns is written to + * `*columns`. + */ +bool +sc_term_get_size(unsigned *rows, unsigned *cols); + +#endif From 7a733328bc5d20c1b5e2fed1b6f1715f2c89dc23 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 15:22:39 +0100 Subject: [PATCH 0070/1133] Adapt help to terminal size If the error stream is a terminal, and we can retrieve the terminal size, wrap the help content according to the terminal width. --- app/src/cli.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index f94504c6..e38ff9f6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -11,6 +11,7 @@ #include "util/log.h" #include "util/strbuf.h" #include "util/str_util.h" +#include "util/term.h" #define STR_IMPL_(x) #x #define STR(x) STR_IMPL_(x) @@ -740,7 +741,23 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { void scrcpy_print_usage(const char *arg0) { - const unsigned cols = 80; // For now, use a hardcoded value +#define SC_TERM_COLS_DEFAULT 80 + unsigned cols; + + if (!isatty(STDERR_FILENO)) { + // Not a tty + cols = SC_TERM_COLS_DEFAULT; + } else { + bool ok = sc_term_get_size(NULL, &cols); + if (!ok) { + // Could not get the terminal size + cols = SC_TERM_COLS_DEFAULT; + } + if (cols < 20) { + // Do not accept a too small value + cols = 20; + } + } fprintf(stderr, "Usage: %s [options]\n\n" "Options:\n", arg0); From c548f017bf26823c9781e6156c25f14e92144f45 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 22:42:08 +0100 Subject: [PATCH 0071/1133] Upgrade junit to 4.13.1 --- server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index ef936d1b..cc03ecc8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -20,7 +20,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.1' } apply from: "$project.rootDir/config/android-checkstyle.gradle" From be55e250ca64563d1a24f0d0d2d8c9d5fecb6ada Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 22:39:36 +0100 Subject: [PATCH 0072/1133] Make screen_render() static It is only used from screen.c. --- app/src/screen.c | 73 ++++++++++++++++++++++++++---------------------- app/src/screen.h | 7 ----- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index e2d15180..d402b402 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -224,6 +224,45 @@ create_texture(struct screen *screen) { return texture; } +// render the texture to the renderer +// +// Set the update_content_rect flag if the window or content size may have +// changed, so that the content rectangle is recomputed +static void +screen_render(struct screen *screen, bool update_content_rect) { + if (update_content_rect) { + screen_update_content_rect(screen); + } + + SDL_RenderClear(screen->renderer); + if (screen->rotation == 0) { + SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); + } else { + // rotation in RenderCopyEx() is clockwise, while screen->rotation is + // counterclockwise (to be consistent with --lock-video-orientation) + int cw_rotation = (4 - screen->rotation) % 4; + double angle = 90 * cw_rotation; + + SDL_Rect *dstrect = NULL; + SDL_Rect rect; + if (screen->rotation & 1) { + rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2; + rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2; + rect.w = screen->rect.h; + rect.h = screen->rect.w; + dstrect = ▭ + } else { + assert(screen->rotation == 2); + dstrect = &screen->rect; + } + + SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, + angle, NULL, 0); + } + SDL_RenderPresent(screen->renderer); +} + + #if defined(__APPLE__) || defined(__WINDOWS__) # define CONTINUOUS_RESIZING_WORKAROUND #endif @@ -642,40 +681,6 @@ screen_update_frame(struct screen *screen) { return true; } -void -screen_render(struct screen *screen, bool update_content_rect) { - if (update_content_rect) { - screen_update_content_rect(screen); - } - - SDL_RenderClear(screen->renderer); - if (screen->rotation == 0) { - SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); - } else { - // rotation in RenderCopyEx() is clockwise, while screen->rotation is - // counterclockwise (to be consistent with --lock-video-orientation) - int cw_rotation = (4 - screen->rotation) % 4; - double angle = 90 * cw_rotation; - - SDL_Rect *dstrect = NULL; - SDL_Rect rect; - if (screen->rotation & 1) { - rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2; - rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2; - rect.w = screen->rect.h; - rect.h = screen->rect.w; - dstrect = ▭ - } else { - assert(screen->rotation == 2); - dstrect = &screen->rect; - } - - SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, - angle, NULL, 0); - } - SDL_RenderPresent(screen->renderer); -} - void screen_switch_fullscreen(struct screen *screen) { uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; diff --git a/app/src/screen.h b/app/src/screen.h index 51946dbb..b82bf631 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -93,13 +93,6 @@ screen_destroy(struct screen *screen); void screen_hide_window(struct screen *screen); -// render the texture to the renderer -// -// Set the update_content_rect flag if the window or content size may have -// changed, so that the content rectangle is recomputed -void -screen_render(struct screen *screen, bool update_content_rect); - // switch the fullscreen mode void screen_switch_fullscreen(struct screen *screen); From d4c262301fb0543ecd4b6c7240f3d87d908b3bf2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 16:12:17 +0100 Subject: [PATCH 0073/1133] Move functions from process to file Move filesystem-related functions from process.[ch] to file.[ch]. --- app/meson.build | 11 +++++- app/src/adb.c | 1 + app/src/icon.c | 2 +- app/src/server.c | 1 + app/src/sys/unix/file.c | 75 ++++++++++++++++++++++++++++++++++++++ app/src/sys/unix/process.c | 71 ------------------------------------ app/src/sys/win/file.c | 43 ++++++++++++++++++++++ app/src/sys/win/process.c | 35 ------------------ app/src/util/file.c | 48 ++++++++++++++++++++++++ app/src/util/file.h | 34 +++++++++++++++++ app/src/util/process.c | 41 --------------------- app/src/util/process.h | 22 ----------- 12 files changed, 212 insertions(+), 172 deletions(-) create mode 100644 app/src/sys/unix/file.c create mode 100644 app/src/sys/win/file.c create mode 100644 app/src/util/file.c create mode 100644 app/src/util/file.h diff --git a/app/meson.build b/app/meson.build index d13e421e..94b4994f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -24,6 +24,7 @@ src = [ 'src/server.c', 'src/stream.c', 'src/video_buffer.c', + 'src/util/file.c', 'src/util/log.c', 'src/util/net.c', 'src/util/process.c', @@ -35,9 +36,15 @@ src = [ ] if host_machine.system() == 'windows' - src += [ 'src/sys/win/process.c' ] + src += [ + 'src/sys/win/file.c', + 'src/sys/win/process.c', + ] else - src += [ 'src/sys/unix/process.c' ] + src += [ + 'src/sys/unix/file.c', + 'src/sys/unix/process.c', + ] endif v4l2_support = host_machine.system() == 'linux' diff --git a/app/src/adb.c b/app/src/adb.c index 7f7e28d8..d127494a 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -5,6 +5,7 @@ #include #include +#include "util/file.h" #include "util/log.h" #include "util/str_util.h" diff --git a/app/src/icon.c b/app/src/icon.c index 607c7162..787d048f 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -8,8 +8,8 @@ #include "config.h" #include "compat.h" +#include "util/file.h" #include "util/log.h" -#include "util/process.h" #include "util/str_util.h" #define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" diff --git a/app/src/server.c b/app/src/server.c index 4a4d3ec4..aba77288 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -8,6 +8,7 @@ #include #include "adb.h" +#include "util/file.h" #include "util/log.h" #include "util/net.h" #include "util/str_util.h" diff --git a/app/src/sys/unix/file.c b/app/src/sys/unix/file.c new file mode 100644 index 00000000..2e71b113 --- /dev/null +++ b/app/src/sys/unix/file.c @@ -0,0 +1,75 @@ +#include "util/file.h" + +#include +#include +#include +#include +#include + +bool +search_executable(const char *file) { + char *path = getenv("PATH"); + if (!path) + return false; + path = strdup(path); + if (!path) + return false; + + bool ret = false; + size_t file_len = strlen(file); + char *saveptr; + for (char *dir = strtok_r(path, ":", &saveptr); dir; + dir = strtok_r(NULL, ":", &saveptr)) { + size_t dir_len = strlen(dir); + char *fullpath = malloc(dir_len + file_len + 2); + if (!fullpath) + continue; + memcpy(fullpath, dir, dir_len); + fullpath[dir_len] = '/'; + memcpy(fullpath + dir_len + 1, file, file_len + 1); + + struct stat sb; + bool fullpath_executable = stat(fullpath, &sb) == 0 && + sb.st_mode & S_IXUSR; + free(fullpath); + if (fullpath_executable) { + ret = true; + break; + } + } + + free(path); + return ret; +} + +char * +get_executable_path(void) { +// +#ifdef __linux__ + char buf[PATH_MAX + 1]; // +1 for the null byte + ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX); + if (len == -1) { + perror("readlink"); + return NULL; + } + buf[len] = '\0'; + return strdup(buf); +#else + // in practice, we only need this feature for portable builds, only used on + // Windows, so we don't care implementing it for every platform + // (it's useful to have a working version on Linux for debugging though) + return NULL; +#endif +} + +bool +is_regular_file(const char *path) { + struct stat path_stat; + + if (stat(path, &path_stat)) { + perror("stat"); + return false; + } + return S_ISREG(path_stat.st_mode); +} + diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 451b6491..e534ae9d 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -3,53 +3,13 @@ #include #include #include -#include #include -#include -#include -#include #include #include #include #include "util/log.h" -bool -search_executable(const char *file) { - char *path = getenv("PATH"); - if (!path) - return false; - path = strdup(path); - if (!path) - return false; - - bool ret = false; - size_t file_len = strlen(file); - char *saveptr; - for (char *dir = strtok_r(path, ":", &saveptr); dir; - dir = strtok_r(NULL, ":", &saveptr)) { - size_t dir_len = strlen(dir); - char *fullpath = malloc(dir_len + file_len + 2); - if (!fullpath) - continue; - memcpy(fullpath, dir, dir_len); - fullpath[dir_len] = '/'; - memcpy(fullpath + dir_len + 1, file, file_len + 1); - - struct stat sb; - bool fullpath_executable = stat(fullpath, &sb) == 0 && - sb.st_mode & S_IXUSR; - free(fullpath); - if (fullpath_executable) { - ret = true; - break; - } - } - - free(path); - return ret; -} - enum process_result process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, int *pipe_stdout, int *pipe_stderr) { @@ -232,37 +192,6 @@ process_close(pid_t pid) { process_wait(pid, true); // ignore exit code } -char * -get_executable_path(void) { -// -#ifdef __linux__ - char buf[PATH_MAX + 1]; // +1 for the null byte - ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX); - if (len == -1) { - perror("readlink"); - return NULL; - } - buf[len] = '\0'; - return strdup(buf); -#else - // in practice, we only need this feature for portable builds, only used on - // Windows, so we don't care implementing it for every platform - // (it's useful to have a working version on Linux for debugging though) - return NULL; -#endif -} - -bool -is_regular_file(const char *path) { - struct stat path_stat; - - if (stat(path, &path_stat)) { - perror("stat"); - return false; - } - return S_ISREG(path_stat.st_mode); -} - ssize_t read_pipe(int pipe, char *data, size_t len) { return read(pipe, data, len); diff --git a/app/src/sys/win/file.c b/app/src/sys/win/file.c new file mode 100644 index 00000000..0f9101e9 --- /dev/null +++ b/app/src/sys/win/file.c @@ -0,0 +1,43 @@ +#include "util/file.h" + +#include + +#include + +#include "util/log.h" +#include "util/str_util.h" + +char * +get_executable_path(void) { + HMODULE hModule = GetModuleHandleW(NULL); + if (!hModule) { + return NULL; + } + WCHAR buf[MAX_PATH + 1]; // +1 for the null byte + int len = GetModuleFileNameW(hModule, buf, MAX_PATH); + if (!len) { + return NULL; + } + buf[len] = '\0'; + return utf8_from_wide_char(buf); +} + +bool +is_regular_file(const char *path) { + wchar_t *wide_path = utf8_to_wide_char(path); + if (!wide_path) { + LOGC("Could not allocate wide char string"); + return false; + } + + struct _stat path_stat; + int r = _wstat(wide_path, &path_stat); + free(wide_path); + + if (r) { + perror("stat"); + return false; + } + return S_ISREG(path_stat.st_mode); +} + diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 9a846fad..289d1fca 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -1,7 +1,6 @@ #include "util/process.h" #include -#include #include "util/log.h" #include "util/str_util.h" @@ -174,40 +173,6 @@ process_close(HANDLE handle) { (void) closed; } -char * -get_executable_path(void) { - HMODULE hModule = GetModuleHandleW(NULL); - if (!hModule) { - return NULL; - } - WCHAR buf[MAX_PATH + 1]; // +1 for the null byte - int len = GetModuleFileNameW(hModule, buf, MAX_PATH); - if (!len) { - return NULL; - } - buf[len] = '\0'; - return utf8_from_wide_char(buf); -} - -bool -is_regular_file(const char *path) { - wchar_t *wide_path = utf8_to_wide_char(path); - if (!wide_path) { - LOGC("Could not allocate wide char string"); - return false; - } - - struct _stat path_stat; - int r = _wstat(wide_path, &path_stat); - free(wide_path); - - if (r) { - perror("stat"); - return false; - } - return S_ISREG(path_stat.st_mode); -} - ssize_t read_pipe(HANDLE pipe, char *data, size_t len) { DWORD r; diff --git a/app/src/util/file.c b/app/src/util/file.c new file mode 100644 index 00000000..bb7fdb2c --- /dev/null +++ b/app/src/util/file.c @@ -0,0 +1,48 @@ +#include "file.h" + +#include +#include + +#include "util/log.h" + +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 + 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; +} + diff --git a/app/src/util/file.h b/app/src/util/file.h new file mode 100644 index 00000000..813af486 --- /dev/null +++ b/app/src/util/file.h @@ -0,0 +1,34 @@ +#ifndef SC_FILE_H +#define SC_FILE_H + +#include "common.h" + +#include + +#ifdef _WIN32 +# define PATH_SEPARATOR '\\' +#else +# define PATH_SEPARATOR '/' +#endif + +#ifndef _WIN32 +// only used to find package manager, not implemented for Windows +bool +search_executable(const char *file); +#endif + +// return the absolute path of the executable (the scrcpy binary) +// may be NULL on error; to be freed by free() +char * +get_executable_path(void); + +// Return the absolute path of a file in the same directory as he executable. +// May be NULL on error. To be freed by free(). +char * +get_local_file_path(const char *name); + +// returns true if the file exists and is not a directory +bool +is_regular_file(const char *path); + +#endif diff --git a/app/src/util/process.c b/app/src/util/process.c index 5d572c26..637132d9 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -21,47 +21,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 - 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; diff --git a/app/src/util/process.h b/app/src/util/process.h index d6471a16..d609ae71 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -10,7 +10,6 @@ // not needed here, but winsock2.h must never be included AFTER windows.h # include # include -# define PATH_SEPARATOR '\\' # define PRIexitcode "lu" // # define PRIsizet "Iu" @@ -23,7 +22,6 @@ #else # include -# define PATH_SEPARATOR '/' # define PRIsizet "zu" # define PRIexitcode "d" # define PROCESS_NONE -1 @@ -73,26 +71,6 @@ process_close(process_t pid); bool process_check_success(process_t proc, const char *name, bool close); -#ifndef _WIN32 -// only used to find package manager, not implemented for Windows -bool -search_executable(const char *file); -#endif - -// return the absolute path of the executable (the scrcpy binary) -// may be NULL on error; to be freed by free() -char * -get_executable_path(void); - -// Return the absolute path of a file in the same directory as he executable. -// May be NULL on error. To be freed by free(). -char * -get_local_file_path(const char *name); - -// returns true if the file exists and is not a directory -bool -is_regular_file(const char *path); - ssize_t read_pipe(pipe_t pipe, char *data, size_t len); From fcc04f967b9463a949641d8d22f15959ff79f2f9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 16:21:07 +0100 Subject: [PATCH 0074/1133] Improve file API Prefix symbols and constants names and improve documentation. --- app/src/adb.c | 2 +- app/src/icon.c | 2 +- app/src/server.c | 4 ++-- app/src/sys/unix/file.c | 6 +++--- app/src/sys/win/file.c | 4 ++-- app/src/util/file.c | 10 +++++----- app/src/util/file.h | 41 ++++++++++++++++++++++++++++------------- 7 files changed, 42 insertions(+), 27 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index d127494a..1eb8d9ed 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -69,7 +69,7 @@ show_adb_installation_msg() { {"pacman", "pacman -S android-tools"}, }; for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { - if (search_executable(pkg_managers[i].binary)) { + if (sc_file_executable_exists(pkg_managers[i].binary)) { LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); return; } diff --git a/app/src/icon.c b/app/src/icon.c index 787d048f..f9799de7 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -46,7 +46,7 @@ get_icon_path(void) { return NULL; } #else - char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME); + char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME); if (!icon_path) { LOGE("Could not get icon path"); return NULL; diff --git a/app/src/server.c b/app/src/server.c index aba77288..25d786c4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -49,7 +49,7 @@ get_server_path(void) { return NULL; } #else - char *server_path = get_local_file_path(SERVER_FILENAME); + char *server_path = sc_file_get_local_path(SERVER_FILENAME); if (!server_path) { LOGE("Could not get local file path, " "using " SERVER_FILENAME " from current directory"); @@ -68,7 +68,7 @@ push_server(const char *serial) { if (!server_path) { return false; } - if (!is_regular_file(server_path)) { + if (!sc_file_is_regular(server_path)) { LOGE("'%s' does not exist or is not a regular file\n", server_path); free(server_path); return false; diff --git a/app/src/sys/unix/file.c b/app/src/sys/unix/file.c index 2e71b113..4e9e45b3 100644 --- a/app/src/sys/unix/file.c +++ b/app/src/sys/unix/file.c @@ -7,7 +7,7 @@ #include bool -search_executable(const char *file) { +sc_file_executable_exists(const char *file) { char *path = getenv("PATH"); if (!path) return false; @@ -43,7 +43,7 @@ search_executable(const char *file) { } char * -get_executable_path(void) { +sc_file_get_executable_path(void) { // #ifdef __linux__ char buf[PATH_MAX + 1]; // +1 for the null byte @@ -63,7 +63,7 @@ get_executable_path(void) { } bool -is_regular_file(const char *path) { +sc_file_is_regular(const char *path) { struct stat path_stat; if (stat(path, &path_stat)) { diff --git a/app/src/sys/win/file.c b/app/src/sys/win/file.c index 0f9101e9..badb8087 100644 --- a/app/src/sys/win/file.c +++ b/app/src/sys/win/file.c @@ -8,7 +8,7 @@ #include "util/str_util.h" char * -get_executable_path(void) { +sc_file_get_executable_path(void) { HMODULE hModule = GetModuleHandleW(NULL); if (!hModule) { return NULL; @@ -23,7 +23,7 @@ get_executable_path(void) { } bool -is_regular_file(const char *path) { +sc_file_is_regular(const char *path) { wchar_t *wide_path = utf8_to_wide_char(path); if (!wide_path) { LOGC("Could not allocate wide char string"); diff --git a/app/src/util/file.c b/app/src/util/file.c index bb7fdb2c..59be2d91 100644 --- a/app/src/util/file.c +++ b/app/src/util/file.c @@ -6,8 +6,8 @@ #include "util/log.h" char * -get_local_file_path(const char *name) { - char *executable_path = get_executable_path(); +sc_file_get_local_path(const char *name) { + char *executable_path = sc_file_get_executable_path(); if (!executable_path) { return NULL; } @@ -15,10 +15,10 @@ get_local_file_path(const char *name) { // dirname() does not work correctly everywhere, so get the parent // directory manually. // See - char *p = strrchr(executable_path, PATH_SEPARATOR); + char *p = strrchr(executable_path, SC_PATH_SEPARATOR); if (!p) { LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')", - executable_path, PATH_SEPARATOR); + executable_path, SC_PATH_SEPARATOR); free(executable_path); return NULL; } @@ -37,7 +37,7 @@ get_local_file_path(const char *name) { } memcpy(file_path, dir, dirlen); - file_path[dirlen] = PATH_SEPARATOR; + file_path[dirlen] = SC_PATH_SEPARATOR; // namelen + 1 to copy the final '\0' memcpy(&file_path[dirlen + 1], name, namelen + 1); diff --git a/app/src/util/file.h b/app/src/util/file.h index 813af486..089f6f75 100644 --- a/app/src/util/file.h +++ b/app/src/util/file.h @@ -6,29 +6,44 @@ #include #ifdef _WIN32 -# define PATH_SEPARATOR '\\' +# define SC_PATH_SEPARATOR '\\' #else -# define PATH_SEPARATOR '/' +# define SC_PATH_SEPARATOR '/' #endif #ifndef _WIN32 -// only used to find package manager, not implemented for Windows +/** + * Indicate if an executable exists using $PATH + * + * In practice, it is only used to know if a package manager is available on + * the system. It is only implemented on Linux. + */ bool -search_executable(const char *file); +sc_file_executable_exists(const char *file); #endif -// return the absolute path of the executable (the scrcpy binary) -// may be NULL on error; to be freed by free() +/** + * Return the absolute path of the executable (the scrcpy binary) + * + * The result must be freed by the caller using free(). It may return NULL on + * error. + */ char * -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(). +sc_file_get_executable_path(void); + +/** + * Return the absolute path of a file in the same directory as the executable + * + * The result must be freed by the caller using free(). It may return NULL on + * error. + */ char * -get_local_file_path(const char *name); +sc_file_get_local_path(const char *name); -// returns true if the file exists and is not a directory +/** + * Indicate if the file exists and is not a directory + */ bool -is_regular_file(const char *path); +sc_file_is_regular(const char *path); #endif From e80e6631e4c259e2e217f8425900b56002850a40 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 17:29:36 +0100 Subject: [PATCH 0075/1133] Remove duplicate function declaration The function process_terminate() was declared twice. --- app/src/util/process.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/util/process.h b/app/src/util/process.h index d609ae71..08a70657 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -47,9 +47,6 @@ process_execute_redirect(const char *const argv[], process_t *process, pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr); -bool -process_terminate(process_t pid); - // kill the process bool process_terminate(process_t pid); From 7e93abcf6d8982f194e016b3ea0227c1834893fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 17:49:47 +0100 Subject: [PATCH 0076/1133] Factorize common impl of process_execute() Both implementations are the same. Move them to the common process.c. --- app/src/sys/unix/process.c | 5 ----- app/src/sys/win/process.c | 5 ----- app/src/util/process.c | 5 +++++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index e534ae9d..ef5d9d79 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -153,11 +153,6 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, return res; } -enum process_result -process_execute(const char *const argv[], pid_t *pid) { - return process_execute_redirect(argv, pid, NULL, NULL, NULL); -} - bool process_terminate(pid_t pid) { if (pid <= 0) { diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 289d1fca..257427f3 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -142,11 +142,6 @@ error_close_stdin: return ret; } -enum process_result -process_execute(const char *const argv[], HANDLE *handle) { - return process_execute_redirect(argv, handle, NULL, NULL, NULL); -} - bool process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); diff --git a/app/src/util/process.c b/app/src/util/process.c index 637132d9..dcb715e6 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -3,6 +3,11 @@ #include #include "log.h" +enum process_result +process_execute(const char *const argv[], process_t *pid) { + return process_execute_redirect(argv, pid, NULL, NULL, NULL); +} + bool process_check_success(process_t proc, const char *name, bool close) { if (proc == PROCESS_NONE) { From aa011832c155ad57230e8241932c3331218c95b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 11 Nov 2021 17:48:41 +0100 Subject: [PATCH 0077/1133] Improve process API Prefix symbols and constants names and improve documentation. --- app/src/adb.c | 71 +++++++++++----------- app/src/adb.h | 21 +++---- app/src/file_handler.c | 32 +++++----- app/src/file_handler.h | 2 +- app/src/server.c | 38 ++++++------ app/src/server.h | 2 +- app/src/sys/unix/process.c | 80 ++++++++++++------------ app/src/sys/win/process.c | 75 ++++++++++++----------- app/src/util/process.c | 21 ++++--- app/src/util/process.h | 121 +++++++++++++++++++++++-------------- 10 files changed, 244 insertions(+), 219 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 1eb8d9ed..c7a64501 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -81,7 +81,7 @@ show_adb_installation_msg() { } static void -show_adb_err_msg(enum process_result err, const char *const argv[]) { +show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { #define MAX_COMMAND_STRING_LEN 1024 char *buf = malloc(MAX_COMMAND_STRING_LEN); if (!buf) { @@ -90,18 +90,18 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { } switch (err) { - case PROCESS_ERROR_GENERIC: + case SC_PROCESS_ERROR_GENERIC: argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); LOGE("Failed to execute: %s", buf); break; - case PROCESS_ERROR_MISSING_BINARY: + case SC_PROCESS_ERROR_MISSING_BINARY: argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); LOGE("Command not found: %s", buf); LOGE("(make 'adb' accessible from your PATH or define its full" "path in the ADB environment variable)"); show_adb_installation_msg(); break; - case PROCESS_SUCCESS: + case SC_PROCESS_SUCCESS: // do nothing break; } @@ -109,16 +109,15 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) { free(buf); } -process_t -adb_execute_redirect(const char *serial, const char *const adb_cmd[], - size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout, - pipe_t *pipe_stderr) { +sc_pid +adb_execute_p(const char *serial, const char *const adb_cmd[], + size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) { int i; - process_t process; + sc_pid pid; const char **argv = malloc((len + 4) * sizeof(*argv)); if (!argv) { - return PROCESS_NONE; + return SC_PROCESS_NONE; } argv[0] = get_adb_command(); @@ -132,24 +131,23 @@ adb_execute_redirect(const char *serial, const char *const adb_cmd[], memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); argv[len + i] = NULL; - enum process_result r = - process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout, - pipe_stderr); - if (r != PROCESS_SUCCESS) { + enum sc_process_result r = + sc_process_execute_p(argv, &pid, pin, pout, perr); + if (r != SC_PROCESS_SUCCESS) { show_adb_err_msg(r, argv); - process = PROCESS_NONE; + pid = SC_PROCESS_NONE; } free(argv); - return process; + return pid; } -process_t +sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { - return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL); + return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL); } -process_t +sc_pid adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { char local[4 + 5 + 1]; // tcp:PORT @@ -160,7 +158,7 @@ adb_forward(const char *serial, uint16_t local_port, return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t +sc_pid adb_forward_remove(const char *serial, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); @@ -168,7 +166,7 @@ adb_forward_remove(const char *serial, uint16_t local_port) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t +sc_pid adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT @@ -179,7 +177,7 @@ adb_reverse(const char *serial, const char *device_socket_name, return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t +sc_pid adb_reverse_remove(const char *serial, const char *device_socket_name) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); @@ -187,66 +185,65 @@ adb_reverse_remove(const char *serial, const char *device_socket_name) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -process_t +sc_pid adb_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) local = strquote(local); if (!local) { - return PROCESS_NONE; + return SC_PROCESS_NONE; } remote = strquote(remote); if (!remote) { free((void *) local); - return PROCESS_NONE; + return SC_PROCESS_NONE; } #endif const char *const adb_cmd[] = {"push", local, remote}; - process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); #ifdef __WINDOWS__ free((void *) remote); free((void *) local); #endif - return proc; + return pid; } -process_t +sc_pid adb_install(const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) local = strquote(local); if (!local) { - return PROCESS_NONE; + return SC_PROCESS_NONE; } #endif const char *const adb_cmd[] = {"install", "-r", local}; - process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); #ifdef __WINDOWS__ free((void *) local); #endif - return proc; + return pid; } static ssize_t adb_execute_for_output(const char *serial, const char *const adb_cmd[], size_t adb_cmd_len, char *buf, size_t buf_len, const char *name) { - pipe_t pipe_stdout; - process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL, - &pipe_stdout, NULL); + sc_pipe pout; + sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL); - ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len); - close_pipe(pipe_stdout); + ssize_t r = sc_pipe_read_all(pout, buf, buf_len); + sc_pipe_close(pout); - if (!process_check_success(proc, name, true)) { + if (!sc_process_check_success(pid, name, true)) { return -1; } diff --git a/app/src/adb.h b/app/src/adb.h index 34182fd3..085b3e6b 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -8,32 +8,31 @@ #include "util/process.h" -process_t +sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len); -process_t -adb_execute_redirect(const char *serial, const char *const adb_cmd[], - size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout, - pipe_t *pipe_stderr); +sc_pid +adb_execute_p(const char *serial, const char *const adb_cmd[], + size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr); -process_t +sc_pid adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name); -process_t +sc_pid adb_forward_remove(const char *serial, uint16_t local_port); -process_t +sc_pid adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port); -process_t +sc_pid adb_reverse_remove(const char *serial, const char *device_socket_name); -process_t +sc_pid adb_push(const char *serial, const char *local, const char *remote); -process_t +sc_pid adb_install(const char *serial, const char *local); // Return the result of "adb get-serialno". diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 27fe6fa3..fe0ab857 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -46,7 +46,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial, file_handler->initialized = false; file_handler->stopped = false; - file_handler->current_process = PROCESS_NONE; + file_handler->current_process = SC_PROCESS_NONE; file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; @@ -65,12 +65,12 @@ file_handler_destroy(struct file_handler *file_handler) { } } -static process_t +static sc_pid install_apk(const char *serial, const char *file) { return adb_install(serial, file); } -static process_t +static sc_pid push_file(const char *serial, const char *file, const char *push_target) { return adb_push(serial, file, push_target); } @@ -109,7 +109,7 @@ run_file_handler(void *data) { for (;;) { sc_mutex_lock(&file_handler->mutex); - file_handler->current_process = PROCESS_NONE; + file_handler->current_process = SC_PROCESS_NONE; while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); } @@ -123,26 +123,26 @@ run_file_handler(void *data) { assert(non_empty); (void) non_empty; - process_t process; + sc_pid pid; if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - process = install_apk(file_handler->serial, req.file); + pid = install_apk(file_handler->serial, req.file); } else { LOGI("Pushing %s...", req.file); - process = push_file(file_handler->serial, req.file, - file_handler->push_target); + pid = push_file(file_handler->serial, req.file, + file_handler->push_target); } - file_handler->current_process = process; + file_handler->current_process = pid; sc_mutex_unlock(&file_handler->mutex); if (req.action == ACTION_INSTALL_APK) { - if (process_check_success(process, "adb install", false)) { + if (sc_process_check_success(pid, "adb install", false)) { LOGI("%s successfully installed", req.file); } else { LOGE("Failed to install %s", req.file); } } else { - if (process_check_success(process, "adb push", false)) { + if (sc_process_check_success(pid, "adb push", false)) { LOGI("%s successfully pushed to %s", req.file, file_handler->push_target); } else { @@ -152,11 +152,11 @@ run_file_handler(void *data) { } sc_mutex_lock(&file_handler->mutex); - // Close the process (it is necessary already terminated) + // Close the process (it is necessarily already terminated) // Execute this call with mutex locked to avoid race conditions with // file_handler_stop() - process_close(file_handler->current_process); - file_handler->current_process = PROCESS_NONE; + sc_process_close(file_handler->current_process); + file_handler->current_process = SC_PROCESS_NONE; sc_mutex_unlock(&file_handler->mutex); file_handler_request_destroy(&req); @@ -183,8 +183,8 @@ file_handler_stop(struct file_handler *file_handler) { sc_mutex_lock(&file_handler->mutex); file_handler->stopped = true; sc_cond_signal(&file_handler->event_cond); - if (file_handler->current_process != PROCESS_NONE) { - if (!process_terminate(file_handler->current_process)) { + if (file_handler->current_process != SC_PROCESS_NONE) { + if (!sc_process_terminate(file_handler->current_process)) { LOGW("Could not terminate push/install process"); } } diff --git a/app/src/file_handler.h b/app/src/file_handler.h index fe1d1804..e2067533 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -29,7 +29,7 @@ struct file_handler { sc_cond event_cond; bool stopped; bool initialized; - process_t current_process; + sc_pid current_process; struct file_handler_request_queue queue; }; diff --git a/app/src/server.c b/app/src/server.c index 25d786c4..326d0e79 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -73,33 +73,33 @@ push_server(const char *serial) { free(server_path); return false; } - process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); + sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH); free(server_path); - return process_check_success(process, "adb push", true); + return sc_process_check_success(pid, "adb push", true); } static bool enable_tunnel_reverse(const char *serial, uint16_t local_port) { - process_t process = adb_reverse(serial, SOCKET_NAME, local_port); - return process_check_success(process, "adb reverse", true); + sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port); + return sc_process_check_success(pid, "adb reverse", true); } static bool disable_tunnel_reverse(const char *serial) { - process_t process = adb_reverse_remove(serial, SOCKET_NAME); - return process_check_success(process, "adb reverse --remove", true); + sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME); + return sc_process_check_success(pid, "adb reverse --remove", true); } static bool enable_tunnel_forward(const char *serial, uint16_t local_port) { - process_t process = adb_forward(serial, local_port, SOCKET_NAME); - return process_check_success(process, "adb forward", true); + sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME); + return sc_process_check_success(pid, "adb forward", true); } static bool disable_tunnel_forward(const char *serial, uint16_t local_port) { - process_t process = adb_forward_remove(serial, local_port); - return process_check_success(process, "adb forward --remove", true); + sc_pid pid = adb_forward_remove(serial, local_port); + return sc_process_check_success(pid, "adb forward --remove", true); } static bool @@ -228,7 +228,7 @@ log_level_to_server_string(enum sc_log_level level) { } } -static process_t +static sc_pid execute_server(struct server *server, const struct server_params *params) { char max_size_string[6]; char bit_rate_string[11]; @@ -327,7 +327,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { bool server_init(struct server *server) { server->serial = NULL; - server->process = PROCESS_NONE; + server->process = SC_PROCESS_NONE; bool ok = sc_mutex_init(&server->mutex); if (!ok) { @@ -357,7 +357,7 @@ server_init(struct server *server) { static int run_wait_server(void *data) { struct server *server = data; - process_wait(server->process, false); // ignore exit code + sc_process_wait(server->process, false); // ignore exit code sc_mutex_lock(&server->mutex); server->process_terminated = true; @@ -396,7 +396,7 @@ server_start(struct server *server, const struct server_params *params) { // server will connect to our server socket server->process = execute_server(server, params); - if (server->process == PROCESS_NONE) { + if (server->process == SC_PROCESS_NONE) { goto error; } @@ -409,8 +409,8 @@ server_start(struct server *server, const struct server_params *params) { 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 + sc_process_terminate(server->process); + sc_process_wait(server->process, true); // ignore exit code goto error; } @@ -508,7 +508,7 @@ server_stop(struct server *server) { } } - assert(server->process != PROCESS_NONE); + assert(server->process != SC_PROCESS_NONE); if (server->tunnel_enabled) { // ignore failure @@ -533,11 +533,11 @@ server_stop(struct server *server) { // The process is terminated, but not reaped (closed) yet, so its PID // is still valid. LOGW("Killing the server..."); - process_terminate(server->process); + sc_process_terminate(server->process); } sc_thread_join(&server->wait_server_thread, NULL); - process_close(server->process); + sc_process_close(server->process); } void diff --git a/app/src/server.h b/app/src/server.h index 242b0525..18721cf7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -22,7 +22,7 @@ struct server_info { struct server { char *serial; - process_t process; + sc_pid process; sc_thread wait_server_thread; sc_mutex mutex; diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index ef5d9d79..5f4a9890 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -10,9 +10,9 @@ #include "util/log.h" -enum process_result -process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, - int *pipe_stdout, int *pipe_stderr) { +enum sc_process_result +sc_process_execute_p(const char *const argv[], sc_pid *pid, + int *pin, int *pout, int *perr) { int in[2]; int out[2]; int err[2]; @@ -20,44 +20,44 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, if (pipe(internal) == -1) { perror("pipe"); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } - if (pipe_stdin) { + if (pin) { if (pipe(in) == -1) { perror("pipe"); close(internal[0]); close(internal[1]); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } } - if (pipe_stdout) { + if (pout) { if (pipe(out) == -1) { perror("pipe"); // clean up - if (pipe_stdin) { + if (pin) { close(in[0]); close(in[1]); } close(internal[0]); close(internal[1]); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } } - if (pipe_stderr) { + if (perr) { if (pipe(err) == -1) { perror("pipe"); // clean up - if (pipe_stdout) { + if (pout) { close(out[0]); close(out[1]); } - if (pipe_stdin) { + if (pin) { close(in[0]); close(in[1]); } close(internal[0]); close(internal[1]); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } } @@ -65,39 +65,39 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, if (*pid == -1) { perror("fork"); // clean up - if (pipe_stderr) { + if (perr) { close(err[0]); close(err[1]); } - if (pipe_stdout) { + if (pout) { close(out[0]); close(out[1]); } - if (pipe_stdin) { + if (pin) { close(in[0]); close(in[1]); } close(internal[0]); close(internal[1]); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } if (*pid == 0) { - if (pipe_stdin) { + if (pin) { if (in[0] != STDIN_FILENO) { dup2(in[0], STDIN_FILENO); close(in[0]); } close(in[1]); } - if (pipe_stdout) { + if (pout) { if (out[1] != STDOUT_FILENO) { dup2(out[1], STDOUT_FILENO); close(out[1]); } close(out[0]); } - if (pipe_stderr) { + if (perr) { if (err[1] != STDERR_FILENO) { dup2(err[1], STDERR_FILENO); close(err[1]); @@ -105,15 +105,15 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, close(err[0]); } close(internal[0]); - enum process_result err; + enum sc_process_result err; if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { execvp(argv[0], (char *const *) argv); perror("exec"); - err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY - : PROCESS_ERROR_GENERIC; + err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY + : SC_PROCESS_ERROR_GENERIC; } else { perror("fcntl"); - err = PROCESS_ERROR_GENERIC; + err = SC_PROCESS_ERROR_GENERIC; } // send err to the parent if (write(internal[1], &err, sizeof(err)) == -1) { @@ -128,25 +128,25 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, close(internal[1]); - enum process_result res = PROCESS_SUCCESS; + enum sc_process_result res = SC_PROCESS_SUCCESS; // wait for EOF or receive err from child if (read(internal[0], &res, sizeof(res)) == -1) { perror("read"); - res = PROCESS_ERROR_GENERIC; + res = SC_PROCESS_ERROR_GENERIC; } close(internal[0]); - if (pipe_stdin) { + if (pin) { close(in[0]); - *pipe_stdin = in[1]; + *pin = in[1]; } - if (pipe_stdout) { - *pipe_stdout = out[0]; + if (pout) { + *pout = out[0]; close(out[1]); } - if (pipe_stderr) { - *pipe_stderr = err[0]; + if (perr) { + *perr = err[0]; close(err[1]); } @@ -154,7 +154,7 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin, } bool -process_terminate(pid_t pid) { +sc_process_terminate(pid_t pid) { if (pid <= 0) { LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); @@ -163,8 +163,8 @@ process_terminate(pid_t pid) { return kill(pid, SIGKILL) != -1; } -exit_code_t -process_wait(pid_t pid, bool close) { +sc_exit_code +sc_process_wait(pid_t pid, bool close) { int code; int options = WEXITED; if (!close) { @@ -175,7 +175,7 @@ process_wait(pid_t pid, bool close) { int r = waitid(P_PID, pid, &info, options); if (r == -1 || info.si_code != CLD_EXITED) { // could not wait, or exited unexpectedly, probably by a signal - code = NO_EXIT_CODE; + code = SC_EXIT_CODE_NONE; } else { code = info.si_status; } @@ -183,17 +183,17 @@ process_wait(pid_t pid, bool close) { } void -process_close(pid_t pid) { - process_wait(pid, true); // ignore exit code +sc_process_close(pid_t pid) { + sc_process_wait(pid, true); // ignore exit code } ssize_t -read_pipe(int pipe, char *data, size_t len) { +sc_pipe_read(int pipe, char *data, size_t len) { return read(pipe, data, len); } void -close_pipe(int pipe) { +sc_pipe_close(int pipe) { if (close(pipe)) { perror("close pipe"); } diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 257427f3..971806d6 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -15,17 +15,16 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { // (don't handle escaping nor quotes) size_t ret = xstrjoin(cmd, argv, ' ', len); if (ret >= len) { - LOGE("Command too long (%" PRIsizet " chars)", len - 1); + LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1); return false; } return true; } -enum process_result -process_execute_redirect(const char *const argv[], HANDLE *handle, - HANDLE *pipe_stdin, HANDLE *pipe_stdout, - HANDLE *pipe_stderr) { - enum process_result ret = PROCESS_ERROR_GENERIC; +enum sc_process_result +sc_process_execute_p(const char *const argv[], HANDLE *handle, + HANDLE *pin, HANDLE *pout, HANDLE *perr) { + enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); @@ -35,32 +34,32 @@ process_execute_redirect(const char *const argv[], HANDLE *handle, HANDLE stdin_read_handle; HANDLE stdout_write_handle; HANDLE stderr_write_handle; - if (pipe_stdin) { - if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) { + if (pin) { + if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) { perror("pipe"); - return PROCESS_ERROR_GENERIC; + return SC_PROCESS_ERROR_GENERIC; } - if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) { + if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) { LOGE("SetHandleInformation stdin failed"); goto error_close_stdin; } } - if (pipe_stdout) { - if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) { + if (pout) { + if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) { perror("pipe"); goto error_close_stdin; } - if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) { + if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) { LOGE("SetHandleInformation stdout failed"); goto error_close_stdout; } } - if (pipe_stderr) { - if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) { + if (perr) { + if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) { perror("pipe"); goto error_close_stdout; } - if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) { + if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) { LOGE("SetHandleInformation stderr failed"); goto error_close_stderr; } @@ -70,15 +69,15 @@ process_execute_redirect(const char *const argv[], HANDLE *handle, PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); - if (pipe_stdin || pipe_stdout || pipe_stderr) { + if (pin || pout || perr) { si.dwFlags = STARTF_USESTDHANDLES; - if (pipe_stdin) { + if (pin) { si.hStdInput = stdin_read_handle; } - if (pipe_stdout) { + if (pout) { si.hStdOutput = stdout_write_handle; } - if (pipe_stderr) { + if (perr) { si.hStdError = stderr_write_handle; } } @@ -102,40 +101,40 @@ process_execute_redirect(const char *const argv[], HANDLE *handle, *handle = NULL; if (GetLastError() == ERROR_FILE_NOT_FOUND) { - ret = PROCESS_ERROR_MISSING_BINARY; + ret = SC_PROCESS_ERROR_MISSING_BINARY; } goto error_close_stderr; } // These handles are used by the child process, close them for this process - if (pipe_stdin) { + if (pin) { CloseHandle(stdin_read_handle); } - if (pipe_stdout) { + if (pout) { CloseHandle(stdout_write_handle); } - if (pipe_stderr) { + if (perr) { CloseHandle(stderr_write_handle); } free(wide); *handle = pi.hProcess; - return PROCESS_SUCCESS; + return SC_PROCESS_SUCCESS; error_close_stderr: - if (pipe_stderr) { - CloseHandle(*pipe_stderr); + if (perr) { + CloseHandle(*perr); CloseHandle(stderr_write_handle); } error_close_stdout: - if (pipe_stdout) { - CloseHandle(*pipe_stdout); + if (pout) { + CloseHandle(*pout); CloseHandle(stdout_write_handle); } error_close_stdin: - if (pipe_stdin) { - CloseHandle(*pipe_stdin); + if (pin) { + CloseHandle(*pin); CloseHandle(stdin_read_handle); } @@ -143,17 +142,17 @@ error_close_stdin: } bool -process_terminate(HANDLE handle) { +sc_process_terminate(HANDLE handle) { return TerminateProcess(handle, 1); } -exit_code_t -process_wait(HANDLE handle, bool close) { +sc_exit_code +sc_process_wait(HANDLE handle, bool close) { DWORD code; if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) { // could not wait or retrieve the exit code - code = NO_EXIT_CODE; // max value, it's unsigned + code = SC_EXIT_CODE_NONE; } if (close) { CloseHandle(handle); @@ -162,14 +161,14 @@ process_wait(HANDLE handle, bool close) { } void -process_close(HANDLE handle) { +sc_process_close(HANDLE handle) { bool closed = CloseHandle(handle); assert(closed); (void) closed; } ssize_t -read_pipe(HANDLE pipe, char *data, size_t len) { +sc_read_pipe(HANDLE pipe, char *data, size_t len) { DWORD r; if (!ReadFile(pipe, data, len, &r, NULL)) { return -1; @@ -178,7 +177,7 @@ read_pipe(HANDLE pipe, char *data, size_t len) { } void -close_pipe(HANDLE pipe) { +sc_close_pipe(HANDLE pipe) { if (!CloseHandle(pipe)) { LOGW("Cannot close pipe"); } diff --git a/app/src/util/process.c b/app/src/util/process.c index dcb715e6..6743438e 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -3,21 +3,22 @@ #include #include "log.h" -enum process_result -process_execute(const char *const argv[], process_t *pid) { - return process_execute_redirect(argv, pid, NULL, NULL, NULL); +enum sc_process_result +sc_process_execute(const char *const argv[], sc_pid *pid) { + return sc_process_execute_p(argv, pid, NULL, NULL, NULL); } bool -process_check_success(process_t proc, const char *name, bool close) { - if (proc == PROCESS_NONE) { +sc_process_check_success(sc_pid pid, const char *name, bool close) { + if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"%s\"", name); return false; } - exit_code_t exit_code = process_wait(proc, close); + sc_exit_code exit_code = sc_process_wait(pid, close); if (exit_code) { - if (exit_code != NO_EXIT_CODE) { - LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); + if (exit_code != SC_EXIT_CODE_NONE) { + LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, + exit_code); } else { LOGE("\"%s\" exited unexpectedly", name); } @@ -27,10 +28,10 @@ process_check_success(process_t proc, const char *name, bool close) { } ssize_t -read_pipe_all(pipe_t pipe, char *data, size_t len) { +sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { size_t copied = 0; while (len > 0) { - ssize_t r = read_pipe(pipe, data, len); + ssize_t r = sc_pipe_read(pipe, data, len); if (r <= 0) { return copied ? (ssize_t) copied : r; } diff --git a/app/src/util/process.h b/app/src/util/process.h index 08a70657..b4980b43 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -10,71 +10,100 @@ // not needed here, but winsock2.h must never be included AFTER windows.h # include # include -# define PRIexitcode "lu" +# define SC_PRIexitcode "lu" // -# define PRIsizet "Iu" -# define PROCESS_NONE NULL -# define NO_EXIT_CODE -1u // max value as unsigned - typedef HANDLE process_t; - typedef DWORD exit_code_t; - typedef HANDLE pipe_t; +# define SC_PRIsizet "Iu" +# define SC_PROCESS_NONE NULL +# define SC_EXIT_CODE_NONE -1u // max value as unsigned + typedef HANDLE sc_pid; + typedef DWORD sc_exit_code; + typedef HANDLE sc_pipe; #else # include -# define PRIsizet "zu" -# define PRIexitcode "d" -# define PROCESS_NONE -1 -# define NO_EXIT_CODE -1 - typedef pid_t process_t; - typedef int exit_code_t; - typedef int pipe_t; +# define SC_PRIsizet "zu" +# define SC_PRIexitcode "d" +# define SC_PROCESS_NONE -1 +# define SC_EXIT_CODE_NONE -1 + typedef pid_t sc_pid; + typedef int sc_exit_code; + typedef int sc_pipe; #endif -enum process_result { - PROCESS_SUCCESS, - PROCESS_ERROR_GENERIC, - PROCESS_ERROR_MISSING_BINARY, +enum sc_process_result { + SC_PROCESS_SUCCESS, + SC_PROCESS_ERROR_GENERIC, + SC_PROCESS_ERROR_MISSING_BINARY, }; -// execute the command and write the result to the output parameter "process" -enum process_result -process_execute(const char *const argv[], process_t *process); - -enum process_result -process_execute_redirect(const char *const argv[], process_t *process, - pipe_t *pipe_stdin, pipe_t *pipe_stdout, - pipe_t *pipe_stderr); - -// kill the process +/** + * Execute the command and write the process id to `pid` + */ +enum sc_process_result +sc_process_execute(const char *const argv[], sc_pid *pid); + +/** + * Execute the command and write the process id to `pid` + * + * If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr + * (`perr`). + */ +enum sc_process_result +sc_process_execute_p(const char *const argv[], sc_pid *pid, + sc_pipe *pin, sc_pipe *pout, sc_pipe *perr); + +/** + * Kill the process + */ bool -process_terminate(process_t pid); - -// wait and close the process (like waitpid()) -// the "close" flag indicates if the process must be "closed" (reaped) -// (passing false is equivalent to enable WNOWAIT in waitid()) -exit_code_t -process_wait(process_t pid, bool close); - -// close the process -// -// Semantically, process_wait(close) = process_wait(noclose) + process_close +sc_process_terminate(sc_pid pid); + +/** + * Wait and close the process (similar to waitpid()) + * + * The `close` flag indicates if the process must be _closed_ (reaped) (passing + * false is equivalent to enable WNOWAIT in waitid()). + */ +sc_exit_code +sc_process_wait(sc_pid pid, bool close); + +/** + * Close (reap) the process + * + * Semantically: + * sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close() + */ void -process_close(process_t pid); +sc_process_close(sc_pid pid); -// convenience function to wait for a successful process execution -// automatically log process errors with the provided process name +/** + * Convenience function to wait for a successful process execution + * + * Automatically log process errors with the provided process name. + */ bool -process_check_success(process_t proc, const char *name, bool close); +sc_process_check_success(sc_pid pid, const char *name, bool close); +/** + * Read from the pipe + * + * Same semantic as read(). + */ ssize_t -read_pipe(pipe_t pipe, char *data, size_t len); +sc_pipe_read(sc_pipe pipe, char *data, size_t len); +/** + * Read exactly `len` chars from a pipe (unless EOF) + */ ssize_t -read_pipe_all(pipe_t pipe, char *data, size_t len); +sc_pipe_read_all(sc_pipe pipe, char *data, size_t len); +/** + * Close the pipe + */ void -close_pipe(pipe_t pipe); +sc_pipe_close(sc_pipe pipe); #endif From 03de9224fc45fdccf772b975a63851284a4579b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 09:49:37 +0100 Subject: [PATCH 0078/1133] Introduce process observer Add a tool to easily observe process termination. This allows to move this complexity out of the server code. --- app/src/server.c | 71 +++++++++++++------------------------- app/src/server.h | 8 ++--- app/src/util/process.c | 78 ++++++++++++++++++++++++++++++++++++++++++ app/src/util/process.h | 67 ++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 53 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 326d0e79..f108c7cc 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -329,19 +329,6 @@ server_init(struct server *server) { server->serial = NULL; server->process = SC_PROCESS_NONE; - bool ok = sc_mutex_init(&server->mutex); - if (!ok) { - return false; - } - - ok = sc_cond_init(&server->process_terminated_cond); - if (!ok) { - sc_mutex_destroy(&server->mutex); - return false; - } - - server->process_terminated = false; - server->server_socket = SC_INVALID_SOCKET; server->video_socket = SC_INVALID_SOCKET; server->control_socket = SC_INVALID_SOCKET; @@ -354,25 +341,20 @@ server_init(struct server *server) { return true; } -static int -run_wait_server(void *data) { - struct server *server = data; - sc_process_wait(server->process, false); // ignore exit code - - sc_mutex_lock(&server->mutex); - server->process_terminated = true; - sc_cond_signal(&server->process_terminated_cond); - sc_mutex_unlock(&server->mutex); +static void +server_on_terminated(void *userdata) { + struct server *server = userdata; - // no need for synchronization, server_socket is initialized before this - // thread was created + // No need for synchronization, server_socket is initialized before the + // observer thread is created. if (server->server_socket != SC_INVALID_SOCKET) { - // Unblock any accept() + // If the server process dies before connecting to the server socket, + // then the client will be stuck forever on accept(). To avoid the + // problem, wake up the accept() call when the server dies. net_interrupt(server->server_socket); } LOGD("Server terminated"); - return 0; } bool @@ -400,14 +382,11 @@ server_start(struct server *server, const struct server_params *params) { 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); + static const struct sc_process_listener listener = { + .on_terminated = server_on_terminated, + }; + bool ok = sc_process_observer_init(&server->observer, server->process, + &listener, server); if (!ok) { sc_process_terminate(server->process); sc_process_wait(server->process, true); // ignore exit code @@ -516,27 +495,25 @@ server_stop(struct server *server) { } // Give some delay for the server to terminate properly - sc_mutex_lock(&server->mutex); - bool signaled = false; - if (!server->process_terminated) { #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) - signaled = sc_cond_timedwait(&server->process_terminated_cond, - &server->mutex, - sc_tick_now() + WATCHDOG_DELAY); - } - sc_mutex_unlock(&server->mutex); + sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; + bool terminated = + sc_process_observer_timedwait(&server->observer, deadline); // After this delay, kill the server if it's not dead already. // On some devices, closing the sockets is not sufficient to wake up the // blocking calls while the device is asleep. - if (!signaled) { - // The process is terminated, but not reaped (closed) yet, so its PID - // is still valid. + if (!terminated) { + // The process may have terminated since the check, but it is not + // reaped (closed) yet, so its PID is still valid, and it is ok to call + // sc_process_terminate() even in that case. LOGW("Killing the server..."); sc_process_terminate(server->process); } - sc_thread_join(&server->wait_server_thread, NULL); + sc_process_observer_join(&server->observer); + sc_process_observer_destroy(&server->observer); + sc_process_close(server->process); } @@ -558,6 +535,4 @@ server_destroy(struct server *server) { } } free(server->serial); - sc_cond_destroy(&server->process_terminated_cond); - sc_mutex_destroy(&server->mutex); } diff --git a/app/src/server.h b/app/src/server.h index 18721cf7..c06b3562 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -22,12 +22,10 @@ struct server_info { struct server { char *serial; - sc_pid process; - sc_thread wait_server_thread; - sc_mutex mutex; - sc_cond process_terminated_cond; - bool process_terminated; + sc_pid process; + // alive only between start() and stop() + struct sc_process_observer observer; sc_socket server_socket; // only used if !tunnel_forward sc_socket video_socket; diff --git a/app/src/util/process.c b/app/src/util/process.c index 6743438e..28f51edd 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -1,5 +1,6 @@ #include "process.h" +#include #include #include "log.h" @@ -41,3 +42,80 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { } return copied; } + +static int +run_observer(void *data) { + struct sc_process_observer *observer = data; + sc_process_wait(observer->pid, false); // ignore exit code + + sc_mutex_lock(&observer->mutex); + observer->terminated = true; + sc_cond_signal(&observer->cond_terminated); + sc_mutex_unlock(&observer->mutex); + + if (observer->listener) { + observer->listener->on_terminated(observer->listener_userdata); + } + + return 0; +} + +bool +sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid, + const struct sc_process_listener *listener, + void *listener_userdata) { + // Either no listener, or on_terminated() is defined + assert(!listener || listener->on_terminated); + + bool ok = sc_mutex_init(&observer->mutex); + if (!ok) { + return false; + } + + ok = sc_cond_init(&observer->cond_terminated); + if (!ok) { + sc_mutex_destroy(&observer->mutex); + return false; + } + + observer->pid = pid; + observer->listener = listener; + observer->listener_userdata = listener_userdata; + observer->terminated = false; + + ok = sc_thread_create(&observer->thread, run_observer, "process_observer", + observer); + if (!ok) { + sc_cond_destroy(&observer->cond_terminated); + sc_mutex_destroy(&observer->mutex); + return false; + } + + return true; +} + +bool +sc_process_observer_timedwait(struct sc_process_observer *observer, + sc_tick deadline) { + sc_mutex_lock(&observer->mutex); + bool timed_out = false; + while (!observer->terminated && !timed_out) { + timed_out = !sc_cond_timedwait(&observer->cond_terminated, + &observer->mutex, deadline); + } + bool terminated = observer->terminated; + sc_mutex_unlock(&observer->mutex); + + return terminated; +} + +void +sc_process_observer_join(struct sc_process_observer *observer) { + sc_thread_join(&observer->thread, NULL); +} + +void +sc_process_observer_destroy(struct sc_process_observer *observer) { + sc_cond_destroy(&observer->cond_terminated); + sc_mutex_destroy(&observer->mutex); +} diff --git a/app/src/util/process.h b/app/src/util/process.h index b4980b43..7964be5c 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -4,6 +4,7 @@ #include "common.h" #include +#include "util/thread.h" #ifdef _WIN32 @@ -32,6 +33,34 @@ #endif +struct sc_process_listener { + void (*on_terminated)(void *userdata); +}; + +/** + * Tool to observe process termination + * + * To keep things simple and multiplatform, it runs a separate thread to wait + * for process termination (without closing the process to avoid race + * conditions). + * + * It allows a caller to block until the process is terminated (with a + * timeout), and to be notified asynchronously from the observer thread. + * + * The process is not owned by the observer (the observer will never close it). + */ +struct sc_process_observer { + sc_pid pid; + + sc_mutex mutex; + sc_cond cond_terminated; + bool terminated; + + sc_thread thread; + const struct sc_process_listener *listener; + void *listener_userdata; +}; + enum sc_process_result { SC_PROCESS_SUCCESS, SC_PROCESS_ERROR_GENERIC, @@ -106,4 +135,42 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len); void sc_pipe_close(sc_pipe pipe); +/** + * Start observing process + * + * The listener is optional. If set, its callback will be called from the + * observer thread once the process is terminated. + */ +bool +sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid, + const struct sc_process_listener *listener, + void *listener_userdata); + +/** + * Wait for process termination until a deadline + * + * Return true if the process is already terminated. Return false if the + * process terminatation has not been detected yet (however, it may have + * terminated in the meantime). + * + * To wait without timeout/deadline, just use sc_process_wait() instead. + */ +bool +sc_process_observer_timedwait(struct sc_process_observer *observer, + sc_tick deadline); + +/** + * Join the observer thread + */ +void +sc_process_observer_join(struct sc_process_observer *observer); + +/** + * Destroy the observer + * + * This does not close the associated process. + */ +void +sc_process_observer_destroy(struct sc_process_observer *observer); + #endif From e69356c5506667135a6479758b594863c8ef15f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Nov 2021 11:08:01 +0100 Subject: [PATCH 0079/1133] Initialize tunnel_enabled flag internally Set the flag from the inner methods, to avoid bypassing the assignment by mistake (on error for example). --- app/src/server.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index f108c7cc..e1cf5165 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -104,10 +104,16 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) { static bool disable_tunnel(struct server *server) { - if (server->tunnel_forward) { - return disable_tunnel_forward(server->serial, server->local_port); - } - return disable_tunnel_reverse(server->serial); + assert(server->tunnel_enabled); + + bool ok = server->tunnel_forward + ? disable_tunnel_forward(server->serial, server->local_port) + : disable_tunnel_reverse(server->serial); + + // Consider tunnel disabled even if the command failed + server->tunnel_enabled = false; + + return ok; } static sc_socket @@ -136,6 +142,7 @@ enable_tunnel_reverse_any_port(struct server *server, if (server->server_socket != SC_INVALID_SOCKET) { // success server->local_port = port; + server->tunnel_enabled = true; return true; } @@ -171,6 +178,7 @@ enable_tunnel_forward_any_port(struct server *server, if (enable_tunnel_forward(server->serial, port)) { // success server->local_port = port; + server->tunnel_enabled = true; return true; } @@ -393,8 +401,6 @@ server_start(struct server *server, const struct server_params *params) { goto error; } - server->tunnel_enabled = true; - return true; error: @@ -463,7 +469,6 @@ server_connect_to(struct server *server, struct server_info *info) { // we don't need the adb tunnel anymore disable_tunnel(server); // ignore failure - server->tunnel_enabled = false; // The sockets will be closed on stop if device_read_info() fails return device_read_info(server->video_socket, info); From ed19901db1d24d4791f5836a613505083a0d94d3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Nov 2021 11:44:58 +0100 Subject: [PATCH 0080/1133] Set video and control sockets only on success Store the video and control sockets only if server_connect_to() returns successfully. --- app/src/server.c | 64 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index e1cf5165..473bbeed 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -138,9 +138,10 @@ enable_tunnel_reverse_any_port(struct server *server, // client can listen before starting the server app, so there is no // need to try to connect until the server socket is listening on the // device. - server->server_socket = listen_on_port(port); - if (server->server_socket != SC_INVALID_SOCKET) { + sc_socket server_socket = listen_on_port(port); + if (server_socket != SC_INVALID_SOCKET) { // success + server->server_socket = server_socket; server->local_port = port; server->tunnel_enabled = true; return true; @@ -432,16 +433,19 @@ device_read_info(sc_socket device_socket, struct server_info *info) { bool server_connect_to(struct server *server, struct server_info *info) { + assert(server->tunnel_enabled); + + sc_socket video_socket = SC_INVALID_SOCKET; + sc_socket control_socket = SC_INVALID_SOCKET; if (!server->tunnel_forward) { - server->video_socket = net_accept(server->server_socket); - if (server->video_socket == SC_INVALID_SOCKET) { - return false; + video_socket = net_accept(server->server_socket); + if (video_socket == SC_INVALID_SOCKET) { + goto fail; } - server->control_socket = net_accept(server->server_socket); - if (server->control_socket == SC_INVALID_SOCKET) { - // the video_socket will be cleaned up on destroy - return false; + control_socket = net_accept(server->server_socket); + if (control_socket == SC_INVALID_SOCKET) { + goto fail; } // we don't need the server socket anymore @@ -453,17 +457,15 @@ server_connect_to(struct server *server, struct server_info *info) { } else { uint32_t attempts = 100; uint32_t delay = 100; // ms - server->video_socket = - connect_to_server(server->local_port, attempts, delay); - if (server->video_socket == SC_INVALID_SOCKET) { - return false; + video_socket = connect_to_server(server->local_port, attempts, delay); + if (video_socket == SC_INVALID_SOCKET) { + goto fail; } // we know that the device is listening, we don't need several attempts - server->control_socket = - net_connect(IPV4_LOCALHOST, server->local_port); - if (server->control_socket == SC_INVALID_SOCKET) { - return false; + control_socket = net_connect(IPV4_LOCALHOST, server->local_port); + if (control_socket == SC_INVALID_SOCKET) { + goto fail; } } @@ -471,7 +473,33 @@ server_connect_to(struct server *server, struct server_info *info) { disable_tunnel(server); // ignore failure // The sockets will be closed on stop if device_read_info() fails - return device_read_info(server->video_socket, info); + bool ok = device_read_info(video_socket, info); + if (!ok) { + goto fail; + } + + assert(video_socket != SC_INVALID_SOCKET); + assert(control_socket != SC_INVALID_SOCKET); + + server->video_socket = video_socket; + server->control_socket = control_socket; + + return true; + +fail: + if (video_socket != SC_INVALID_SOCKET) { + if (!net_close(video_socket)) { + LOGW("Could not close video socket"); + } + } + + if (control_socket != SC_INVALID_SOCKET) { + if (!net_close(control_socket)) { + LOGW("Could not close control socket"); + } + } + + return false; } void From 0bfa75a48b49d70bf0a50d48d5d1d63dd1d126ac Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 22:01:57 +0100 Subject: [PATCH 0081/1133] Split socket creation and connect/listen This will allow to assign the socket to a variable before connecting or listening, so that it can be interrupted from another thread. --- app/src/server.c | 57 +++++++++++++++++++++++++++++----------------- app/src/util/net.c | 33 ++++++++++++--------------- app/src/util/net.h | 9 +++++--- 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 473bbeed..abf9170e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -116,10 +116,10 @@ disable_tunnel(struct server *server) { return ok; } -static sc_socket -listen_on_port(uint16_t port) { +static bool +listen_on_port(sc_socket socket, uint16_t port) { #define IPV4_LOCALHOST 0x7F000001 - return net_listen(IPV4_LOCALHOST, port, 1); + return net_listen(socket, IPV4_LOCALHOST, port, 1); } static bool @@ -138,13 +138,18 @@ enable_tunnel_reverse_any_port(struct server *server, // client can listen before starting the server app, so there is no // need to try to connect until the server socket is listening on the // device. - sc_socket server_socket = listen_on_port(port); + sc_socket server_socket = net_socket(); if (server_socket != SC_INVALID_SOCKET) { - // success - server->server_socket = server_socket; - server->local_port = port; - server->tunnel_enabled = true; - return true; + bool ok = listen_on_port(server_socket, port); + if (ok) { + // success + server->server_socket = server_socket; + server->local_port = port; + server->tunnel_enabled = true; + return true; + } + + net_close(server_socket); } // failure, disable tunnel and try another port @@ -299,11 +304,11 @@ execute_server(struct server *server, const struct server_params *params) { return adb_execute(server->serial, cmd, ARRAY_LEN(cmd)); } -static sc_socket -connect_and_read_byte(uint16_t port) { - sc_socket socket = net_connect(IPV4_LOCALHOST, port); - if (socket == SC_INVALID_SOCKET) { - return SC_INVALID_SOCKET; +static bool +connect_and_read_byte(sc_socket socket, uint16_t port) { + bool ok = net_connect(socket, IPV4_LOCALHOST, port); + if (!ok) { + return false; } char byte; @@ -311,20 +316,25 @@ connect_and_read_byte(uint16_t port) { // is not listening, so read one byte to detect a working connection 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 false; } - return socket; + + return true; } static sc_socket 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); + sc_socket socket = net_socket(); if (socket != SC_INVALID_SOCKET) { - // it worked! - return socket; + bool ok = connect_and_read_byte(socket, port); + if (ok) { + // it worked! + return socket; + } + + net_close(socket); } if (attempts) { SDL_Delay(delay); @@ -463,10 +473,15 @@ server_connect_to(struct server *server, struct server_info *info) { } // we know that the device is listening, we don't need several attempts - control_socket = net_connect(IPV4_LOCALHOST, server->local_port); + control_socket = net_socket(); if (control_socket == SC_INVALID_SOCKET) { goto fail; } + bool ok = net_connect(control_socket, IPV4_LOCALHOST, + server->local_port); + if (!ok) { + goto fail; + } } // we don't need the adb tunnel anymore diff --git a/app/src/util/net.c b/app/src/util/net.c index cfc60433..6bfe7a52 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -94,13 +94,18 @@ net_perror(const char *s) { } sc_socket -net_connect(uint32_t addr, uint16_t port) { +net_socket(void) { 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; } + return sock; +} + +bool +net_connect(sc_socket socket, uint32_t addr, uint16_t port) { + sc_raw_socket raw_sock = unwrap(socket); SOCKADDR_IN sin; sin.sin_family = AF_INET; @@ -109,21 +114,15 @@ net_connect(uint32_t addr, uint16_t port) { if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("connect"); - net_close(sock); - return SC_INVALID_SOCKET; + return false; } - return sock; + return true; } -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; - } +bool +net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) { + sc_raw_socket raw_sock = unwrap(socket); int reuse = 1; if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, @@ -138,17 +137,15 @@ net_listen(uint32_t addr, uint16_t port, int backlog) { if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { net_perror("bind"); - net_close(sock); - return SC_INVALID_SOCKET; + return false; } if (listen(raw_sock, backlog) == SOCKET_ERROR) { net_perror("listen"); - net_close(sock); - return SC_INVALID_SOCKET; + return false; } - return sock; + return true; } sc_socket diff --git a/app/src/util/net.h b/app/src/util/net.h index c742c00e..9d675a2d 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -31,10 +31,13 @@ void net_cleanup(void); sc_socket -net_connect(uint32_t addr, uint16_t port); +net_socket(void); -sc_socket -net_listen(uint32_t addr, uint16_t port, int backlog); +bool +net_connect(sc_socket socket, uint32_t addr, uint16_t port); + +bool +net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog); sc_socket net_accept(sc_socket server_socket); From 9fa4d1cd4a7a54d19593aa460eacbd34a665216b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Oct 2021 23:20:47 +0200 Subject: [PATCH 0082/1133] Reorder server and server_params This will allow to define a server_params field in server. --- app/src/server.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/server.h b/app/src/server.h index c06b3562..0506bf10 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -20,21 +20,6 @@ struct server_info { struct sc_size frame_size; }; -struct server { - char *serial; - - sc_pid process; - // alive only between start() and stop() - struct sc_process_observer observer; - - 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" -}; - struct server_params { const char *serial; enum sc_log_level log_level; @@ -54,6 +39,21 @@ struct server_params { bool power_off_on_close; }; +struct server { + char *serial; + + sc_pid process; + // alive only between start() and stop() + struct sc_process_observer observer; + + 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" +}; + // init default values bool server_init(struct server *server); From 882e4cff5f2e1d22748b79d3371e47e4d0874ca3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Oct 2021 23:40:52 +0200 Subject: [PATCH 0083/1133] Copy server params This is a preliminary step necessary to move the server to a separate thread. --- app/src/scrcpy.c | 13 +++++---- app/src/server.c | 76 ++++++++++++++++++++++++++++++++++++++---------- app/src/server.h | 9 +++--- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c8adb5a1..43ed428e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -275,10 +275,6 @@ scrcpy(struct scrcpy_options *options) { atexit(SDL_Quit); - if (!server_init(&s->server)) { - return false; - } - bool ret = false; bool server_started = false; @@ -313,7 +309,12 @@ scrcpy(struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, }; - if (!server_start(&s->server, ¶ms)) { + + if (!server_init(&s->server, ¶ms)) { + return false; + } + + if (!server_start(&s->server)) { goto end; } @@ -338,7 +339,7 @@ scrcpy(struct scrcpy_options *options) { } if (options->display && options->control) { - if (!file_handler_init(&s->file_handler, s->server.serial, + if (!file_handler_init(&s->file_handler, options->serial, options->push_target)) { goto end; } diff --git a/app/src/server.c b/app/src/server.c index abf9170e..9e6ef3f6 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -62,6 +62,44 @@ get_server_path(void) { 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; \ + } \ + } + + COPY(serial); + COPY(crop); + COPY(codec_options); + COPY(encoder_name); +#undef COPY + + return true; + +error: + server_params_destroy(dst); + return false; +} + static bool push_server(const char *serial) { char *server_path = get_server_path(); @@ -106,9 +144,10 @@ static bool disable_tunnel(struct server *server) { assert(server->tunnel_enabled); + const char *serial = server->params.serial; bool ok = server->tunnel_forward - ? disable_tunnel_forward(server->serial, server->local_port) - : disable_tunnel_reverse(server->serial); + ? disable_tunnel_forward(serial, server->local_port) + : disable_tunnel_reverse(serial); // Consider tunnel disabled even if the command failed server->tunnel_enabled = false; @@ -125,9 +164,10 @@ listen_on_port(sc_socket socket, 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(server->serial, port)) { + if (!enable_tunnel_reverse(serial, port)) { // the command itself failed, it will fail on any port return false; } @@ -153,7 +193,7 @@ enable_tunnel_reverse_any_port(struct server *server, } // failure, disable tunnel and try another port - if (!disable_tunnel_reverse(server->serial)) { + if (!disable_tunnel_reverse(serial)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -179,9 +219,11 @@ 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(server->serial, port)) { + if (enable_tunnel_forward(serial, port)) { // success server->local_port = port; server->tunnel_enabled = true; @@ -244,6 +286,8 @@ log_level_to_server_string(enum sc_log_level level) { static sc_pid 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]; @@ -301,7 +345,7 @@ execute_server(struct server *server, const struct server_params *params) { // Port: 5005 // Then click on "Debug" #endif - return adb_execute(server->serial, cmd, ARRAY_LEN(cmd)); + return adb_execute(serial, cmd, ARRAY_LEN(cmd)); } static bool @@ -344,8 +388,13 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { } bool -server_init(struct server *server) { - server->serial = NULL; +server_init(struct server *server, const struct server_params *params) { + bool ok = server_params_copy(&server->params, params); + if (!ok) { + LOGE("Could not copy server params"); + return false; + } + server->process = SC_PROCESS_NONE; server->server_socket = SC_INVALID_SOCKET; @@ -377,13 +426,8 @@ server_on_terminated(void *userdata) { } bool -server_start(struct server *server, const struct server_params *params) { - if (params->serial) { - server->serial = strdup(params->serial); - if (!server->serial) { - return false; - } - } +server_start(struct server *server) { + const struct server_params *params = &server->params; if (!push_server(params->serial)) { /* server->serial will be freed on server_destroy() */ @@ -582,5 +626,5 @@ server_destroy(struct server *server) { LOGW("Could not close control socket"); } } - free(server->serial); + server_params_destroy(&server->params); } diff --git a/app/src/server.h b/app/src/server.h index 0506bf10..42082fa9 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -40,7 +40,8 @@ struct server_params { }; struct server { - char *serial; + // The internal allocated strings are copies owned by the server + struct server_params params; sc_pid process; // alive only between start() and stop() @@ -54,13 +55,13 @@ struct server { bool tunnel_forward; // use "adb forward" instead of "adb reverse" }; -// init default values +// init the server with the given params bool -server_init(struct server *server); +server_init(struct server *server, const struct server_params *params); // push, enable tunnel et start the server bool -server_start(struct server *server, const struct server_params *params); +server_start(struct server *server); // block until the communication with the server is established bool From a54dc8212ffb1eec975a27a4d70fea25ed8c3f24 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 18:30:20 +0100 Subject: [PATCH 0084/1133] Reorder server functions This will avoid forward declarations in future commits. --- app/src/server.c | 114 +++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 9e6ef3f6..1491052a 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -409,63 +409,6 @@ server_init(struct server *server, const struct server_params *params) { return true; } -static void -server_on_terminated(void *userdata) { - struct server *server = userdata; - - // No need for synchronization, server_socket is initialized before the - // observer thread is created. - if (server->server_socket != SC_INVALID_SOCKET) { - // If the server process dies before connecting to the server socket, - // then the client will be stuck forever on accept(). To avoid the - // problem, wake up the accept() call when the server dies. - net_interrupt(server->server_socket); - } - - LOGD("Server terminated"); -} - -bool -server_start(struct server *server) { - const struct server_params *params = &server->params; - - 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 == SC_PROCESS_NONE) { - goto error; - } - - static const struct sc_process_listener listener = { - .on_terminated = server_on_terminated, - }; - bool ok = sc_process_observer_init(&server->observer, server->process, - &listener, server); - if (!ok) { - sc_process_terminate(server->process); - sc_process_wait(server->process, true); // ignore exit code - goto error; - } - - return true; - -error: - // The server socket (if any) will be closed on server_destroy() - - disable_tunnel(server); - - return false; -} - static bool device_read_info(sc_socket device_socket, struct server_info *info) { unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; @@ -561,6 +504,63 @@ fail: return false; } +static void +server_on_terminated(void *userdata) { + struct server *server = userdata; + + // No need for synchronization, server_socket is initialized before the + // observer thread is created. + if (server->server_socket != SC_INVALID_SOCKET) { + // If the server process dies before connecting to the server socket, + // then the client will be stuck forever on accept(). To avoid the + // problem, wake up the accept() call when the server dies. + net_interrupt(server->server_socket); + } + + LOGD("Server terminated"); +} + +bool +server_start(struct server *server) { + const struct server_params *params = &server->params; + + 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 == SC_PROCESS_NONE) { + goto error; + } + + static const struct sc_process_listener listener = { + .on_terminated = server_on_terminated, + }; + bool ok = sc_process_observer_init(&server->observer, server->process, + &listener, server); + if (!ok) { + sc_process_terminate(server->process); + sc_process_wait(server->process, true); // ignore exit code + goto error; + } + + return true; + +error: + // The server socket (if any) will be closed on server_destroy() + + disable_tunnel(server); + + return false; +} + void server_stop(struct server *server) { if (server->server_socket != SC_INVALID_SOCKET) { From 5b9c88693ea1b2d19f325ab6ff6c06119781a101 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 31 Oct 2021 14:56:37 +0100 Subject: [PATCH 0085/1133] Wait using a condition variable in server Currently, server_stop() is called from the same thread as server_connect_to(), so interruption may never happen. This is a step to prepare executing the server from a dedicated thread. --- app/src/server.c | 45 +++++++++++++++++++++++++++++++++++++++++---- app/src/server.h | 4 ++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 1491052a..5ed1984c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -367,7 +367,8 @@ connect_and_read_byte(sc_socket socket, uint16_t port) { } static sc_socket -connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { +connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { + uint16_t port = server->local_port; do { LOGD("Remaining connection attempts: %d", (int) attempts); sc_socket socket = net_socket(); @@ -381,7 +382,20 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) { net_close(socket); } if (attempts) { - SDL_Delay(delay); + sc_mutex_lock(&server->mutex); + sc_tick deadline = sc_tick_now() + delay; + bool timed_out = false; + while (!server->stopped && !timed_out) { + timed_out = !sc_cond_timedwait(&server->cond_stopped, + &server->mutex, deadline); + } + bool stopped = server->stopped; + sc_mutex_unlock(&server->mutex); + + if (stopped) { + LOGI("Connection attempt stopped"); + break; + } } } while (--attempts > 0); return SC_INVALID_SOCKET; @@ -395,7 +409,23 @@ server_init(struct server *server, const struct server_params *params) { return false; } + ok = sc_mutex_init(&server->mutex); + if (!ok) { + LOGE("Could not create server mutex"); + server_params_destroy(&server->params); + return false; + } + + ok = sc_cond_init(&server->cond_stopped); + if (!ok) { + LOGE("Could not create server cond_stopped"); + sc_mutex_destroy(&server->mutex); + server_params_destroy(&server->params); + return false; + } + server->process = SC_PROCESS_NONE; + server->stopped = false; server->server_socket = SC_INVALID_SOCKET; server->video_socket = SC_INVALID_SOCKET; @@ -453,8 +483,8 @@ server_connect_to(struct server *server, struct server_info *info) { server->server_socket = SC_INVALID_SOCKET; } else { uint32_t attempts = 100; - uint32_t delay = 100; // ms - video_socket = connect_to_server(server->local_port, attempts, delay); + sc_tick delay = SC_TICK_FROM_MS(100); + video_socket = connect_to_server(server, attempts, delay); if (video_socket == SC_INVALID_SOCKET) { goto fail; } @@ -563,6 +593,11 @@ error: void server_stop(struct server *server) { + sc_mutex_lock(&server->mutex); + server->stopped = true; + sc_cond_signal(&server->cond_stopped); + sc_mutex_unlock(&server->mutex); + if (server->server_socket != SC_INVALID_SOCKET) { if (!net_interrupt(server->server_socket)) { LOGW("Could not interrupt server socket"); @@ -627,4 +662,6 @@ server_destroy(struct server *server) { } } server_params_destroy(&server->params); + sc_cond_destroy(&server->cond_stopped); + sc_mutex_destroy(&server->mutex); } diff --git a/app/src/server.h b/app/src/server.h index 42082fa9..5807c322 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -47,6 +47,10 @@ struct server { // alive only between start() and stop() struct sc_process_observer observer; + sc_mutex mutex; + sc_cond cond_stopped; + bool stopped; + sc_socket server_socket; // only used if !tunnel_forward sc_socket video_socket; sc_socket control_socket; From 04267085441d6fcd05eff7df0118708f7622e237 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Oct 2021 15:33:23 +0200 Subject: [PATCH 0086/1133] Run the server from a dedicated thread Define server callbacks, start the server asynchronously and listen to connection events to initialize scrcpy properly. It will help to simplify the server code, and allows to run the UI event loop while the server is connecting. In particular, this will allow to receive SIGINT/Ctrl+C events during connection to interrupt immediately. --- app/src/events.h | 6 +- app/src/scrcpy.c | 72 +++++++++++++++++++++--- app/src/server.c | 144 ++++++++++++++++++++++++++++------------------- app/src/server.h | 37 +++++++++--- 4 files changed, 181 insertions(+), 78 deletions(-) diff --git a/app/src/events.h b/app/src/events.h index a4d6f3df..abe1a72c 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,2 +1,4 @@ -#define EVENT_NEW_FRAME SDL_USEREVENT -#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) +#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) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 43ed428e..38a50a0b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -217,6 +217,29 @@ 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)) { + 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) { @@ -262,6 +285,32 @@ stream_on_eos(struct stream *stream, void *userdata) { PUSH_EVENT(EVENT_STREAM_STOPPED); } +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; + + LOGD("Server disconnected"); + // Do nothing, the disconnection will be handled by the "stream stopped" + // event +} + bool scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; @@ -310,7 +359,12 @@ scrcpy(struct scrcpy_options *options) { .power_off_on_close = options->power_off_on_close, }; - if (!server_init(&s->server, ¶ms)) { + static const struct server_callbacks cbs = { + .on_connection_failed = server_on_connection_failed, + .on_connected = server_on_connected, + .on_disconnected = server_on_disconnected, + }; + if (!server_init(&s->server, ¶ms, &cbs, NULL)) { return false; } @@ -332,12 +386,14 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); - struct server_info info; - - if (!server_connect_to(&s->server, &info)) { + // Await for server without blocking Ctrl+C handling + if (!await_for_server()) { goto end; } + // It is necessarily initialized here, since the device is connected + struct server_info *info = &s->server.info; + if (options->display && options->control) { if (!file_handler_init(&s->file_handler, options->serial, options->push_target)) { @@ -361,7 +417,7 @@ scrcpy(struct scrcpy_options *options) { if (!recorder_init(&s->recorder, options->record_filename, options->record_format, - info.frame_size)) { + info->frame_size)) { goto end; } rec = &s->recorder; @@ -407,11 +463,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 : info->device_name; struct screen_params screen_params = { .window_title = window_title, - .frame_size = info.frame_size, + .frame_size = info->frame_size, .always_on_top = options->always_on_top, .window_x = options->window_x, .window_y = options->window_y, @@ -435,7 +491,7 @@ 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)) { + info->frame_size, options->v4l2_buffer)) { goto end; } diff --git a/app/src/server.c b/app/src/server.c index 5ed1984c..4709059f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -402,7 +402,8 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { } bool -server_init(struct server *server, const struct server_params *params) { +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"); @@ -424,7 +425,6 @@ server_init(struct server *server, const struct server_params *params) { return false; } - server->process = SC_PROCESS_NONE; server->stopped = false; server->server_socket = SC_INVALID_SOCKET; @@ -436,6 +436,14 @@ server_init(struct server *server, const struct server_params *params) { 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; } @@ -458,7 +466,7 @@ device_read_info(sc_socket device_socket, struct server_info *info) { return true; } -bool +static bool server_connect_to(struct server *server, struct server_info *info) { assert(server->tunnel_enabled); @@ -531,6 +539,9 @@ fail: } } + // Always leave this function with tunnel disabled + disable_tunnel(server); + return false; } @@ -547,57 +558,68 @@ server_on_terminated(void *userdata) { net_interrupt(server->server_socket); } + server->cbs->on_disconnected(server, server->cbs_userdata); + LOGD("Server terminated"); } -bool -server_start(struct server *server) { +static int +run_server(void *data) { + struct server *server = data; + const struct server_params *params = &server->params; - if (!push_server(params->serial)) { - /* server->serial will be freed on server_destroy() */ - return false; + bool ok = push_server(params->serial); + if (!ok) { + goto error_connection_failed; } - if (!enable_tunnel_any_port(server, params->port_range, - params->force_adb_forward)) { - return false; + ok = enable_tunnel_any_port(server, params->port_range, + params->force_adb_forward); + if (!ok) { + goto error_connection_failed; } // server will connect to our server socket - server->process = execute_server(server, params); - if (server->process == SC_PROCESS_NONE) { - goto error; + sc_pid pid = execute_server(server, params); + if (pid == SC_PROCESS_NONE) { + disable_tunnel(server); + goto error_connection_failed; } static const struct sc_process_listener listener = { .on_terminated = server_on_terminated, }; - bool ok = sc_process_observer_init(&server->observer, server->process, - &listener, server); + struct sc_process_observer observer; + ok = sc_process_observer_init(&observer, pid, &listener, server); if (!ok) { - sc_process_terminate(server->process); - sc_process_wait(server->process, true); // ignore exit code - goto error; + sc_process_terminate(pid); + sc_process_wait(pid, true); // ignore exit code + disable_tunnel(server); + goto error_connection_failed; } - return true; - -error: - // The server socket (if any) will be closed on server_destroy() - - disable_tunnel(server); + ok = server_connect_to(server, &server->info); + // The tunnel is always closed by server_connect_to() + if (!ok) { + sc_process_terminate(pid); + sc_process_wait(pid, true); // ignore exit code + sc_process_observer_join(&observer); + sc_process_observer_destroy(&observer); + goto error_connection_failed; + } - return false; -} + // Now connected + server->cbs->on_connected(server, server->cbs_userdata); -void -server_stop(struct server *server) { + // Wait for server_stop() sc_mutex_lock(&server->mutex); - server->stopped = true; - sc_cond_signal(&server->cond_stopped); + while (!server->stopped) { + sc_cond_wait(&server->cond_stopped, &server->mutex); + } sc_mutex_unlock(&server->mutex); + // Server stop has been requested if (server->server_socket != SC_INVALID_SOCKET) { if (!net_interrupt(server->server_socket)) { LOGW("Could not interrupt server socket"); @@ -614,18 +636,10 @@ server_stop(struct server *server) { } } - assert(server->process != SC_PROCESS_NONE); - - if (server->tunnel_enabled) { - // ignore failure - disable_tunnel(server); - } - // Give some delay for the server to terminate properly #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; - bool terminated = - sc_process_observer_timedwait(&server->observer, deadline); + bool terminated = sc_process_observer_timedwait(&observer, deadline); // After this delay, kill the server if it's not dead already. // On some devices, closing the sockets is not sufficient to wake up the @@ -635,32 +649,44 @@ server_stop(struct server *server) { // reaped (closed) yet, so its PID is still valid, and it is ok to call // sc_process_terminate() even in that case. LOGW("Killing the server..."); - sc_process_terminate(server->process); + sc_process_terminate(pid); } - sc_process_observer_join(&server->observer); - sc_process_observer_destroy(&server->observer); + sc_process_observer_join(&observer); + sc_process_observer_destroy(&observer); + + sc_process_close(pid); + + return 0; - sc_process_close(server->process); +error_connection_failed: + server->cbs->on_connection_failed(server, server->cbs_userdata); + return -1; +} + +bool +server_start(struct server *server) { + bool ok = sc_thread_create(&server->thread, run_server, "server", server); + if (!ok) { + LOGE("Could not create server thread"); + return false; + } + + return true; +} + +void +server_stop(struct server *server) { + sc_mutex_lock(&server->mutex); + server->stopped = true; + sc_cond_signal(&server->cond_stopped); + sc_mutex_unlock(&server->mutex); + + sc_thread_join(&server->thread, NULL); } 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); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); diff --git a/app/src/server.h b/app/src/server.h index 5807c322..e4666346 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -43,9 +43,8 @@ struct server { // The internal allocated strings are copies owned by the server struct server_params params; - sc_pid process; - // alive only between start() and stop() - struct sc_process_observer observer; + sc_thread thread; + struct server_info info; // initialized once connected sc_mutex mutex; sc_cond cond_stopped; @@ -57,20 +56,40 @@ struct server { 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 { + /** + * Called when the server failed to connect + * + * If it is called, then on_connected() and on_disconnected() will never be + * called. + */ + void (*on_connection_failed)(struct server *server, void *userdata); + + /** + * Called on server connection + */ + void (*on_connected)(struct server *server, void *userdata); + + /** + * Called on server disconnection (after it has been connected) + */ + void (*on_disconnected)(struct server *server, void *userdata); }; // init the server with the given params bool -server_init(struct server *server, const struct server_params *params); +server_init(struct server *server, const struct server_params *params, + const struct server_callbacks *cbs, void *cbs_userdata); -// push, enable tunnel et start the server +// start the server asynchronously bool server_start(struct server *server); -// block until the communication with the server is established -bool -server_connect_to(struct server *server, struct server_info *info); - // disconnect and kill the server process void server_stop(struct server *server); From e0896142dbbbadd87a6339111a406307edca1b97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 18:50:50 +0100 Subject: [PATCH 0087/1133] Introduce interruptor tool An interruptor instance will help to wake up a blocking call from another thread (typically to terminate immediately on Ctrl+C). --- app/meson.build | 1 + app/src/util/intr.c | 83 +++++++++++++++++++++++++++++++++++++++++++++ app/src/util/intr.h | 78 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 app/src/util/intr.c create mode 100644 app/src/util/intr.h diff --git a/app/meson.build b/app/meson.build index 94b4994f..8240dd16 100644 --- a/app/meson.build +++ b/app/meson.build @@ -25,6 +25,7 @@ src = [ 'src/stream.c', 'src/video_buffer.c', 'src/util/file.c', + 'src/util/intr.c', 'src/util/log.c', 'src/util/net.c', 'src/util/process.c', diff --git a/app/src/util/intr.c b/app/src/util/intr.c new file mode 100644 index 00000000..d4fce55c --- /dev/null +++ b/app/src/util/intr.c @@ -0,0 +1,83 @@ +#include "intr.h" + +#include "util/log.h" + +#include + +bool +sc_intr_init(struct sc_intr *intr) { + bool ok = sc_mutex_init(&intr->mutex); + if (!ok) { + LOGE("Could not init intr mutex"); + return false; + } + + intr->socket = SC_INVALID_SOCKET; + intr->process = SC_PROCESS_NONE; + + atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed); + + return true; +} + +bool +sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) { + assert(intr->process == SC_PROCESS_NONE); + + sc_mutex_lock(&intr->mutex); + bool interrupted = + atomic_load_explicit(&intr->interrupted, memory_order_relaxed); + if (!interrupted) { + intr->socket = socket; + } + sc_mutex_unlock(&intr->mutex); + + return !interrupted; +} + +bool +sc_intr_set_process(struct sc_intr *intr, sc_pid pid) { + assert(intr->socket == SC_INVALID_SOCKET); + + sc_mutex_lock(&intr->mutex); + bool interrupted = + atomic_load_explicit(&intr->interrupted, memory_order_relaxed); + if (!interrupted) { + intr->process = pid; + } + sc_mutex_unlock(&intr->mutex); + + return !interrupted; +} + +void +sc_intr_interrupt(struct sc_intr *intr) { + sc_mutex_lock(&intr->mutex); + + atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed); + + // No more than one component to interrupt + assert(intr->socket == SC_INVALID_SOCKET || + intr->process == SC_PROCESS_NONE); + + if (intr->socket != SC_INVALID_SOCKET) { + LOGD("Interrupting socket"); + net_interrupt(intr->socket); + intr->socket = SC_INVALID_SOCKET; + } + if (intr->process != SC_PROCESS_NONE) { + LOGD("Interrupting process"); + sc_process_terminate(intr->process); + intr->process = SC_PROCESS_NONE; + } + + sc_mutex_unlock(&intr->mutex); +} + +void +sc_intr_destroy(struct sc_intr *intr) { + assert(intr->socket == SC_INVALID_SOCKET); + assert(intr->process == SC_PROCESS_NONE); + + sc_mutex_destroy(&intr->mutex); +} diff --git a/app/src/util/intr.h b/app/src/util/intr.h new file mode 100644 index 00000000..7f0fb9b7 --- /dev/null +++ b/app/src/util/intr.h @@ -0,0 +1,78 @@ +#ifndef SC_INTR_H +#define SC_INTR_H + +#include "common.h" + +#include +#include + +#include "net.h" +#include "process.h" +#include "thread.h" + +/** + * Interruptor to wake up a blocking call from another thread + * + * It allows to register a socket or a process before a blocking call, and + * interrupt/close from another thread to wake up the blocking call. + */ +struct sc_intr { + sc_mutex mutex; + + sc_socket socket; + sc_pid process; + + // Written protected by the mutex to avoid race conditions against + // sc_intr_set_socket() and sc_intr_set_process(), but can be read + // (atomically) without mutex + atomic_bool interrupted; +}; + +/** + * Initialize an interruptor + */ +bool +sc_intr_init(struct sc_intr *intr); + +/** + * Set a socket as the interruptible component + * + * Call with SC_INVALID_SOCKET to unset. + */ +bool +sc_intr_set_socket(struct sc_intr *intr, sc_socket socket); + +/** + * Set a process as the interruptible component + * + * Call with SC_PROCESS_NONE to unset. + */ +bool +sc_intr_set_process(struct sc_intr *intr, sc_pid socket); + +/** + * Interrupt the current interruptible component + * + * Must be called from a different thread. + */ +void +sc_intr_interrupt(struct sc_intr *intr); + +/** + * Read the interrupted state + * + * It is exposed as a static inline function because it just loads from an + * atomic. + */ +static inline bool +sc_intr_is_interrupted(struct sc_intr *intr) { + return atomic_load_explicit(&intr->interrupted, memory_order_relaxed); +} + +/** + * Destroy the interruptor + */ +void +sc_intr_destroy(struct sc_intr *intr); + +#endif From 40340509d9245694bf46e60604c0fb262f668465 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 18:50:50 +0100 Subject: [PATCH 0088/1133] Add interruptor utilities Expose wrapper functions for interrupting blocking calls, for process and sockets. --- app/meson.build | 2 + app/src/util/net_intr.c | 97 +++++++++++++++++++++++++++++++++++++ app/src/util/net_intr.h | 35 +++++++++++++ app/src/util/process_intr.c | 16 ++++++ app/src/util/process_intr.h | 13 +++++ 5 files changed, 163 insertions(+) create mode 100644 app/src/util/net_intr.c create mode 100644 app/src/util/net_intr.h create mode 100644 app/src/util/process_intr.c create mode 100644 app/src/util/process_intr.h diff --git a/app/meson.build b/app/meson.build index 8240dd16..09c79f21 100644 --- a/app/meson.build +++ b/app/meson.build @@ -28,7 +28,9 @@ src = [ 'src/util/intr.c', 'src/util/log.c', 'src/util/net.c', + 'src/util/net_intr.c', 'src/util/process.c', + 'src/util/process_intr.c', 'src/util/strbuf.c', 'src/util/str_util.c', 'src/util/term.c', diff --git a/app/src/util/net_intr.c b/app/src/util/net_intr.c new file mode 100644 index 00000000..b9443e2f --- /dev/null +++ b/app/src/util/net_intr.c @@ -0,0 +1,97 @@ +#include "net_intr.h" + +bool +net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, + uint16_t port) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return false; + } + + bool ret = net_connect(socket, addr, port); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return ret; +} + +bool +net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, + uint16_t port, int backlog) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return false; + } + + bool ret = net_listen(socket, addr, port, backlog); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return ret; +} + +sc_socket +net_accept_intr(struct sc_intr *intr, sc_socket server_socket) { + if (!sc_intr_set_socket(intr, server_socket)) { + // Already interrupted + return SC_INVALID_SOCKET; + } + + sc_socket socket = net_accept(server_socket); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return socket; +} + +ssize_t +net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return -1; + } + + ssize_t r = net_recv(socket, buf, len); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return r; +} + +ssize_t +net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf, + size_t len) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return -1; + } + + ssize_t r = net_recv_all(socket, buf, len); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return r; +} + +ssize_t +net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf, + size_t len) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return -1; + } + + ssize_t w = net_send(socket, buf, len); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return w; +} + +ssize_t +net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf, + size_t len) { + if (!sc_intr_set_socket(intr, socket)) { + // Already interrupted + return -1; + } + + ssize_t w = net_send_all(socket, buf, len); + + sc_intr_set_socket(intr, SC_INVALID_SOCKET); + return w; +} diff --git a/app/src/util/net_intr.h b/app/src/util/net_intr.h new file mode 100644 index 00000000..a83fadda --- /dev/null +++ b/app/src/util/net_intr.h @@ -0,0 +1,35 @@ +#ifndef SC_NET_INTR_H +#define SC_NET_INTR_H + +#include "common.h" + +#include "intr.h" +#include "net.h" + +bool +net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, + uint16_t port); + +bool +net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, + uint16_t port, int backlog); + +sc_socket +net_accept_intr(struct sc_intr *intr, sc_socket server_socket); + +ssize_t +net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len); + +ssize_t +net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf, + size_t len); + +ssize_t +net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf, + size_t len); + +ssize_t +net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf, + size_t len); + +#endif diff --git a/app/src/util/process_intr.c b/app/src/util/process_intr.c new file mode 100644 index 00000000..bb483123 --- /dev/null +++ b/app/src/util/process_intr.c @@ -0,0 +1,16 @@ +#include "process_intr.h" + +bool +sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, + const char *name) { + if (!sc_intr_set_process(intr, pid)) { + // Already interrupted + return false; + } + + // Always pass close=false, interrupting would be racy otherwise + bool ret = sc_process_check_success(pid, name, false); + + sc_intr_set_process(intr, SC_PROCESS_NONE); + return ret; +} diff --git a/app/src/util/process_intr.h b/app/src/util/process_intr.h new file mode 100644 index 00000000..ff0dfc76 --- /dev/null +++ b/app/src/util/process_intr.h @@ -0,0 +1,13 @@ +#ifndef SC_PROCESS_INTR_H +#define SC_PROCESS_INTR_H + +#include "common.h" + +#include "intr.h" +#include "process.h" + +bool +sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, + const char *name); + +#endif From f488cbd7e7f8a74ace23416faa962b6a510f8374 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 18:50:50 +0100 Subject: [PATCH 0089/1133] Make server interruptible Use an interruptor to immediately wake up blocking calls on server_stop(). --- app/src/server.c | 105 +++++++++++++++++++++++++---------------------- app/src/server.h | 3 ++ 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 4709059f..b21fa802 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -10,7 +10,8 @@ #include "adb.h" #include "util/file.h" #include "util/log.h" -#include "util/net.h" +#include "util/net_intr.h" +#include "util/process_intr.h" #include "util/str_util.h" #define SOCKET_NAME "scrcpy" @@ -101,7 +102,7 @@ error: } static bool -push_server(const char *serial) { +push_server(struct sc_intr *intr, const char *serial) { char *server_path = get_server_path(); if (!server_path) { return false; @@ -113,31 +114,34 @@ push_server(const char *serial) { } sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH); free(server_path); - return sc_process_check_success(pid, "adb push", true); + return sc_process_check_success_intr(intr, pid, "adb push"); } static bool -enable_tunnel_reverse(const char *serial, uint16_t local_port) { +enable_tunnel_reverse(struct sc_intr *intr, const char *serial, + uint16_t local_port) { sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port); - return sc_process_check_success(pid, "adb reverse", true); + return sc_process_check_success_intr(intr, pid, "adb reverse"); } static bool -disable_tunnel_reverse(const char *serial) { +disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME); - return sc_process_check_success(pid, "adb reverse --remove", true); + return sc_process_check_success_intr(intr, pid, "adb reverse --remove"); } static bool -enable_tunnel_forward(const char *serial, uint16_t local_port) { +enable_tunnel_forward(struct sc_intr *intr, const char *serial, + uint16_t local_port) { sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME); - return sc_process_check_success(pid, "adb forward", true); + return sc_process_check_success_intr(intr, pid, "adb forward"); } static bool -disable_tunnel_forward(const char *serial, uint16_t local_port) { +disable_tunnel_forward(struct sc_intr *intr, const char *serial, + uint16_t local_port) { sc_pid pid = adb_forward_remove(serial, local_port); - return sc_process_check_success(pid, "adb forward --remove", true); + return sc_process_check_success_intr(intr, pid, "adb forward --remove"); } static bool @@ -146,8 +150,8 @@ disable_tunnel(struct server *server) { const char *serial = server->params.serial; bool ok = server->tunnel_forward - ? disable_tunnel_forward(serial, server->local_port) - : disable_tunnel_reverse(serial); + ? disable_tunnel_forward(&server->intr, serial, server->local_port) + : disable_tunnel_reverse(&server->intr, serial); // Consider tunnel disabled even if the command failed server->tunnel_enabled = false; @@ -156,9 +160,9 @@ disable_tunnel(struct server *server) { } static bool -listen_on_port(sc_socket socket, uint16_t port) { +listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { #define IPV4_LOCALHOST 0x7F000001 - return net_listen(socket, IPV4_LOCALHOST, port, 1); + return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); } static bool @@ -167,7 +171,7 @@ enable_tunnel_reverse_any_port(struct server *server, const char *serial = server->params.serial; uint16_t port = port_range.first; for (;;) { - if (!enable_tunnel_reverse(serial, port)) { + if (!enable_tunnel_reverse(&server->intr, serial, port)) { // the command itself failed, it will fail on any port return false; } @@ -180,7 +184,7 @@ enable_tunnel_reverse_any_port(struct server *server, // device. sc_socket server_socket = net_socket(); if (server_socket != SC_INVALID_SOCKET) { - bool ok = listen_on_port(server_socket, port); + bool ok = listen_on_port(&server->intr, server_socket, port); if (ok) { // success server->server_socket = server_socket; @@ -192,8 +196,13 @@ enable_tunnel_reverse_any_port(struct server *server, net_close(server_socket); } + if (sc_intr_is_interrupted(&server->intr)) { + // Stop immediately + return false; + } + // failure, disable tunnel and try another port - if (!disable_tunnel_reverse(serial)) { + if (!disable_tunnel_reverse(&server->intr, serial)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -223,13 +232,18 @@ enable_tunnel_forward_any_port(struct server *server, const char *serial = server->params.serial; uint16_t port = port_range.first; for (;;) { - if (enable_tunnel_forward(serial, port)) { + if (enable_tunnel_forward(&server->intr, serial, port)) { // success server->local_port = port; server->tunnel_enabled = true; return true; } + if (sc_intr_is_interrupted(&server->intr)) { + // Stop immediately + return false; + } + if (port < port_range.last) { LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16, port, (uint16_t) (port + 1)); @@ -349,8 +363,8 @@ execute_server(struct server *server, const struct server_params *params) { } static bool -connect_and_read_byte(sc_socket socket, uint16_t port) { - bool ok = net_connect(socket, IPV4_LOCALHOST, port); +connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { + bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port); if (!ok) { return false; } @@ -358,7 +372,7 @@ connect_and_read_byte(sc_socket socket, uint16_t port) { char byte; // the connection may succeed even if the server behind the "adb tunnel" // is not listening, so read one byte to detect a working connection - if (net_recv(socket, &byte, 1) != 1) { + if (net_recv_intr(intr, socket, &byte, 1) != 1) { // the server is not listening yet behind the adb tunnel return false; } @@ -373,7 +387,7 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { LOGD("Remaining connection attempts: %d", (int) attempts); sc_socket socket = net_socket(); if (socket != SC_INVALID_SOCKET) { - bool ok = connect_and_read_byte(socket, port); + bool ok = connect_and_read_byte(&server->intr, socket, port); if (ok) { // it worked! return socket; @@ -425,6 +439,15 @@ server_init(struct server *server, const struct server_params *params, return false; } + ok = sc_intr_init(&server->intr); + if (!ok) { + LOGE("Could not create intr"); + sc_cond_destroy(&server->cond_stopped); + sc_mutex_destroy(&server->mutex); + server_params_destroy(&server->params); + return false; + } + server->stopped = false; server->server_socket = SC_INVALID_SOCKET; @@ -448,9 +471,10 @@ server_init(struct server *server, const struct server_params *params, } static bool -device_read_info(sc_socket device_socket, struct server_info *info) { +device_read_info(struct sc_intr *intr, sc_socket device_socket, + struct server_info *info) { unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; - ssize_t r = net_recv_all(device_socket, buf, sizeof(buf)); + ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf)); if (r < DEVICE_NAME_FIELD_LENGTH + 4) { LOGE("Could not retrieve device information"); return false; @@ -473,12 +497,12 @@ server_connect_to(struct server *server, struct server_info *info) { sc_socket video_socket = SC_INVALID_SOCKET; sc_socket control_socket = SC_INVALID_SOCKET; if (!server->tunnel_forward) { - video_socket = net_accept(server->server_socket); + video_socket = net_accept_intr(&server->intr, server->server_socket); if (video_socket == SC_INVALID_SOCKET) { goto fail; } - control_socket = net_accept(server->server_socket); + control_socket = net_accept_intr(&server->intr, server->server_socket); if (control_socket == SC_INVALID_SOCKET) { goto fail; } @@ -502,8 +526,8 @@ server_connect_to(struct server *server, struct server_info *info) { if (control_socket == SC_INVALID_SOCKET) { goto fail; } - bool ok = net_connect(control_socket, IPV4_LOCALHOST, - server->local_port); + bool ok = net_connect_intr(&server->intr, control_socket, + IPV4_LOCALHOST, server->local_port); if (!ok) { goto fail; } @@ -513,7 +537,7 @@ server_connect_to(struct server *server, struct server_info *info) { disable_tunnel(server); // ignore failure // The sockets will be closed on stop if device_read_info() fails - bool ok = device_read_info(video_socket, info); + bool ok = device_read_info(&server->intr, video_socket, info); if (!ok) { goto fail; } @@ -569,7 +593,7 @@ run_server(void *data) { const struct server_params *params = &server->params; - bool ok = push_server(params->serial); + bool ok = push_server(&server->intr, params->serial); if (!ok) { goto error_connection_failed; } @@ -619,23 +643,6 @@ run_server(void *data) { } sc_mutex_unlock(&server->mutex); - // Server stop has been requested - if (server->server_socket != SC_INVALID_SOCKET) { - if (!net_interrupt(server->server_socket)) { - LOGW("Could not interrupt server socket"); - } - } - if (server->video_socket != SC_INVALID_SOCKET) { - if (!net_interrupt(server->video_socket)) { - LOGW("Could not interrupt video socket"); - } - } - if (server->control_socket != SC_INVALID_SOCKET) { - if (!net_interrupt(server->control_socket)) { - LOGW("Could not interrupt control socket"); - } - } - // Give some delay for the server to terminate properly #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; @@ -680,6 +687,7 @@ server_stop(struct server *server) { sc_mutex_lock(&server->mutex); server->stopped = true; sc_cond_signal(&server->cond_stopped); + sc_intr_interrupt(&server->intr); sc_mutex_unlock(&server->mutex); sc_thread_join(&server->thread, NULL); @@ -688,6 +696,7 @@ server_stop(struct server *server) { void server_destroy(struct server *server) { server_params_destroy(&server->params); + sc_intr_destroy(&server->intr); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); } diff --git a/app/src/server.h b/app/src/server.h index e4666346..b021ba32 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -10,6 +10,7 @@ #include "adb.h" #include "coords.h" #include "options.h" +#include "util/intr.h" #include "util/log.h" #include "util/net.h" #include "util/thread.h" @@ -50,6 +51,8 @@ struct server { sc_cond cond_stopped; bool stopped; + struct sc_intr intr; + sc_socket server_socket; // only used if !tunnel_forward sc_socket video_socket; sc_socket control_socket; From 37c840a4c83a240d7cf327395bd4be7a882eda65 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 21:40:22 +0100 Subject: [PATCH 0090/1133] Interrupt on process terminated Interrupt any blocking call on process terminated, like on server_stop(). This allows to interrupt any blocking accept() with correct synchronization without additional complexity. --- app/src/server.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index b21fa802..bb49db37 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -511,8 +511,8 @@ server_connect_to(struct server *server, struct server_info *info) { if (!net_close(server->server_socket)) { LOGW("Could not close server socket on connect"); } - // Do not attempt to close it again on server_destroy() - server->server_socket = SC_INVALID_SOCKET; + + // server_socket is never used anymore } else { uint32_t attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); @@ -573,14 +573,11 @@ static void server_on_terminated(void *userdata) { struct server *server = userdata; - // No need for synchronization, server_socket is initialized before the - // observer thread is created. - if (server->server_socket != SC_INVALID_SOCKET) { - // If the server process dies before connecting to the server socket, - // then the client will be stuck forever on accept(). To avoid the - // problem, wake up the accept() call when the server dies. - net_interrupt(server->server_socket); - } + // If the server process dies before connecting to the server socket, + // then the client will be stuck forever on accept(). To avoid the problem, + // wake up the accept() call (or any other) when the server dies, like on + // stop() (it is safe to call interrupt() twice). + sc_intr_interrupt(&server->intr); server->cbs->on_disconnected(server, server->cbs_userdata); From 0d45c29d13be15764a3e58e47d5e55f07b4e5dee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 18:59:56 +0100 Subject: [PATCH 0091/1133] Move IPV4_LOCALHOST to net.h This constant will be used from several files. --- app/src/server.c | 1 - app/src/util/net.h | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index bb49db37..7a1f2abc 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -161,7 +161,6 @@ disable_tunnel(struct server *server) { static bool listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { -#define IPV4_LOCALHOST 0x7F000001 return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); } diff --git a/app/src/util/net.h b/app/src/util/net.h index 9d675a2d..7a9fbe47 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -22,8 +22,11 @@ # include # define SC_INVALID_SOCKET -1 typedef int sc_socket; + #endif +#define IPV4_LOCALHOST 0x7F000001 + bool net_init(void); From c4d008b96ae3f23a467642fed38f91e54b1f36b8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 22:32:29 +0100 Subject: [PATCH 0092/1133] Extract adb tunnel to a separate component This simplifies the server code. --- app/meson.build | 1 + app/src/adb_tunnel.c | 192 ++++++++++++++++++++++++++++++++++++++++ app/src/adb_tunnel.h | 47 ++++++++++ app/src/server.c | 205 ++++--------------------------------------- app/src/server.h | 6 +- 5 files changed, 260 insertions(+), 191 deletions(-) create mode 100644 app/src/adb_tunnel.c create mode 100644 app/src/adb_tunnel.h diff --git a/app/meson.build b/app/meson.build index 09c79f21..7abef1e2 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,7 @@ src = [ 'src/main.c', 'src/adb.c', + 'src/adb_tunnel.c', 'src/cli.c', 'src/clock.c', 'src/compat.c', diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c new file mode 100644 index 00000000..d1a19afc --- /dev/null +++ b/app/src/adb_tunnel.c @@ -0,0 +1,192 @@ +#include "adb_tunnel.h" + +#include + +#include "adb.h" +#include "util/log.h" +#include "util/net_intr.h" +#include "util/process_intr.h" + +#define SC_SOCKET_NAME "scrcpy" + +static bool +enable_tunnel_reverse(struct sc_intr *intr, const char *serial, + uint16_t local_port) { + sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port); + return sc_process_check_success_intr(intr, pid, "adb reverse"); +} + +static bool +disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { + sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME); + return sc_process_check_success_intr(intr, pid, "adb reverse --remove"); +} + +static bool +enable_tunnel_forward(struct sc_intr *intr, const char *serial, + uint16_t local_port) { + sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME); + return sc_process_check_success_intr(intr, pid, "adb forward"); +} + +static bool +disable_tunnel_forward(struct sc_intr *intr, const char *serial, + uint16_t local_port) { + sc_pid pid = adb_forward_remove(serial, local_port); + return sc_process_check_success_intr(intr, pid, "adb forward --remove"); +} + +static bool +listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { + return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); +} + +static bool +enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, + struct sc_intr *intr, const char *serial, + struct sc_port_range port_range) { + uint16_t port = port_range.first; + for (;;) { + if (!enable_tunnel_reverse(intr, serial, port)) { + // the command itself failed, it will fail on any port + return false; + } + + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at the network level, the + // client listens and the server connects to the client. That way, the + // client can listen before starting the server app, so there is no + // need to try to connect until the server socket is listening on the + // device. + sc_socket server_socket = net_socket(); + if (server_socket != SC_INVALID_SOCKET) { + bool ok = listen_on_port(intr, server_socket, port); + if (ok) { + // success + tunnel->server_socket = server_socket; + tunnel->local_port = port; + tunnel->enabled = true; + return true; + } + + net_close(server_socket); + } + + if (sc_intr_is_interrupted(intr)) { + // Stop immediately + return false; + } + + // failure, disable tunnel and try another port + if (!disable_tunnel_reverse(intr, serial)) { + LOGW("Could not remove reverse tunnel on port %" PRIu16, port); + } + + // check before incrementing to avoid overflow on port 65535 + if (port < port_range.last) { + LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16, + port, (uint16_t) (port + 1)); + port++; + continue; + } + + if (port_range.first == port_range.last) { + LOGE("Could not listen on port %" PRIu16, port_range.first); + } else { + LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16, + port_range.first, port_range.last); + } + return false; + } +} + +static bool +enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, + struct sc_intr *intr, const char *serial, + struct sc_port_range port_range) { + tunnel->forward = true; + + uint16_t port = port_range.first; + for (;;) { + if (enable_tunnel_forward(intr, serial, port)) { + // success + tunnel->local_port = port; + tunnel->enabled = true; + return true; + } + + if (sc_intr_is_interrupted(intr)) { + // Stop immediately + return false; + } + + if (port < port_range.last) { + LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16, + port, (uint16_t) (port + 1)); + port++; + continue; + } + + if (port_range.first == port_range.last) { + LOGE("Could not forward port %" PRIu16, port_range.first); + } else { + LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16, + port_range.first, port_range.last); + } + return false; + } +} + +void +sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) { + tunnel->enabled = false; + tunnel->forward = false; + tunnel->server_socket = SC_INVALID_SOCKET; + tunnel->local_port = 0; +} + +bool +sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, + const char *serial, struct sc_port_range port_range, + bool force_adb_forward) { + assert(!tunnel->enabled); + + if (!force_adb_forward) { + // Attempt to use "adb reverse" + if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) { + return true; + } + + // if "adb reverse" does not work (e.g. over "adb connect"), it + // fallbacks to "adb forward", so the app socket is the client + + LOGW("'adb reverse' failed, fallback to 'adb forward'"); + } + + return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range); +} + +bool +sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, + const char *serial) { + assert(tunnel->enabled); + + bool ret; + if (tunnel->forward) { + ret = disable_tunnel_forward(intr, serial, tunnel->local_port); + } else { + ret = disable_tunnel_reverse(intr, serial); + + assert(tunnel->server_socket != SC_INVALID_SOCKET); + if (!net_close(tunnel->server_socket)) { + LOGW("Could not close server socket"); + } + + // server_socket is never used anymore + } + + // Consider tunnel disabled even if the command failed + tunnel->enabled = false; + + return ret; +} diff --git a/app/src/adb_tunnel.h b/app/src/adb_tunnel.h new file mode 100644 index 00000000..12e3cf17 --- /dev/null +++ b/app/src/adb_tunnel.h @@ -0,0 +1,47 @@ +#ifndef SC_ADB_TUNNEL_H +#define SC_ADB_TUNNEL_H + +#include "common.h" + +#include +#include + +#include "options.h" +#include "util/intr.h" +#include "util/net.h" + +struct sc_adb_tunnel { + bool enabled; + bool forward; // use "adb forward" instead of "adb reverse" + sc_socket server_socket; // only used if !forward + uint16_t local_port; +}; + +/** + * Initialize the adb tunnel struct to default values + */ +void +sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel); + +/** + * Open a tunnel + * + * Blocking calls may be interrupted asynchronously via `intr`. + * + * If `force_adb_forward` is not set, then attempts to set up an "adb reverse" + * tunnel first. Only if it fails (typical on old Android version connected via + * TCP/IP), use "adb forward". + */ +bool +sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, + const char *serial, struct sc_port_range port_range, + bool force_adb_forward); + +/** + * Close the tunnel + */ +bool +sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, + const char *serial); + +#endif diff --git a/app/src/server.c b/app/src/server.c index 7a1f2abc..923e3a76 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -14,7 +14,6 @@ #include "util/process_intr.h" #include "util/str_util.h" -#define SOCKET_NAME "scrcpy" #define SERVER_FILENAME "scrcpy-server" #define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME @@ -117,167 +116,6 @@ push_server(struct sc_intr *intr, const char *serial) { return sc_process_check_success_intr(intr, pid, "adb push"); } -static bool -enable_tunnel_reverse(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port); - return sc_process_check_success_intr(intr, pid, "adb reverse"); -} - -static bool -disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { - sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb reverse --remove"); -} - -static bool -enable_tunnel_forward(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb forward"); -} - -static bool -disable_tunnel_forward(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - sc_pid pid = adb_forward_remove(serial, local_port); - return sc_process_check_success_intr(intr, pid, "adb forward --remove"); -} - -static bool -disable_tunnel(struct server *server) { - assert(server->tunnel_enabled); - - const char *serial = server->params.serial; - bool ok = server->tunnel_forward - ? disable_tunnel_forward(&server->intr, serial, server->local_port) - : disable_tunnel_reverse(&server->intr, serial); - - // Consider tunnel disabled even if the command failed - server->tunnel_enabled = false; - - return ok; -} - -static bool -listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { - return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); -} - -static bool -enable_tunnel_reverse_any_port(struct 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(&server->intr, serial, port)) { - // the command itself failed, it will fail on any port - return false; - } - - // At the application level, the device part is "the server" because it - // serves video stream and control. However, at the network level, the - // client listens and the server connects to the client. That way, the - // client can listen before starting the server app, so there is no - // need to try to connect until the server socket is listening on the - // device. - sc_socket server_socket = net_socket(); - if (server_socket != SC_INVALID_SOCKET) { - bool ok = listen_on_port(&server->intr, server_socket, port); - if (ok) { - // success - server->server_socket = server_socket; - server->local_port = port; - server->tunnel_enabled = true; - return true; - } - - net_close(server_socket); - } - - if (sc_intr_is_interrupted(&server->intr)) { - // Stop immediately - return false; - } - - // failure, disable tunnel and try another port - if (!disable_tunnel_reverse(&server->intr, serial)) { - LOGW("Could not remove reverse tunnel on port %" PRIu16, port); - } - - // check before incrementing to avoid overflow on port 65535 - if (port < port_range.last) { - LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16, - port, (uint16_t) (port + 1)); - port++; - continue; - } - - if (port_range.first == port_range.last) { - LOGE("Could not listen on port %" PRIu16, port_range.first); - } else { - LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16, - port_range.first, port_range.last); - } - return false; - } -} - -static bool -enable_tunnel_forward_any_port(struct 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(&server->intr, serial, port)) { - // success - server->local_port = port; - server->tunnel_enabled = true; - return true; - } - - if (sc_intr_is_interrupted(&server->intr)) { - // Stop immediately - return false; - } - - if (port < port_range.last) { - LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16, - port, (uint16_t) (port + 1)); - port++; - continue; - } - - if (port_range.first == port_range.last) { - LOGE("Could not forward port %" PRIu16, port_range.first); - } else { - LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16, - port_range.first, port_range.last); - } - return false; - } -} - -static bool -enable_tunnel_any_port(struct server *server, struct sc_port_range port_range, - bool force_adb_forward) { - if (!force_adb_forward) { - // Attempt to use "adb reverse" - if (enable_tunnel_reverse_any_port(server, port_range)) { - return true; - } - - // if "adb reverse" does not work (e.g. over "adb connect"), it - // fallbacks to "adb forward", so the app socket is the client - - LOGW("'adb reverse' failed, fallback to 'adb forward'"); - } - - return enable_tunnel_forward_any_port(server, port_range); -} - static const char * log_level_to_server_string(enum sc_log_level level) { switch (level) { @@ -336,7 +174,7 @@ execute_server(struct server *server, const struct server_params *params) { bit_rate_string, max_fps_string, lock_video_orientation_string, - server->tunnel_forward ? "true" : "false", + server->tunnel.forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) params->control ? "true" : "false", @@ -381,7 +219,7 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { static sc_socket connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { - uint16_t port = server->local_port; + uint16_t port = server->tunnel.local_port; do { LOGD("Remaining connection attempts: %d", (int) attempts); sc_socket socket = net_socket(); @@ -449,14 +287,10 @@ server_init(struct server *server, const struct server_params *params, server->stopped = false; - server->server_socket = SC_INVALID_SOCKET; server->video_socket = SC_INVALID_SOCKET; server->control_socket = SC_INVALID_SOCKET; - server->local_port = 0; - - server->tunnel_enabled = false; - server->tunnel_forward = false; + sc_adb_tunnel_init(&server->tunnel); assert(cbs); assert(cbs->on_connection_failed); @@ -491,27 +325,24 @@ device_read_info(struct sc_intr *intr, sc_socket device_socket, static bool server_connect_to(struct server *server, struct server_info *info) { - assert(server->tunnel_enabled); + struct sc_adb_tunnel *tunnel = &server->tunnel; + + assert(tunnel->enabled); + + const char *serial = server->params.serial; sc_socket video_socket = SC_INVALID_SOCKET; sc_socket control_socket = SC_INVALID_SOCKET; - if (!server->tunnel_forward) { - video_socket = net_accept_intr(&server->intr, server->server_socket); + if (!tunnel->forward) { + video_socket = net_accept_intr(&server->intr, tunnel->server_socket); if (video_socket == SC_INVALID_SOCKET) { goto fail; } - control_socket = net_accept_intr(&server->intr, server->server_socket); + control_socket = net_accept_intr(&server->intr, tunnel->server_socket); if (control_socket == SC_INVALID_SOCKET) { goto fail; } - - // we don't need the server socket anymore - if (!net_close(server->server_socket)) { - LOGW("Could not close server socket on connect"); - } - - // server_socket is never used anymore } else { uint32_t attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); @@ -526,14 +357,14 @@ server_connect_to(struct server *server, struct server_info *info) { goto fail; } bool ok = net_connect_intr(&server->intr, control_socket, - IPV4_LOCALHOST, server->local_port); + IPV4_LOCALHOST, tunnel->local_port); if (!ok) { goto fail; } } // we don't need the adb tunnel anymore - disable_tunnel(server); // ignore failure + sc_adb_tunnel_close(tunnel, &server->intr, serial); // The sockets will be closed on stop if device_read_info() fails bool ok = device_read_info(&server->intr, video_socket, info); @@ -563,7 +394,7 @@ fail: } // Always leave this function with tunnel disabled - disable_tunnel(server); + sc_adb_tunnel_close(tunnel, &server->intr, serial); return false; } @@ -594,8 +425,8 @@ run_server(void *data) { goto error_connection_failed; } - ok = enable_tunnel_any_port(server, params->port_range, - params->force_adb_forward); + ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial, + params->port_range, params->force_adb_forward); if (!ok) { goto error_connection_failed; } @@ -603,7 +434,7 @@ run_server(void *data) { // server will connect to our server socket sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { - disable_tunnel(server); + sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); goto error_connection_failed; } @@ -615,7 +446,7 @@ run_server(void *data) { if (!ok) { sc_process_terminate(pid); sc_process_wait(pid, true); // ignore exit code - disable_tunnel(server); + sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); goto error_connection_failed; } diff --git a/app/src/server.h b/app/src/server.h index b021ba32..cb2cf3a8 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -8,6 +8,7 @@ #include #include "adb.h" +#include "adb_tunnel.h" #include "coords.h" #include "options.h" #include "util/intr.h" @@ -52,13 +53,10 @@ struct server { bool stopped; struct sc_intr intr; + struct sc_adb_tunnel tunnel; - 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; From 9a0bd545d533d13fda8823481330329d6865cb2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 09:58:52 +0100 Subject: [PATCH 0093/1133] Rename SC_INVALID_SOCKET to SC_SOCKET_NONE For consistency with SC_PROCESS_NONE. --- app/src/adb_tunnel.c | 6 +++--- app/src/server.c | 28 ++++++++++++++-------------- app/src/util/intr.c | 12 ++++++------ app/src/util/intr.h | 2 +- app/src/util/net.c | 10 +++++----- app/src/util/net.h | 4 ++-- app/src/util/net_intr.c | 16 ++++++++-------- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index d1a19afc..f02eb83e 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -59,7 +59,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, // need to try to connect until the server socket is listening on the // device. sc_socket server_socket = net_socket(); - if (server_socket != SC_INVALID_SOCKET) { + if (server_socket != SC_SOCKET_NONE) { bool ok = listen_on_port(intr, server_socket, port); if (ok) { // success @@ -141,7 +141,7 @@ void sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) { tunnel->enabled = false; tunnel->forward = false; - tunnel->server_socket = SC_INVALID_SOCKET; + tunnel->server_socket = SC_SOCKET_NONE; tunnel->local_port = 0; } @@ -177,7 +177,7 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, } else { ret = disable_tunnel_reverse(intr, serial); - assert(tunnel->server_socket != SC_INVALID_SOCKET); + assert(tunnel->server_socket != SC_SOCKET_NONE); if (!net_close(tunnel->server_socket)) { LOGW("Could not close server socket"); } diff --git a/app/src/server.c b/app/src/server.c index 923e3a76..d6b143aa 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -223,7 +223,7 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { do { LOGD("Remaining connection attempts: %d", (int) attempts); sc_socket socket = net_socket(); - if (socket != SC_INVALID_SOCKET) { + if (socket != SC_SOCKET_NONE) { bool ok = connect_and_read_byte(&server->intr, socket, port); if (ok) { // it worked! @@ -249,7 +249,7 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { } } } while (--attempts > 0); - return SC_INVALID_SOCKET; + return SC_SOCKET_NONE; } bool @@ -287,8 +287,8 @@ server_init(struct server *server, const struct server_params *params, server->stopped = false; - server->video_socket = SC_INVALID_SOCKET; - server->control_socket = SC_INVALID_SOCKET; + server->video_socket = SC_SOCKET_NONE; + server->control_socket = SC_SOCKET_NONE; sc_adb_tunnel_init(&server->tunnel); @@ -331,29 +331,29 @@ server_connect_to(struct server *server, struct server_info *info) { const char *serial = server->params.serial; - sc_socket video_socket = SC_INVALID_SOCKET; - sc_socket control_socket = SC_INVALID_SOCKET; + sc_socket video_socket = SC_SOCKET_NONE; + sc_socket control_socket = SC_SOCKET_NONE; if (!tunnel->forward) { video_socket = net_accept_intr(&server->intr, tunnel->server_socket); - if (video_socket == SC_INVALID_SOCKET) { + if (video_socket == SC_SOCKET_NONE) { goto fail; } control_socket = net_accept_intr(&server->intr, tunnel->server_socket); - if (control_socket == SC_INVALID_SOCKET) { + if (control_socket == SC_SOCKET_NONE) { goto fail; } } else { uint32_t attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); video_socket = connect_to_server(server, attempts, delay); - if (video_socket == SC_INVALID_SOCKET) { + if (video_socket == SC_SOCKET_NONE) { goto fail; } // we know that the device is listening, we don't need several attempts control_socket = net_socket(); - if (control_socket == SC_INVALID_SOCKET) { + if (control_socket == SC_SOCKET_NONE) { goto fail; } bool ok = net_connect_intr(&server->intr, control_socket, @@ -372,8 +372,8 @@ server_connect_to(struct server *server, struct server_info *info) { goto fail; } - assert(video_socket != SC_INVALID_SOCKET); - assert(control_socket != SC_INVALID_SOCKET); + assert(video_socket != SC_SOCKET_NONE); + assert(control_socket != SC_SOCKET_NONE); server->video_socket = video_socket; server->control_socket = control_socket; @@ -381,13 +381,13 @@ server_connect_to(struct server *server, struct server_info *info) { return true; fail: - if (video_socket != SC_INVALID_SOCKET) { + if (video_socket != SC_SOCKET_NONE) { if (!net_close(video_socket)) { LOGW("Could not close video socket"); } } - if (control_socket != SC_INVALID_SOCKET) { + if (control_socket != SC_SOCKET_NONE) { if (!net_close(control_socket)) { LOGW("Could not close control socket"); } diff --git a/app/src/util/intr.c b/app/src/util/intr.c index d4fce55c..50d9abbe 100644 --- a/app/src/util/intr.c +++ b/app/src/util/intr.c @@ -12,7 +12,7 @@ sc_intr_init(struct sc_intr *intr) { return false; } - intr->socket = SC_INVALID_SOCKET; + intr->socket = SC_SOCKET_NONE; intr->process = SC_PROCESS_NONE; atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed); @@ -37,7 +37,7 @@ sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) { bool sc_intr_set_process(struct sc_intr *intr, sc_pid pid) { - assert(intr->socket == SC_INVALID_SOCKET); + assert(intr->socket == SC_SOCKET_NONE); sc_mutex_lock(&intr->mutex); bool interrupted = @@ -57,13 +57,13 @@ sc_intr_interrupt(struct sc_intr *intr) { atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed); // No more than one component to interrupt - assert(intr->socket == SC_INVALID_SOCKET || + assert(intr->socket == SC_SOCKET_NONE || intr->process == SC_PROCESS_NONE); - if (intr->socket != SC_INVALID_SOCKET) { + if (intr->socket != SC_SOCKET_NONE) { LOGD("Interrupting socket"); net_interrupt(intr->socket); - intr->socket = SC_INVALID_SOCKET; + intr->socket = SC_SOCKET_NONE; } if (intr->process != SC_PROCESS_NONE) { LOGD("Interrupting process"); @@ -76,7 +76,7 @@ sc_intr_interrupt(struct sc_intr *intr) { void sc_intr_destroy(struct sc_intr *intr) { - assert(intr->socket == SC_INVALID_SOCKET); + assert(intr->socket == SC_SOCKET_NONE); assert(intr->process == SC_PROCESS_NONE); sc_mutex_destroy(&intr->mutex); diff --git a/app/src/util/intr.h b/app/src/util/intr.h index 7f0fb9b7..1c20f6df 100644 --- a/app/src/util/intr.h +++ b/app/src/util/intr.h @@ -37,7 +37,7 @@ sc_intr_init(struct sc_intr *intr); /** * Set a socket as the interruptible component * - * Call with SC_INVALID_SOCKET to unset. + * Call with SC_SOCKET_NONE to unset. */ bool sc_intr_set_socket(struct sc_intr *intr, sc_socket socket); diff --git a/app/src/util/net.c b/app/src/util/net.c index 6bfe7a52..8595bc79 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -46,13 +46,13 @@ static inline sc_socket wrap(sc_raw_socket sock) { #ifdef __WINDOWS__ if (sock == INVALID_SOCKET) { - return SC_INVALID_SOCKET; + return SC_SOCKET_NONE; } struct sc_socket_windows *socket = malloc(sizeof(*socket)); if (!socket) { closesocket(sock); - return SC_INVALID_SOCKET; + return SC_SOCKET_NONE; } socket->socket = sock; @@ -67,7 +67,7 @@ wrap(sc_raw_socket sock) { static inline sc_raw_socket unwrap(sc_socket socket) { #ifdef __WINDOWS__ - if (socket == SC_INVALID_SOCKET) { + if (socket == SC_SOCKET_NONE) { return INVALID_SOCKET; } @@ -97,7 +97,7 @@ sc_socket net_socket(void) { sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0); sc_socket sock = wrap(raw_sock); - if (sock == SC_INVALID_SOCKET) { + if (sock == SC_SOCKET_NONE) { net_perror("socket"); } return sock; @@ -195,7 +195,7 @@ net_send_all(sc_socket socket, const void *buf, size_t len) { bool net_interrupt(sc_socket socket) { - assert(socket != SC_INVALID_SOCKET); + assert(socket != SC_SOCKET_NONE); sc_raw_socket raw_sock = unwrap(socket); diff --git a/app/src/util/net.h b/app/src/util/net.h index 7a9fbe47..57fd6c5e 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -11,7 +11,7 @@ # include # include -# define SC_INVALID_SOCKET NULL +# define SC_SOCKET_NONE NULL typedef struct sc_socket_windows { SOCKET socket; atomic_flag closed; @@ -20,7 +20,7 @@ #else // not __WINDOWS__ # include -# define SC_INVALID_SOCKET -1 +# define SC_SOCKET_NONE -1 typedef int sc_socket; #endif diff --git a/app/src/util/net_intr.c b/app/src/util/net_intr.c index b9443e2f..bb70010b 100644 --- a/app/src/util/net_intr.c +++ b/app/src/util/net_intr.c @@ -10,7 +10,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, bool ret = net_connect(socket, addr, port); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return ret; } @@ -24,7 +24,7 @@ net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, bool ret = net_listen(socket, addr, port, backlog); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return ret; } @@ -32,12 +32,12 @@ sc_socket net_accept_intr(struct sc_intr *intr, sc_socket server_socket) { if (!sc_intr_set_socket(intr, server_socket)) { // Already interrupted - return SC_INVALID_SOCKET; + return SC_SOCKET_NONE; } sc_socket socket = net_accept(server_socket); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return socket; } @@ -50,7 +50,7 @@ net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) { ssize_t r = net_recv(socket, buf, len); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return r; } @@ -64,7 +64,7 @@ net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf, ssize_t r = net_recv_all(socket, buf, len); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return r; } @@ -78,7 +78,7 @@ net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf, ssize_t w = net_send(socket, buf, len); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return w; } @@ -92,6 +92,6 @@ net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf, ssize_t w = net_send_all(socket, buf, len); - sc_intr_set_socket(intr, SC_INVALID_SOCKET); + sc_intr_set_socket(intr, SC_SOCKET_NONE); return w; } From 979ce64dc091bc54a0ac8612a0368f5163500f39 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 23:08:19 +0100 Subject: [PATCH 0094/1133] Improve string util API Use prefixed names and improve documentation. --- app/src/adb.c | 6 +- app/src/cli.c | 6 +- app/src/control_msg.c | 2 +- app/src/icon.c | 2 +- app/src/recorder.c | 2 +- app/src/server.c | 2 +- app/src/sys/win/file.c | 4 +- app/src/sys/win/process.c | 4 +- app/src/util/str_util.c | 26 +++---- app/src/util/str_util.h | 107 +++++++++++++++++++---------- app/src/v4l2_sink.c | 2 +- app/tests/test_strutil.c | 140 +++++++++++++++++++------------------- 12 files changed, 169 insertions(+), 134 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index c7a64501..39777f12 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -190,11 +190,11 @@ adb_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) - local = strquote(local); + local = sc_str_quote(local); if (!local) { return SC_PROCESS_NONE; } - remote = strquote(remote); + remote = sc_str_quote(remote); if (!remote) { free((void *) local); return SC_PROCESS_NONE; @@ -217,7 +217,7 @@ adb_install(const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) - local = strquote(local); + local = sc_str_quote(local); if (!local) { return SC_PROCESS_NONE; } diff --git a/app/src/cli.c b/app/src/cli.c index e38ff9f6..d46485b2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -779,9 +779,9 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min, long value; bool ok; if (accept_suffix) { - ok = parse_integer_with_suffix(s, &value); + ok = sc_str_parse_integer_with_suffix(s, &value); } else { - ok = parse_integer(s, &value); + ok = sc_str_parse_integer(s, &value); } if (!ok) { LOGE("Could not parse %s: %s", name, s); @@ -801,7 +801,7 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min, static size_t parse_integers_arg(const char *s, size_t max_items, long *out, long min, long max, const char *name) { - size_t count = parse_integers(s, ':', max_items, out); + size_t count = sc_str_parse_integers(s, ':', max_items, out); if (!count) { LOGE("Could not parse %s: %s", name, s); return 0; diff --git a/app/src/control_msg.c b/app/src/control_msg.c index a6a020bd..ea552bc2 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -66,7 +66,7 @@ write_position(uint8_t *buf, const struct sc_position *position) { // write length (2 bytes) + string (non nul-terminated) static size_t write_string(const char *utf8, size_t max_len, unsigned char *buf) { - size_t len = utf8_truncation_index(utf8, max_len); + size_t len = sc_str_utf8_truncation_index(utf8, max_len); buffer_write32be(buf, len); memcpy(&buf[4], utf8, len); return 4 + len; diff --git a/app/src/icon.c b/app/src/icon.c index f9799de7..8e18b102 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -26,7 +26,7 @@ get_icon_path(void) { if (icon_path_env) { // if the envvar is set, use it #ifdef __WINDOWS__ - char *icon_path = utf8_from_wide_char(icon_path_env); + char *icon_path = sc_str_from_wchars(icon_path_env); #else char *icon_path = strdup(icon_path_env); #endif diff --git a/app/src/recorder.c b/app/src/recorder.c index a690e2de..20e4340f 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -26,7 +26,7 @@ find_muxer(const char *name) { oformat = av_oformat_next(oformat); #endif // until null or containing the requested name - } while (oformat && !strlist_contains(oformat->name, ',', name)); + } while (oformat && !sc_str_list_contains(oformat->name, ',', name)); return oformat; } diff --git a/app/src/server.c b/app/src/server.c index d6b143aa..0edd8a05 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -29,7 +29,7 @@ get_server_path(void) { if (server_path_env) { // if the envvar is set, use it #ifdef __WINDOWS__ - char *server_path = utf8_from_wide_char(server_path_env); + char *server_path = sc_str_from_wchars(server_path_env); #else char *server_path = strdup(server_path_env); #endif diff --git a/app/src/sys/win/file.c b/app/src/sys/win/file.c index badb8087..650e0b4b 100644 --- a/app/src/sys/win/file.c +++ b/app/src/sys/win/file.c @@ -19,12 +19,12 @@ sc_file_get_executable_path(void) { return NULL; } buf[len] = '\0'; - return utf8_from_wide_char(buf); + return sc_str_from_wchars(buf); } bool sc_file_is_regular(const char *path) { - wchar_t *wide_path = utf8_to_wide_char(path); + wchar_t *wide_path = sc_str_to_wchars(path); if (!wide_path) { LOGC("Could not allocate wide char string"); return false; diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 971806d6..cfbb3948 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -13,7 +13,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { // // only make it work for this very specific program // (don't handle escaping nor quotes) - size_t ret = xstrjoin(cmd, argv, ' ', len); + size_t ret = sc_str_join(cmd, argv, ' ', len); if (ret >= len) { LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1); return false; @@ -88,7 +88,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, goto error_close_stderr; } - wchar_t *wide = utf8_to_wide_char(cmd); + wchar_t *wide = sc_str_to_wchars(cmd); free(cmd); if (!wide) { LOGC("Could not allocate wide char string"); diff --git a/app/src/util/str_util.c b/app/src/util/str_util.c index b4e7cac4..d15dad20 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str_util.c @@ -13,7 +13,7 @@ #endif size_t -xstrncpy(char *dest, const char *src, size_t n) { +sc_strncpy(char *dest, const char *src, size_t n) { size_t i; for (i = 0; i < n - 1 && src[i] != '\0'; ++i) dest[i] = src[i]; @@ -23,7 +23,7 @@ xstrncpy(char *dest, const char *src, size_t n) { } size_t -xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { +sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) { const char *const *remaining = tokens; const char *token = *remaining++; size_t i = 0; @@ -33,7 +33,7 @@ xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) { if (i == n) goto truncated; } - size_t w = xstrncpy(dst + i, token, n - i); + size_t w = sc_strncpy(dst + i, token, n - i); if (w >= n - i) goto truncated; i += w; @@ -47,7 +47,7 @@ truncated: } char * -strquote(const char *src) { +sc_str_quote(const char *src) { size_t len = strlen(src); char *quoted = malloc(len + 3); if (!quoted) { @@ -61,7 +61,7 @@ strquote(const char *src) { } bool -parse_integer(const char *s, long *out) { +sc_str_parse_integer(const char *s, long *out) { char *endptr; if (*s == '\0') { return false; @@ -80,7 +80,8 @@ parse_integer(const char *s, long *out) { } size_t -parse_integers(const char *s, const char sep, size_t max_items, long *out) { +sc_str_parse_integers(const char *s, const char sep, size_t max_items, + long *out) { size_t count = 0; char *endptr; do { @@ -109,7 +110,7 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out) { } bool -parse_integer_with_suffix(const char *s, long *out) { +sc_str_parse_integer_with_suffix(const char *s, long *out) { char *endptr; if (*s == '\0') { return false; @@ -143,7 +144,7 @@ parse_integer_with_suffix(const char *s, long *out) { } bool -strlist_contains(const char *list, char sep, const char *s) { +sc_str_list_contains(const char *list, char sep, const char *s) { char *p; do { p = strchr(list, sep); @@ -161,7 +162,7 @@ strlist_contains(const char *list, char sep, const char *s) { } size_t -utf8_truncation_index(const char *utf8, size_t max_len) { +sc_str_utf8_truncation_index(const char *utf8, size_t max_len) { size_t len = strlen(utf8); if (len <= max_len) { return len; @@ -179,7 +180,7 @@ utf8_truncation_index(const char *utf8, size_t max_len) { #ifdef _WIN32 wchar_t * -utf8_to_wide_char(const char *utf8) { +sc_str_to_wchars(const char *utf8) { int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); if (!len) { return NULL; @@ -195,7 +196,7 @@ utf8_to_wide_char(const char *utf8) { } char * -utf8_from_wide_char(const wchar_t *ws) { +sc_str_from_wchars(const wchar_t *ws) { int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); if (!len) { return NULL; @@ -212,7 +213,8 @@ utf8_from_wide_char(const wchar_t *ws) { #endif -char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) { +char * +sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) { assert(indent < columns); struct sc_strbuf buf; diff --git a/app/src/util/str_util.h b/app/src/util/str_util.h index 344522c9..47e01344 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str_util.h @@ -1,65 +1,97 @@ -#ifndef STRUTIL_H -#define STRUTIL_H +#ifndef SC_STRUTIL_H +#define SC_STRUTIL_H #include "common.h" #include #include -// like strncpy, except: -// - it copies at most n-1 chars -// - the dest string is nul-terminated -// - it does not write useless bytes if strlen(src) < n -// - it returns the number of chars actually written (max n-1) if src has -// been copied completely, or n if src has been truncated +/** + * Like strncpy(), except: + * - it copies at most n-1 chars + * - the dest string is nul-terminated + * - it does not write useless bytes if strlen(src) < n + * - it returns the number of chars actually written (max n-1) if src has + * been copied completely, or n if src has been truncated + */ size_t -xstrncpy(char *dest, const char *src, size_t n); +sc_strncpy(char *dest, const char *src, size_t n); -// join tokens by sep into dst -// returns the number of chars actually written (max n-1) if no truncation -// occurred, or n if truncated +/** + * Join tokens by separator `sep` into `dst` + * + * Return the number of chars actually written (max n-1) if no truncation + * occurred, or n if truncated. + */ size_t -xstrjoin(char *dst, const char *const tokens[], char sep, size_t n); +sc_str_join(char *dst, const char *const tokens[], char sep, size_t n); -// quote a string -// returns the new allocated string, to be freed by the caller +/** + * Quote a string + * + * Return a new allocated string, surrounded with quotes (`"`). + */ char * -strquote(const char *src); +sc_str_quote(const char *src); -// parse s as an integer into value -// returns true if the conversion succeeded, false otherwise +/** + * Parse `s` as an integer into `out` + * + * Return true if the conversion succeeded, false otherwise. + */ bool -parse_integer(const char *s, long *out); +sc_str_parse_integer(const char *s, long *out); -// parse s as integers separated by sep (for example '1234:2000') -// returns the number of integers on success, 0 on failure +/** + * Parse `s` as integers separated by `sep` (for example `1234:2000`) into `out` + * + * Returns the number of integers on success, 0 on failure. + */ size_t -parse_integers(const char *s, const char sep, size_t max_items, long *out); +sc_str_parse_integers(const char *s, const char sep, size_t max_items, + long *out); -// parse s as an integer into value -// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as -// suffix -// returns true if the conversion succeeded, false otherwise +/** + * Parse `s` as an integer into `out` + * + * Like `sc_str_parse_integer()`, but accept 'k'/'K' (x1000) and 'm'/'M' + * (x1000000) as suffixes. + * + * Return true if the conversion succeeded, false otherwise. + */ bool -parse_integer_with_suffix(const char *s, long *out); +sc_str_parse_integer_with_suffix(const char *s, long *out); -// search s in the list separated by sep -// for example, strlist_contains("a,bc,def", ',', "bc") returns true +/** + * Search `s` in the list separated by `sep` + * + * For example, sc_str_list_contains("a,bc,def", ',', "bc") returns true. + */ bool -strlist_contains(const char *list, char sep, const char *s); +sc_str_list_contains(const char *list, char sep, const char *s); -// return the index to truncate a UTF-8 string at a valid position +/** + * Return the index to truncate a UTF-8 string at a valid position + */ size_t -utf8_truncation_index(const char *utf8, size_t max_len); +sc_str_utf8_truncation_index(const char *utf8, size_t max_len); #ifdef _WIN32 -// convert a UTF-8 string to a wchar_t string -// returns the new allocated string, to be freed by the caller +/** + * Convert a UTF-8 string to a wchar_t string + * + * Return the new allocated string, to be freed by the caller. + */ wchar_t * -utf8_to_wide_char(const char *utf8); +sc_str_to_wchars(const char *utf8); +/** + * Convert a wchar_t string to a UTF-8 string + * + * Return the new allocated string, to be freed by the caller. + */ char * -utf8_from_wide_char(const wchar_t *s); +sc_str_from_wchars(const wchar_t *s); #endif /** @@ -68,6 +100,7 @@ utf8_from_wide_char(const wchar_t *s); * Break input lines at word boundaries (spaces) so that they fit in `columns` * columns, left-indented by `indent` spaces. */ -char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); +char * +sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); #endif diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 48d90364..a35a9096 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -21,7 +21,7 @@ find_muxer(const char *name) { oformat = av_oformat_next(oformat); #endif // until null or containing the requested name - } while (oformat && !strlist_contains(oformat->name, ',', name)); + } while (oformat && !sc_str_list_contains(oformat->name, ',', name)); return oformat; } diff --git a/app/tests/test_strutil.c b/app/tests/test_strutil.c index 2d23176e..6a53f94b 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_strutil.c @@ -7,9 +7,9 @@ #include "util/str_util.h" -static void test_xstrncpy_simple(void) { +static void test_strncpy_simple(void) { char s[] = "xxxxxxxxxx"; - size_t w = xstrncpy(s, "abcdef", sizeof(s)); + size_t w = sc_strncpy(s, "abcdef", sizeof(s)); // returns strlen of copied string assert(w == 6); @@ -24,9 +24,9 @@ static void test_xstrncpy_simple(void) { assert(!strcmp("abcdef", s)); } -static void test_xstrncpy_just_fit(void) { +static void test_strncpy_just_fit(void) { char s[] = "xxxxxx"; - size_t w = xstrncpy(s, "abcdef", sizeof(s)); + size_t w = sc_strncpy(s, "abcdef", sizeof(s)); // returns strlen of copied string assert(w == 6); @@ -38,9 +38,9 @@ static void test_xstrncpy_just_fit(void) { assert(!strcmp("abcdef", s)); } -static void test_xstrncpy_truncated(void) { +static void test_strncpy_truncated(void) { char s[] = "xxx"; - size_t w = xstrncpy(s, "abcdef", sizeof(s)); + size_t w = sc_strncpy(s, "abcdef", sizeof(s)); // returns 'n' (sizeof(s)) assert(w == 4); @@ -52,10 +52,10 @@ static void test_xstrncpy_truncated(void) { assert(!strncmp("abcdef", s, 3)); } -static void test_xstrjoin_simple(void) { +static void test_join_simple(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxxxxxxxxx"; - size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); + size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); // returns strlen of concatenation assert(w == 11); @@ -70,10 +70,10 @@ static void test_xstrjoin_simple(void) { assert(!strcmp("abc de fghi", s)); } -static void test_xstrjoin_just_fit(void) { +static void test_join_just_fit(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxxxxxx"; - size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); + size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); // returns strlen of concatenation assert(w == 11); @@ -85,10 +85,10 @@ static void test_xstrjoin_just_fit(void) { assert(!strcmp("abc de fghi", s)); } -static void test_xstrjoin_truncated_in_token(void) { +static void test_join_truncated_in_token(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxx"; - size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); + size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); // returns 'n' (sizeof(s)) assert(w == 6); @@ -100,10 +100,10 @@ static void test_xstrjoin_truncated_in_token(void) { assert(!strcmp("abc d", s)); } -static void test_xstrjoin_truncated_before_sep(void) { +static void test_join_truncated_before_sep(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxx"; - size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); + size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); // returns 'n' (sizeof(s)) assert(w == 7); @@ -115,10 +115,10 @@ static void test_xstrjoin_truncated_before_sep(void) { assert(!strcmp("abc de", s)); } -static void test_xstrjoin_truncated_after_sep(void) { +static void test_join_truncated_after_sep(void) { const char *const tokens[] = { "abc", "de", "fghi", NULL }; char s[] = "xxxxxxx"; - size_t w = xstrjoin(s, tokens, ' ', sizeof(s)); + size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); // returns 'n' (sizeof(s)) assert(w == 8); @@ -130,9 +130,9 @@ static void test_xstrjoin_truncated_after_sep(void) { assert(!strcmp("abc de ", s)); } -static void test_strquote(void) { +static void test_quote(void) { const char *s = "abcde"; - char *out = strquote(s); + char *out = sc_str_quote(s); // add '"' at the beginning and the end assert(!strcmp("\"abcde\"", out)); @@ -146,71 +146,71 @@ static void test_utf8_truncate(void) { size_t count; - count = utf8_truncation_index(s, 1); + count = sc_str_utf8_truncation_index(s, 1); assert(count == 1); - count = utf8_truncation_index(s, 2); + count = sc_str_utf8_truncation_index(s, 2); assert(count == 1); // É is 2 bytes-wide - count = utf8_truncation_index(s, 3); + count = sc_str_utf8_truncation_index(s, 3); assert(count == 3); - count = utf8_truncation_index(s, 4); + count = sc_str_utf8_truncation_index(s, 4); assert(count == 4); - count = utf8_truncation_index(s, 5); + count = sc_str_utf8_truncation_index(s, 5); assert(count == 4); // Ô is 2 bytes-wide - count = utf8_truncation_index(s, 6); + count = sc_str_utf8_truncation_index(s, 6); assert(count == 6); - count = utf8_truncation_index(s, 7); + count = sc_str_utf8_truncation_index(s, 7); assert(count == 7); - count = utf8_truncation_index(s, 8); + count = sc_str_utf8_truncation_index(s, 8); assert(count == 7); // no more chars } static void test_parse_integer(void) { long value; - bool ok = parse_integer("1234", &value); + bool ok = sc_str_parse_integer("1234", &value); assert(ok); assert(value == 1234); - ok = parse_integer("-1234", &value); + ok = sc_str_parse_integer("-1234", &value); assert(ok); assert(value == -1234); - ok = parse_integer("1234k", &value); + ok = sc_str_parse_integer("1234k", &value); assert(!ok); - ok = parse_integer("123456789876543212345678987654321", &value); + ok = sc_str_parse_integer("123456789876543212345678987654321", &value); assert(!ok); // out-of-range } static void test_parse_integers(void) { long values[5]; - size_t count = parse_integers("1234", ':', 5, values); + size_t count = sc_str_parse_integers("1234", ':', 5, values); assert(count == 1); assert(values[0] == 1234); - count = parse_integers("1234:5678", ':', 5, values); + count = sc_str_parse_integers("1234:5678", ':', 5, values); assert(count == 2); assert(values[0] == 1234); assert(values[1] == 5678); - count = parse_integers("1234:5678", ':', 2, values); + count = sc_str_parse_integers("1234:5678", ':', 2, values); assert(count == 2); assert(values[0] == 1234); assert(values[1] == 5678); - count = parse_integers("1234:-5678", ':', 2, values); + count = sc_str_parse_integers("1234:-5678", ':', 2, values); assert(count == 2); assert(values[0] == 1234); assert(values[1] == -5678); - count = parse_integers("1:2:3:4:5", ':', 5, values); + count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values); assert(count == 5); assert(values[0] == 1); assert(values[1] == 2); @@ -218,85 +218,85 @@ static void test_parse_integers(void) { assert(values[3] == 4); assert(values[4] == 5); - count = parse_integers("1234:5678", ':', 1, values); + count = sc_str_parse_integers("1234:5678", ':', 1, values); assert(count == 0); // max_items == 1 - count = parse_integers("1:2:3:4:5", ':', 3, values); + count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values); assert(count == 0); // max_items == 3 - count = parse_integers(":1234", ':', 5, values); + count = sc_str_parse_integers(":1234", ':', 5, values); assert(count == 0); // invalid - count = parse_integers("1234:", ':', 5, values); + count = sc_str_parse_integers("1234:", ':', 5, values); assert(count == 0); // invalid - count = parse_integers("1234:", ':', 1, values); + count = sc_str_parse_integers("1234:", ':', 1, values); assert(count == 0); // invalid, even when max_items == 1 - count = parse_integers("1234::5678", ':', 5, values); + count = sc_str_parse_integers("1234::5678", ':', 5, values); assert(count == 0); // invalid } static void test_parse_integer_with_suffix(void) { long value; - bool ok = parse_integer_with_suffix("1234", &value); + bool ok = sc_str_parse_integer_with_suffix("1234", &value); assert(ok); assert(value == 1234); - ok = parse_integer_with_suffix("-1234", &value); + ok = sc_str_parse_integer_with_suffix("-1234", &value); assert(ok); assert(value == -1234); - ok = parse_integer_with_suffix("1234k", &value); + ok = sc_str_parse_integer_with_suffix("1234k", &value); assert(ok); assert(value == 1234000); - ok = parse_integer_with_suffix("1234m", &value); + ok = sc_str_parse_integer_with_suffix("1234m", &value); assert(ok); assert(value == 1234000000); - ok = parse_integer_with_suffix("-1234k", &value); + ok = sc_str_parse_integer_with_suffix("-1234k", &value); assert(ok); assert(value == -1234000); - ok = parse_integer_with_suffix("-1234m", &value); + ok = sc_str_parse_integer_with_suffix("-1234m", &value); assert(ok); assert(value == -1234000000); - ok = parse_integer_with_suffix("123456789876543212345678987654321", &value); + ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value); assert(!ok); // out-of-range char buf[32]; sprintf(buf, "%ldk", LONG_MAX / 2000); - ok = parse_integer_with_suffix(buf, &value); + ok = sc_str_parse_integer_with_suffix(buf, &value); assert(ok); assert(value == LONG_MAX / 2000 * 1000); sprintf(buf, "%ldm", LONG_MAX / 2000); - ok = parse_integer_with_suffix(buf, &value); + ok = sc_str_parse_integer_with_suffix(buf, &value); assert(!ok); sprintf(buf, "%ldk", LONG_MIN / 2000); - ok = parse_integer_with_suffix(buf, &value); + ok = sc_str_parse_integer_with_suffix(buf, &value); assert(ok); assert(value == LONG_MIN / 2000 * 1000); sprintf(buf, "%ldm", LONG_MIN / 2000); - ok = parse_integer_with_suffix(buf, &value); + ok = sc_str_parse_integer_with_suffix(buf, &value); assert(!ok); } static void test_strlist_contains(void) { - assert(strlist_contains("a,bc,def", ',', "bc")); - assert(!strlist_contains("a,bc,def", ',', "b")); - assert(strlist_contains("", ',', "")); - assert(strlist_contains("abc,", ',', "")); - assert(strlist_contains(",abc", ',', "")); - assert(strlist_contains("abc,,def", ',', "")); - assert(!strlist_contains("abc", ',', "")); - assert(strlist_contains(",,|x", '|', ",,")); - assert(strlist_contains("xyz", '\0', "xyz")); + assert(sc_str_list_contains("a,bc,def", ',', "bc")); + assert(!sc_str_list_contains("a,bc,def", ',', "b")); + assert(sc_str_list_contains("", ',', "")); + assert(sc_str_list_contains("abc,", ',', "")); + assert(sc_str_list_contains(",abc", ',', "")); + assert(sc_str_list_contains("abc,,def", ',', "")); + assert(!sc_str_list_contains("abc", ',', "")); + assert(sc_str_list_contains(",,|x", '|', ",,")); + assert(sc_str_list_contains("xyz", '\0', "xyz")); } static void test_wrap_lines(void) { @@ -341,15 +341,15 @@ int main(int argc, char *argv[]) { (void) argc; (void) argv; - test_xstrncpy_simple(); - test_xstrncpy_just_fit(); - test_xstrncpy_truncated(); - test_xstrjoin_simple(); - test_xstrjoin_just_fit(); - test_xstrjoin_truncated_in_token(); - test_xstrjoin_truncated_before_sep(); - test_xstrjoin_truncated_after_sep(); - test_strquote(); + test_strncpy_simple(); + test_strncpy_just_fit(); + test_strncpy_truncated(); + test_join_simple(); + test_join_just_fit(); + test_join_truncated_in_token(); + test_join_truncated_before_sep(); + test_join_truncated_after_sep(); + test_quote(); test_utf8_truncate(); test_parse_integer(); test_parse_integers(); From 057c7a4df4ec1bd70c660cfe03a590e996c744ad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 23:12:51 +0100 Subject: [PATCH 0095/1133] Move str_util to str Simplify naming. --- app/meson.build | 12 ++++++------ app/src/adb.c | 2 +- app/src/cli.c | 2 +- app/src/control_msg.c | 2 +- app/src/icon.c | 2 +- app/src/recorder.c | 2 +- app/src/server.c | 2 +- app/src/sys/win/file.c | 2 +- app/src/sys/win/process.c | 2 +- app/src/util/{str_util.c => str.c} | 2 +- app/src/util/{str_util.h => str.h} | 4 ++-- app/src/v4l2_sink.c | 2 +- app/tests/{test_strutil.c => test_str.c} | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) rename app/src/util/{str_util.c => str.c} (99%) rename app/src/util/{str_util.h => str.h} (98%) rename app/tests/{test_strutil.c => test_str.c} (99%) diff --git a/app/meson.build b/app/meson.build index 7abef1e2..4894babc 100644 --- a/app/meson.build +++ b/app/meson.build @@ -33,7 +33,7 @@ src = [ 'src/util/process.c', 'src/util/process_intr.c', 'src/util/strbuf.c', - 'src/util/str_util.c', + 'src/util/str.c', 'src/util/term.c', 'src/util/thread.c', 'src/util/tick.c', @@ -199,8 +199,8 @@ if get_option('buildtype') == 'debug' 'tests/test_cli.c', 'src/cli.c', 'src/options.c', + 'src/util/str.c', 'src/util/strbuf.c', - 'src/util/str_util.c', 'src/util/term.c', ]], ['test_clock', [ @@ -210,8 +210,8 @@ if get_option('buildtype') == 'debug' ['test_control_msg_serialize', [ 'tests/test_control_msg_serialize.c', 'src/control_msg.c', + 'src/util/str.c', 'src/util/strbuf.c', - 'src/util/str_util.c', ]], ['test_device_msg_deserialize', [ 'tests/test_device_msg_deserialize.c', @@ -224,10 +224,10 @@ if get_option('buildtype') == 'debug' 'tests/test_strbuf.c', 'src/util/strbuf.c', ]], - ['test_strutil', [ - 'tests/test_strutil.c', + ['test_str', [ + 'tests/test_str.c', + 'src/util/str.c', 'src/util/strbuf.c', - 'src/util/str_util.c', ]], ] diff --git a/app/src/adb.c b/app/src/adb.c index 39777f12..6251174e 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -7,7 +7,7 @@ #include "util/file.h" #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" static const char *adb_command; diff --git a/app/src/cli.c b/app/src/cli.c index d46485b2..1550c706 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -9,8 +9,8 @@ #include "options.h" #include "util/log.h" +#include "util/str.h" #include "util/strbuf.h" -#include "util/str_util.h" #include "util/term.h" #define STR_IMPL_(x) #x diff --git a/app/src/control_msg.c b/app/src/control_msg.c index ea552bc2..74e3315c 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -7,7 +7,7 @@ #include "util/buffer_util.h" #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" /** * Map an enum value to a string based on an array, without crashing on an diff --git a/app/src/icon.c b/app/src/icon.c index 8e18b102..a3efbb01 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -10,7 +10,7 @@ #include "compat.h" #include "util/file.h" #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" #define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" #define SCRCPY_DEFAULT_ICON_PATH \ diff --git a/app/src/recorder.c b/app/src/recorder.c index 20e4340f..b9c585f4 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -6,7 +6,7 @@ #include #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" /** Downcast packet_sink to recorder */ #define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) diff --git a/app/src/server.c b/app/src/server.c index 0edd8a05..29c92eb3 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -12,7 +12,7 @@ #include "util/log.h" #include "util/net_intr.h" #include "util/process_intr.h" -#include "util/str_util.h" +#include "util/str.h" #define SERVER_FILENAME "scrcpy-server" diff --git a/app/src/sys/win/file.c b/app/src/sys/win/file.c index 650e0b4b..5233b177 100644 --- a/app/src/sys/win/file.c +++ b/app/src/sys/win/file.c @@ -5,7 +5,7 @@ #include #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" char * sc_file_get_executable_path(void) { diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index cfbb3948..4dcd542e 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -3,7 +3,7 @@ #include #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" #define CMD_MAX_LEN 8192 diff --git a/app/src/util/str_util.c b/app/src/util/str.c similarity index 99% rename from app/src/util/str_util.c rename to app/src/util/str.c index d15dad20..7935c6bb 100644 --- a/app/src/util/str_util.c +++ b/app/src/util/str.c @@ -1,4 +1,4 @@ -#include "str_util.h" +#include "str.h" #include #include diff --git a/app/src/util/str_util.h b/app/src/util/str.h similarity index 98% rename from app/src/util/str_util.h rename to app/src/util/str.h index 47e01344..54e32808 100644 --- a/app/src/util/str_util.h +++ b/app/src/util/str.h @@ -1,5 +1,5 @@ -#ifndef SC_STRUTIL_H -#define SC_STRUTIL_H +#ifndef SC_STR_H +#define SC_STR_H #include "common.h" diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index a35a9096..753d5b6a 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -1,7 +1,7 @@ #include "v4l2_sink.h" #include "util/log.h" -#include "util/str_util.h" +#include "util/str.h" /** Downcast frame_sink to sc_v4l2_sink */ #define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink) diff --git a/app/tests/test_strutil.c b/app/tests/test_str.c similarity index 99% rename from app/tests/test_strutil.c rename to app/tests/test_str.c index 6a53f94b..2b030885 100644 --- a/app/tests/test_strutil.c +++ b/app/tests/test_str.c @@ -5,7 +5,7 @@ #include #include -#include "util/str_util.h" +#include "util/str.h" static void test_strncpy_simple(void) { char s[] = "xxxxxxxxxx"; From c1a34881d733dd91d2c4d2184ad4b89ffee8314e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 23:24:12 +0100 Subject: [PATCH 0096/1133] Use sc_ prefix for server --- app/src/scrcpy.c | 28 ++++++++--------- app/src/server.c | 82 +++++++++++++++++++++++++----------------------- app/src/server.h | 34 ++++++++++---------- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 38a50a0b..9643b04e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -34,7 +34,7 @@ #endif struct scrcpy { - struct server server; + struct sc_server server; struct screen screen; struct stream stream; struct decoder decoder; @@ -286,7 +286,7 @@ stream_on_eos(struct stream *stream, void *userdata) { } static void -server_on_connection_failed(struct server *server, void *userdata) { +sc_server_on_connection_failed(struct sc_server *server, void *userdata) { (void) server; (void) userdata; @@ -294,7 +294,7 @@ server_on_connection_failed(struct server *server, void *userdata) { } static void -server_on_connected(struct server *server, void *userdata) { +sc_server_on_connected(struct sc_server *server, void *userdata) { (void) server; (void) userdata; @@ -302,7 +302,7 @@ server_on_connected(struct server *server, void *userdata) { } static void -server_on_disconnected(struct server *server, void *userdata) { +sc_server_on_disconnected(struct sc_server *server, void *userdata) { (void) server; (void) userdata; @@ -340,7 +340,7 @@ scrcpy(struct scrcpy_options *options) { bool controller_started = false; bool screen_initialized = false; - struct server_params params = { + struct sc_server_params params = { .serial = options->serial, .log_level = options->log_level, .crop = options->crop, @@ -359,16 +359,16 @@ scrcpy(struct scrcpy_options *options) { .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, + static const struct sc_server_callbacks cbs = { + .on_connection_failed = sc_server_on_connection_failed, + .on_connected = sc_server_on_connected, + .on_disconnected = sc_server_on_disconnected, }; - if (!server_init(&s->server, ¶ms, &cbs, NULL)) { + if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) { return false; } - if (!server_start(&s->server)) { + if (!sc_server_start(&s->server)) { goto end; } @@ -392,7 +392,7 @@ scrcpy(struct scrcpy_options *options) { } // It is necessarily initialized here, since the device is connected - struct server_info *info = &s->server.info; + struct sc_server_info *info = &s->server.info; if (options->display && options->control) { if (!file_handler_init(&s->file_handler, options->serial, @@ -608,7 +608,7 @@ end: if (server_started) { // shutdown the sockets and kill the server - server_stop(&s->server); + sc_server_stop(&s->server); } // now that the sockets are shutdown, the stream and controller are @@ -653,7 +653,7 @@ end: file_handler_destroy(&s->file_handler); } - server_destroy(&s->server); + sc_server_destroy(&s->server); return ret; } diff --git a/app/src/server.c b/app/src/server.c index 29c92eb3..58247a72 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -14,10 +14,10 @@ #include "util/process_intr.h" #include "util/str.h" -#define SERVER_FILENAME "scrcpy-server" +#define SC_SERVER_FILENAME "scrcpy-server" -#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME -#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" +#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME +#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" static char * get_server_path(void) { @@ -42,18 +42,18 @@ get_server_path(void) { } #ifndef PORTABLE - LOGD("Using server: " DEFAULT_SERVER_PATH); - char *server_path = strdup(DEFAULT_SERVER_PATH); + LOGD("Using server: " SC_SERVER_PATH_DEFAULT); + char *server_path = strdup(SC_SERVER_PATH_DEFAULT); if (!server_path) { LOGE("Could not allocate memory"); return NULL; } #else - char *server_path = sc_file_get_local_path(SERVER_FILENAME); + char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME); if (!server_path) { LOGE("Could not get local file path, " - "using " SERVER_FILENAME " from current directory"); - return strdup(SERVER_FILENAME); + "using " SC_SERVER_FILENAME " from current directory"); + return strdup(SC_SERVER_FILENAME); } LOGD("Using server (portable): %s", server_path); @@ -63,7 +63,7 @@ get_server_path(void) { } static void -server_params_destroy(struct server_params *params) { +sc_server_params_destroy(struct sc_server_params *params) { // The server stores a copy of the params provided by the user free((char *) params->serial); free((char *) params->crop); @@ -72,7 +72,8 @@ server_params_destroy(struct server_params *params) { } static bool -server_params_copy(struct server_params *dst, const struct server_params *src) { +sc_server_params_copy(struct sc_server_params *dst, + const struct sc_server_params *src) { *dst = *src; // The params reference user-allocated memory, so we must copy them to @@ -96,7 +97,7 @@ server_params_copy(struct server_params *dst, const struct server_params *src) { return true; error: - server_params_destroy(dst); + sc_server_params_destroy(dst); return false; } @@ -111,7 +112,7 @@ push_server(struct sc_intr *intr, const char *serial) { free(server_path); return false; } - sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH); + sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH); free(server_path); return sc_process_check_success_intr(intr, pid, "adb push"); } @@ -136,7 +137,8 @@ log_level_to_server_string(enum sc_log_level level) { } static sc_pid -execute_server(struct server *server, const struct server_params *params) { +execute_server(struct sc_server *server, + const struct sc_server_params *params) { const char *serial = server->params.serial; char max_size_string[6]; @@ -152,7 +154,7 @@ execute_server(struct server *server, const struct server_params *params) { sprintf(display_id_string, "%"PRIu32, params->display_id); const char *const cmd[] = { "shell", - "CLASSPATH=" DEVICE_SERVER_PATH, + "CLASSPATH=" SC_DEVICE_SERVER_PATH, "app_process", #ifdef SERVER_DEBUGGER # define SERVER_DEBUGGER_PORT "5005" @@ -218,7 +220,7 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { } static sc_socket -connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { +connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) { uint16_t port = server->tunnel.local_port; do { LOGD("Remaining connection attempts: %d", (int) attempts); @@ -253,9 +255,9 @@ connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) { } 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); +sc_server_init(struct sc_server *server, const struct sc_server_params *params, + const struct sc_server_callbacks *cbs, void *cbs_userdata) { + bool ok = sc_server_params_copy(&server->params, params); if (!ok) { LOGE("Could not copy server params"); return false; @@ -264,7 +266,7 @@ server_init(struct server *server, const struct server_params *params, ok = sc_mutex_init(&server->mutex); if (!ok) { LOGE("Could not create server mutex"); - server_params_destroy(&server->params); + sc_server_params_destroy(&server->params); return false; } @@ -272,7 +274,7 @@ server_init(struct server *server, const struct server_params *params, if (!ok) { LOGE("Could not create server cond_stopped"); sc_mutex_destroy(&server->mutex); - server_params_destroy(&server->params); + sc_server_params_destroy(&server->params); return false; } @@ -281,7 +283,7 @@ server_init(struct server *server, const struct server_params *params, LOGE("Could not create intr"); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); - server_params_destroy(&server->params); + sc_server_params_destroy(&server->params); return false; } @@ -305,26 +307,26 @@ server_init(struct server *server, const struct server_params *params, static bool device_read_info(struct sc_intr *intr, sc_socket device_socket, - struct server_info *info) { - unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; + struct sc_server_info *info) { + unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4]; ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf)); - if (r < DEVICE_NAME_FIELD_LENGTH + 4) { + if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) { LOGE("Could not retrieve device information"); return false; } // in case the client sends garbage - buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; + buf[SC_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) - | buf[DEVICE_NAME_FIELD_LENGTH + 1]; - info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) - | buf[DEVICE_NAME_FIELD_LENGTH + 3]; + info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8) + | buf[SC_DEVICE_NAME_FIELD_LENGTH + 1]; + info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8) + | buf[SC_DEVICE_NAME_FIELD_LENGTH + 3]; return true; } static bool -server_connect_to(struct server *server, struct server_info *info) { +sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { struct sc_adb_tunnel *tunnel = &server->tunnel; assert(tunnel->enabled); @@ -400,8 +402,8 @@ fail: } static void -server_on_terminated(void *userdata) { - struct server *server = userdata; +sc_server_on_terminated(void *userdata) { + struct sc_server *server = userdata; // If the server process dies before connecting to the server socket, // then the client will be stuck forever on accept(). To avoid the problem, @@ -416,9 +418,9 @@ server_on_terminated(void *userdata) { static int run_server(void *data) { - struct server *server = data; + struct sc_server *server = data; - const struct server_params *params = &server->params; + const struct sc_server_params *params = &server->params; bool ok = push_server(&server->intr, params->serial); if (!ok) { @@ -439,7 +441,7 @@ run_server(void *data) { } static const struct sc_process_listener listener = { - .on_terminated = server_on_terminated, + .on_terminated = sc_server_on_terminated, }; struct sc_process_observer observer; ok = sc_process_observer_init(&observer, pid, &listener, server); @@ -450,7 +452,7 @@ run_server(void *data) { goto error_connection_failed; } - ok = server_connect_to(server, &server->info); + ok = sc_server_connect_to(server, &server->info); // The tunnel is always closed by server_connect_to() if (!ok) { sc_process_terminate(pid); @@ -499,7 +501,7 @@ error_connection_failed: } bool -server_start(struct server *server) { +sc_server_start(struct sc_server *server) { bool ok = sc_thread_create(&server->thread, run_server, "server", server); if (!ok) { LOGE("Could not create server thread"); @@ -510,7 +512,7 @@ server_start(struct server *server) { } void -server_stop(struct server *server) { +sc_server_stop(struct sc_server *server) { sc_mutex_lock(&server->mutex); server->stopped = true; sc_cond_signal(&server->cond_stopped); @@ -521,8 +523,8 @@ server_stop(struct server *server) { } void -server_destroy(struct server *server) { - server_params_destroy(&server->params); +sc_server_destroy(struct sc_server *server) { + sc_server_params_destroy(&server->params); sc_intr_destroy(&server->intr); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); diff --git a/app/src/server.h b/app/src/server.h index cb2cf3a8..3859c328 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -16,13 +16,13 @@ #include "util/net.h" #include "util/thread.h" -#define DEVICE_NAME_FIELD_LENGTH 64 -struct server_info { - char device_name[DEVICE_NAME_FIELD_LENGTH]; +#define SC_DEVICE_NAME_FIELD_LENGTH 64 +struct sc_server_info { + char device_name[SC_DEVICE_NAME_FIELD_LENGTH]; struct sc_size frame_size; }; -struct server_params { +struct sc_server_params { const char *serial; enum sc_log_level log_level; const char *crop; @@ -41,12 +41,12 @@ struct server_params { bool power_off_on_close; }; -struct server { +struct sc_server { // The internal allocated strings are copies owned by the server - struct server_params params; + struct sc_server_params params; sc_thread thread; - struct server_info info; // initialized once connected + struct sc_server_info info; // initialized once connected sc_mutex mutex; sc_cond cond_stopped; @@ -58,45 +58,45 @@ struct server { sc_socket video_socket; sc_socket control_socket; - const struct server_callbacks *cbs; + const struct sc_server_callbacks *cbs; void *cbs_userdata; }; -struct server_callbacks { +struct sc_server_callbacks { /** * Called when the server failed to connect * * If it is called, then on_connected() and on_disconnected() will never be * called. */ - void (*on_connection_failed)(struct server *server, void *userdata); + void (*on_connection_failed)(struct sc_server *server, void *userdata); /** * Called on server connection */ - void (*on_connected)(struct server *server, void *userdata); + void (*on_connected)(struct sc_server *server, void *userdata); /** * Called on server disconnection (after it has been connected) */ - void (*on_disconnected)(struct server *server, void *userdata); + void (*on_disconnected)(struct sc_server *server, void *userdata); }; // init the server with the given params bool -server_init(struct server *server, const struct server_params *params, - const struct server_callbacks *cbs, void *cbs_userdata); +sc_server_init(struct sc_server *server, const struct sc_server_params *params, + const struct sc_server_callbacks *cbs, void *cbs_userdata); // start the server asynchronously bool -server_start(struct server *server); +sc_server_start(struct sc_server *server); // disconnect and kill the server process void -server_stop(struct server *server); +sc_server_stop(struct sc_server *server); // close and release sockets void -server_destroy(struct server *server); +sc_server_destroy(struct sc_server *server); #endif From 45b0f8123a52f5c73a5860d616f4ceba2766ca6a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 20:59:01 +0100 Subject: [PATCH 0097/1133] Increase delay to inject HID on Ctrl+v 2 milliseconds turn out to be insufficient sometimes. It seems that 5 milliseconds is enough (and still not noticeable). --- app/src/hid_keyboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index c6fba21c..3ac1a441 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -307,7 +307,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, // requested. Wait a bit so that the clipboard is set before // injecting Ctrl+v via HID, otherwise it would paste the old // clipboard content. - hid_event.delay = SC_TICK_FROM_MS(2); + hid_event.delay = SC_TICK_FROM_MS(5); } if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { From 1d97d77032f62cc48e6eb03e3c77d68951b5cb92 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 09:39:26 +0100 Subject: [PATCH 0098/1133] Improve scrcpy presentation in README --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a0ab271c..1bfaff7c 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,33 @@ [Read in another language](#translations) This application provides display and control of Android devices connected on -USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. +USB (or [over TCP/IP](#wireless)). It does not require any _root_ access. It works on _GNU/Linux_, _Windows_ and _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) It focuses on: - - **lightness** (native, displays only the device screen) - - **performance** (30~60fps) - - **quality** (1920×1080 or above) - - **low latency** ([35~70ms][lowlatency]) - - **low startup time** (~1 second to display the first image) - - **non-intrusiveness** (nothing is left installed on the device) + - **lightness**: native, displays only the device screen + - **performance**: 30~120fps, depending on the device + - **quality**: 1920×1080 or above + - **low latency**: [35~70ms][lowlatency] + - **low startup time**: ~1 second to display the first image + - **non-intrusiveness**: nothing is left installed on the device + - **user benefits**: no account, no ads, no internet required + - **freedom**: free and open source software [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 +Its features include: + - [recording](#recording) + - mirroring with [device screen off](#turn-screen-off) + - [copy-paste](#copy-paste) in both directions + - [configurable quality](#capture-configuration) + - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only) + - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) + (Linux-only) + - and more… ## Requirements From 99a4a4847711c4ab02dac63550bda8435330d338 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 19:33:18 +0100 Subject: [PATCH 0099/1133] Replace "connected on" to "connected via" I'm not sure "connected on USB" is correct. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bfaff7c..7d2d3e0b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [Read in another language](#translations) -This application provides display and control of Android devices connected on +This application provides display and control of Android devices connected via USB (or [over TCP/IP](#wireless)). It does not require any _root_ access. It works on _GNU/Linux_, _Windows_ and _macOS_. From 4e811a4a685aba17c517db1f68e434dcfa907d5b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 19:17:30 +0100 Subject: [PATCH 0100/1133] Adapt icon in README Use SVG format, align to the right, and reduce its size. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d2d3e0b..7b1ad410 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # scrcpy (v1.19) -![icon](data/icon.png) +scrcpy [Read in another language](#translations) From 57387fa7077850f11b9ab6ce699cb5c7876964d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 23:20:06 +0100 Subject: [PATCH 0101/1133] Mention crash on Android 12 on old scrcpy in FAQ --- FAQ.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/FAQ.md b/FAQ.md index a1c2fb76..fdc6e1ae 100644 --- a/FAQ.md +++ b/FAQ.md @@ -222,6 +222,27 @@ scrcpy -m 800 You could also try another [encoder](README.md#encoder). +If you encounter this exception on Android 12, then just upgrade to scrcpy >= +1.18 (see [#2129]): + +``` +> ERROR: Exception on thread Thread[main,5,main] +java.lang.AssertionError: java.lang.reflect.InvocationTargetException + at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75) + ... +Caused by: java.lang.reflect.InvocationTargetException + at java.lang.reflect.Method.invoke(Native Method) + at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73) + ... 7 more +Caused by: java.lang.IllegalArgumentException: displayToken must not be null + at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067) + at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147) + ... 9 more +``` + +[#2129]: https://github.com/Genymobile/scrcpy/issues/2129 + + ## Command line on Windows Some Windows users are not familiar with the command line. Here is how to open a From 1be5daf999a59cd4b70fbf8e4745217f90eb4e03 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 13 Nov 2021 23:57:18 +0100 Subject: [PATCH 0102/1133] Fix word order in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b1ad410..337e2944 100644 --- a/README.md +++ b/README.md @@ -697,7 +697,7 @@ a location inverted through the center of the screen. 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 +On Linux, scrcpy can simulate a physical USB 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. From 1bb0df5da17a08473a7bdf2e6419a0f0966aa023 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 00:34:12 +0100 Subject: [PATCH 0103/1133] Extract ANDROID_JAR path in build script This will allow to reuse it. --- server/build_without_gradle.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 05b822ba..926c19f5 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -21,6 +21,7 @@ BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" SERVER_DIR=$(dirname "$0") SERVER_BINARY=scrcpy-server +ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" echo "Platform: android-$PLATFORM" echo "Build-tools: $BUILD_TOOLS" @@ -47,8 +48,8 @@ cd "$SERVER_DIR/src/main/aidl" echo "Compiling java sources..." cd ../java -javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \ - -cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \ +javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ + -source 1.8 -target 1.8 \ com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/wrappers/*.java From 52138fd9213216a21fbdcda4d430394d7c8f0979 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 00:23:36 +0100 Subject: [PATCH 0104/1133] Update script to build without gradle to SDK 31 Build tools 31.x.x do not ship dx anymore. Use d8 instead. Refs 8bf28e9f5340d2a54dbc9b2c9d924b7ee4fa8d31 --- server/build_without_gradle.sh | 42 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 926c19f5..c33ee47a 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,9 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=1.19 -PLATFORM=${ANDROID_PLATFORM:-30} -BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} +PLATFORM_VERSION=31 +PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" @@ -55,16 +56,33 @@ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ echo "Dexing..." cd "$CLASSES_DIR" -"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ - --output "$BUILD_DIR/classes.dex" \ - android/view/*.class \ - android/content/*.class \ - com/genymobile/scrcpy/*.class \ - com/genymobile/scrcpy/wrappers/*.class -echo "Archiving..." -cd "$BUILD_DIR" -jar cvf "$SERVER_BINARY" classes.dex -rm -rf classes.dex classes +if [[ $PLATFORM_VERSION -lt 31 ]] +then + # use dx + "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ + --output "$BUILD_DIR/classes.dex" \ + android/view/*.class \ + android/content/*.class \ + com/genymobile/scrcpy/*.class \ + com/genymobile/scrcpy/wrappers/*.class + + echo "Archiving..." + cd "$BUILD_DIR" + jar cvf "$SERVER_BINARY" classes.dex + rm -rf classes.dex classes +else + # use d8 + "$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \ + --output "$BUILD_DIR/classes.zip" \ + android/view/*.class \ + android/content/*.class \ + com/genymobile/scrcpy/*.class \ + com/genymobile/scrcpy/wrappers/*.class + + cd "$BUILD_DIR" + mv classes.zip "$SERVER_BINARY" + rm -rf classes +fi echo "Server generated in $BUILD_DIR/$SERVER_BINARY" From a045e28df83711ea5ea10d8dc9f8dfaf781a022a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 00:22:12 +0100 Subject: [PATCH 0105/1133] Bump version to 1.20 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index fc61bcea..7a814212 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.19', + version: '1.20', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index cc03ecc8..52781a29 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 11900 - versionName "1.19" + versionCode 12000 + versionName "1.20" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index c33ee47a..61b42103 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.19 +SCRCPY_VERSION_NAME=1.20 PLATFORM_VERSION=31 PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} From 65b023ac6d586593193fd5290f65e25603b68e02 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 01:51:32 +0100 Subject: [PATCH 0106/1133] Update links to v1.20 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index 5edb5a21..9eda3715 100644 --- a/BUILD.md +++ b/BUILD.md @@ -270,10 +270,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.19`][direct-scrcpy-server] - _(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_ + - [`scrcpy-server-v1.20`][direct-scrcpy-server] + _(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 2b52cf72..c9080861 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.19) +# scrcpy (v1.20) scrcpy @@ -101,10 +101,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.19.zip`][direct-win64] - _(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_ + - [`scrcpy-win64-v1.20.zip`][direct-win64] + _(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-win64-v1.19.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index dcb254f9..e12b4469 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 -PREBUILT_SERVER_SHA256=876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 +PREBUILT_SERVER_SHA256=b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 739ff9dce011238844dc00e4e245afbb0561928d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 15:15:34 +0100 Subject: [PATCH 0107/1133] Fix compilation errors with old SDL versions SDL_PixelFormatEnum has been introduced in SDL 2.0.10: SDL_PIXELFORMAT_BGR444 has been introduced in SDL 2.0.12: Fixes #2777 PR #2781 Reviewed-by: Yu-Chen Lin --- app/src/icon.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/icon.c b/app/src/icon.c index a3efbb01..2616007e 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -158,6 +158,12 @@ free_ctx: return result; } +#if !SDL_VERSION_ATLEAST(2, 0, 10) +// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL +// versions. +typedef int SDL_PixelFormatEnum; +#endif + static SDL_PixelFormatEnum to_sdl_pixel_format(enum AVPixelFormat fmt) { switch (fmt) { @@ -172,7 +178,9 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) { 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; +#if SDL_VERSION_ATLEAST(2, 0, 12) case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444; +#endif case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8; default: return SDL_PIXELFORMAT_UNKNOWN; } From 6a27062f486a1abba07defac4ca0458b0e43ecb6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 15:39:20 +0100 Subject: [PATCH 0108/1133] Stop connection attempts if interrupted If the interruptor is interrupted, every network call will fail, but the retry-on-error mechanism must also be stopped. --- app/src/server.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/server.c b/app/src/server.c index 58247a72..d792364d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,6 +234,12 @@ connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) { net_close(socket); } + + if (sc_intr_is_interrupted(&server->intr)) { + // Stop immediately + break; + } + if (attempts) { sc_mutex_lock(&server->mutex); sc_tick deadline = sc_tick_now() + delay; From 52cebe1597cef201f28dc3c6122dc022616362c2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 22:37:51 +0100 Subject: [PATCH 0109/1133] Fix Windows sc_pipe function names The implementation name was incorrect (it was harmless, because they are not used on Windows). --- app/src/sys/win/process.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 4dcd542e..e4460ea7 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -168,7 +168,7 @@ sc_process_close(HANDLE handle) { } ssize_t -sc_read_pipe(HANDLE pipe, char *data, size_t len) { +sc_pipe_read(HANDLE pipe, char *data, size_t len) { DWORD r; if (!ReadFile(pipe, data, len, &r, NULL)) { return -1; @@ -177,7 +177,7 @@ sc_read_pipe(HANDLE pipe, char *data, size_t len) { } void -sc_close_pipe(HANDLE pipe) { +sc_pipe_close(HANDLE pipe) { if (!CloseHandle(pipe)) { LOGW("Cannot close pipe"); } From fd4ec784e02d5f3ce715c3b1e1f967716a069667 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 22:53:02 +0100 Subject: [PATCH 0110/1133] Remove useless assignments on error Leave the output parameter untouched on error. --- app/src/sys/win/process.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index e4460ea7..f76fa12e 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -84,7 +84,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, char *cmd = malloc(CMD_MAX_LEN); if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { - *handle = NULL; goto error_close_stderr; } @@ -98,7 +97,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { free(wide); - *handle = NULL; if (GetLastError() == ERROR_FILE_NOT_FOUND) { ret = SC_PROCESS_ERROR_MISSING_BINARY; From 9cb8766220b55e9ac5d222d16f2138c481034e0d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Nov 2021 07:49:01 +0100 Subject: [PATCH 0111/1133] Factorize resource release after CreateProcess() Free the wide characters string in all cases before checking for errors. --- app/src/sys/win/process.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index f76fa12e..45980790 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -94,10 +94,10 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, goto error_close_stderr; } - if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, - &pi)) { - free(wide); - + BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, + &pi); + free(wide); + if (!ok) { if (GetLastError() == ERROR_FILE_NOT_FOUND) { ret = SC_PROCESS_ERROR_MISSING_BINARY; } @@ -115,7 +115,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, CloseHandle(stderr_write_handle); } - free(wide); *handle = pi.hProcess; return SC_PROCESS_SUCCESS; From 9cb14b51663d61c6f0b41da3c5699de418d633aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 14 Nov 2021 19:29:54 +0100 Subject: [PATCH 0112/1133] Inherit only specific handles on Windows To be able to communicate with a child process via stdin, stdout and stderr, the CreateProcess() parameter bInheritHandles must be set to TRUE. But this causes *all* handles to be inherited, including sockets. As a result, the server socket was inherited by the process running adb to execute the server on the device, so it could not be closed properly, causing other scrcpy instances to fail. To fix the issue, use an extended API to explicitly set the HANDLEs to inherit: - - Fixes #2779 PR #2783 --- app/src/sys/win/process.c | 88 +++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 45980790..6566b80e 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -1,5 +1,10 @@ +// +#define _WIN32_WINNT 0x0600 // For extended process API + #include "util/process.h" +#include + #include #include "util/log.h" @@ -26,6 +31,9 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, HANDLE *pin, HANDLE *pout, HANDLE *perr) { enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; + // Add 1 per non-NULL pointer + unsigned handle_count = !!pin + !!pout + !!perr; + SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; @@ -65,43 +73,94 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, } } - STARTUPINFOW si; + STARTUPINFOEXW si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - if (pin || pout || perr) { - si.dwFlags = STARTF_USESTDHANDLES; + si.StartupInfo.cb = sizeof(si); + HANDLE handles[3]; + + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; + if (handle_count) { + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; if (pin) { - si.hStdInput = stdin_read_handle; + si.StartupInfo.hStdInput = stdin_read_handle; } if (pout) { - si.hStdOutput = stdout_write_handle; + si.StartupInfo.hStdOutput = stdout_write_handle; } if (perr) { - si.hStdError = stderr_write_handle; + si.StartupInfo.hStdError = stderr_write_handle; + } + + SIZE_T size; + // Call it once to know the required buffer size + BOOL ok = + InitializeProcThreadAttributeList(NULL, 1, 0, &size) + || GetLastError() == ERROR_INSUFFICIENT_BUFFER; + if (!ok) { + goto error_close_stderr; + } + + lpAttributeList = malloc(size); + if (!lpAttributeList) { + goto error_close_stderr; + } + + ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size); + if (!ok) { + free(lpAttributeList); + goto error_close_stderr; } + + // Explicitly pass the HANDLEs that must be inherited + unsigned i = 0; + if (pin) { + handles[i++] = stdin_read_handle; + } + if (pout) { + handles[i++] = stdout_write_handle; + } + if (perr) { + handles[i++] = stderr_write_handle; + } + ok = UpdateProcThreadAttribute(lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + handles, handle_count * sizeof(HANDLE), + NULL, NULL); + if (!ok) { + goto error_free_attribute_list; + } + + si.lpAttributeList = lpAttributeList; } char *cmd = malloc(CMD_MAX_LEN); if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { - goto error_close_stderr; + goto error_free_attribute_list; } wchar_t *wide = sc_str_to_wchars(cmd); free(cmd); if (!wide) { LOGC("Could not allocate wide char string"); - goto error_close_stderr; + goto error_free_attribute_list; } - BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si, - &pi); + BOOL bInheritHandles = handle_count > 0; + DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 0; + BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles, + dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); free(wide); if (!ok) { if (GetLastError() == ERROR_FILE_NOT_FOUND) { ret = SC_PROCESS_ERROR_MISSING_BINARY; } - goto error_close_stderr; + goto error_free_attribute_list; + } + + if (lpAttributeList) { + DeleteProcThreadAttributeList(lpAttributeList); + free(lpAttributeList); } // These handles are used by the child process, close them for this process @@ -119,6 +178,11 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, return SC_PROCESS_SUCCESS; +error_free_attribute_list: + if (lpAttributeList) { + DeleteProcThreadAttributeList(lpAttributeList); + free(lpAttributeList); + } error_close_stderr: if (perr) { CloseHandle(*perr); From 02ae0db6cd14a06899e6f8e92bfe107210ec51a3 Mon Sep 17 00:00:00 2001 From: Alex Burdusel <1262636+AlexBurdu@users.noreply.github.com> Date: Mon, 15 Nov 2021 22:18:40 +0200 Subject: [PATCH 0113/1133] Fix wrong package to install for Ubuntu/Debian Without this package, meson fails: Run-time dependency libusb-1.0 found: NO (tried pkgconfig and cmake) app/meson.build:88:8: ERROR: Dependency "libusb-1.0" not found, tried pkgconfig and cmake PR #2790 Signed-off-by: Romain Vimont --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 9eda3715..5f473ce2 100644 --- a/BUILD.md +++ b/BUILD.md @@ -15,7 +15,7 @@ First, you need to install the required packages: 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 + libusb-1.0-0 libusb-1.0-0-dev ``` Then clone the repo and execute the installation script @@ -94,7 +94,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 \ - libusb-dev + libusb-1.0-0-dev # server build dependencies sudo apt install openjdk-11-jdk From 57fb08e443272a9d346b036fb421cdf0ccff3074 Mon Sep 17 00:00:00 2001 From: LuXu Date: Mon, 15 Nov 2021 17:20:57 +0800 Subject: [PATCH 0114/1133] Update Simplified Chinese README to 1.20 PR #2786 Signed-off-by: Romain Vimont --- README.md | 2 +- README.zh-Hans.md | 313 +++++++++++++++++++++++++++++++++------------- 2 files changed, 224 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index c9080861..8d80cb1f 100644 --- a/README.md +++ b/README.md @@ -955,7 +955,7 @@ This README is available in other languages: - [한국어 (Korean, `ko`) - v1.11](README.ko.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) +- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index bdd8023c..b96d6d5a 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -2,27 +2,41 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ 只有原版的[README](README.md)会保持最新。 -本文根据[ed130e05]进行翻译。 +Current version is based on [65b023a] -[ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md +本文根据[65b023a]进行翻译。 -# scrcpy (v1.17) +[65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md + +# scrcpy (v1.20) + +scrcpy 本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。 ![screenshot](assets/screenshot-debian-600.jpg) -它专注于: +本应用专注于: - - **轻量** (原生,仅显示设备屏幕) - - **性能** (30~60fps) - - **质量** (分辨率可达 1920×1080 或更高) - - **低延迟** ([35~70ms][lowlatency]) - - **快速启动** (最快 1 秒内即可显示第一帧) - - **无侵入性** (不会在设备上遗留任何程序) + - **轻量**: 原生,仅显示设备屏幕 + - **性能**: 30~120fps,取决于设备 + - **质量**: 分辨率可达 1920×1080 或更高 + - **低延迟**: [35~70ms][lowlatency] + - **快速启动**: 最快 1 秒内即可显示第一帧 + - **无侵入性**: 不会在设备上遗留任何程序 + - **用户利益**: 无需帐号,无广告,无需联网 + - **自由**: 自由和开源软件 [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 +功能: + - [屏幕录制](#屏幕录制) + - 镜像时[关闭设备屏幕](#关闭设备屏幕) + - 双向[复制粘贴](#复制粘贴) + - [可配置显示质量](#采集设置) + - 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux) + - [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux) + - 更多 …… ## 系统要求 @@ -41,6 +55,17 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ Packaging status +### 概要 + + - Linux: `apt install scrcpy` + - Windows: [下载][direct-win64] + - macOS: `brew install scrcpy` + +从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple]) + +[BUILD]: BUILD.md +[BUILD_simple]: BUILD.md#simple + ### Linux 在 Debian (目前仅支持 _testing_ 和 _sid_ 分支) 和Ubuntu (20.04) 上: @@ -70,13 +95,12 @@ apt install scrcpy [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy -您也可以[自行构建][BUILD] (不必担心,这并不困难)。 - +您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。 ### Windows -在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 +在 Windows 上,为简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 - [README](README.md#windows) @@ -114,13 +138,17 @@ brew install scrcpy 你还需要在 `PATH` 内有 `adb`。如果还没有: ```bash -# Homebrew >= 2.6.0 -brew install --cask android-platform-tools +brew install android-platform-tools +``` -# Homebrew < 2.6.0 -brew cask install android-platform-tools +或者通过 [MacPorts],该方法同时设置好 adb: + +```bash +sudo port install scrcpy ``` +[MacPorts]: https://www.macports.org/ + 您也可以[自行构建][BUILD]。 @@ -140,7 +168,7 @@ scrcpy --help ## 功能介绍 -### 捕获设置 +### 采集设置 #### 降低分辨率 @@ -158,7 +186,7 @@ scrcpy -m 1024 # 简写 #### 修改码率 -默认码率是 8Mbps。要改变视频的码率 (例如改为 2Mbps): +默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps): ```bash scrcpy --bit-rate 2M @@ -167,7 +195,7 @@ scrcpy -b 2M # 简写 #### 限制帧率 -要限制捕获的帧率: +要限制采集的帧率: ```bash scrcpy --max-fps 15 @@ -194,10 +222,11 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素 要锁定镜像画面的方向: ```bash -scrcpy --lock-video-orientation 0 # 自然方向 -scrcpy --lock-video-orientation 1 # 逆时针旋转 90° -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 顺时针旋转 90° +scrcpy --lock-video-orientation # 初始(目前)方向 +scrcpy --lock-video-orientation=0 # 自然方向 +scrcpy --lock-video-orientation=1 # 逆时针旋转 90° +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 顺时针旋转 90° ``` 只影响录制的方向。 @@ -219,7 +248,9 @@ scrcpy --encoder OMX.qcom.video.encoder.avc scrcpy --encoder _ ``` -### 屏幕录制 +### 采集 + +#### 屏幕录制 可以在镜像的同时录制视频: @@ -241,6 +272,75 @@ scrcpy -Nr file.mkv [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation +#### v4l2loopback + +在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。 + +需安装 `v4l2loopback` 模块: + +```bash +sudo apt install v4l2loopback-dkms +``` + +创建一个 v4l2 设备: + +```bash +sudo modprobe v4l2loopback +``` + +这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。 + +列出已启用的设备: + +```bash +# 需要 v4l-utils 包 +v4l2-ctl --list-devices + +# 简单但或许足够 +ls /dev/video* +``` + +使用一个 v4l2 漏开启 scrcpy: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像 +scrcpy --v4l2-sink=/dev/videoN -N # 简写 +``` + +(将 `N` 替换为设备 ID,使用 `ls /dev/video*` 命令查看) + +启用之后,可以使用 v4l2 工具打开视频流: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟 +``` + +例如,可以在 [OBS] 中采集视频。 + +[OBS]: https://obsproject.com/ + + +#### 缓冲 + +可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。 + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +对于显示缓冲: + +```bash +scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲 +``` + +对于 V4L2 漏: + +```bash +scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲 +``` + + ### 连接 #### 无线 @@ -249,16 +349,17 @@ _Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接 1. 将设备和电脑连接至同一 Wi-Fi。 2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: + ```bash adb shell ip route | awk '{print $9}' ``` -3. 启用设备的网络 adb 功能 `adb tcpip 5555`。 +3. 启用设备的网络 adb 功能: `adb tcpip 5555`。 4. 断开设备的 USB 连接。 -5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_. +5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。 6. 正常运行 `scrcpy`。 -可能需要降低码率和分辨率: +可能降低码率和分辨率会更好一些: ```bash scrcpy --bit-rate 2M --max-size 800 @@ -327,7 +428,7 @@ scrcpy --force-adb-forward ``` -类似无线网络连接,可能需要降低画面质量: +类似地,对于无线连接,可能需要降低画面质量: ``` scrcpy -b2M -m800 --max-fps 15 @@ -353,7 +454,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 #### 无边框 -关闭边框: +禁用窗口边框: ```bash scrcpy --window-borderless @@ -369,7 +470,7 @@ scrcpy --always-on-top #### 全屏 -您可以通过如下命令直接全屏启动scrcpy: +您可以通过如下命令直接全屏启动 scrcpy: ```bash scrcpy --fullscreen @@ -394,7 +495,7 @@ scrcpy --rotation 1 也可以使用 MOD+ _(左箭头)_ 和 MOD+ _(右箭头)_ 随时更改。 -需要注意的是, _scrcpy_ 有三个不同的方向: +需要注意的是, _scrcpy_ 中有三类旋转方向: - MOD+r 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。 - [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。 - `--rotation` (或 MOD+/MOD+) 只旋转窗口的内容。这只影响显示,不影响录制。 @@ -404,7 +505,7 @@ scrcpy --rotation 1 #### 只读 -禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放): +禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放): ```bash scrcpy --no-control @@ -430,14 +531,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=” #### 保持常亮 -阻止设备在连接时休眠: +阻止设备在连接时一段时间后休眠: ```bash scrcpy --stay-awake scrcpy -w ``` -程序关闭时会恢复设备原来的设置。 +scrcpy 关闭时会恢复设备原来的设置。 #### 关闭设备屏幕 @@ -451,7 +552,7 @@ scrcpy -S 或者在任何时候按 MOD+o。 -要重新打开屏幕,按下 MOD+Shift+o. +要重新打开屏幕,按下 MOD+Shift+o。 在Android上,`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 MOD+p),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。 @@ -462,20 +563,17 @@ scrcpy --turn-screen-off --stay-awake scrcpy -Sw ``` +#### 退出时息屏 -#### 渲染过期帧 - -默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。 - -强制渲染所有帧 (可能导致延迟变高): +scrcpy 退出时关闭设备屏幕: ```bash -scrcpy --render-expired-frames +scrcpy --power-off-on-close ``` #### 显示触摸 -在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)。 +在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。 Android 在 _开发者选项_ 中提供了这项功能。 @@ -538,10 +636,32 @@ scrcpy --disable-screensaver 更准确的说,在按住鼠标左键时按住 Ctrl。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。 -实际上,_scrcpy_ 会在以屏幕中心对称的位置上生成由“虚拟手指”发出的额外触摸事件。 +实际上,_scrcpy_ 会在关于屏幕中心对称的位置上用“虚拟手指”发出触摸事件。 + +#### 物理键盘模拟 (HID) + +默认情况下,scrcpy 使用安卓按键或文本注入,这在任何情况都可以使用,但仅限于ASCII字符。 + +在 Linux 上,scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。 + +[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support + +不过,这种方法仅支持 USB 连接以及 Linux平台。 + +启用 HID 模式: + +```bash +scrcpy --hid-keyboard +scrcpy -K # 简写 +``` + +如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。 +在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。 -#### 文字注入偏好 +[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 + +#### 文本注入偏好 打字的时候,系统会产生两种[事件][textevents]: - _按键事件_ ,代表一个按键被按下或松开。 @@ -557,13 +677,15 @@ scrcpy --prefer-text (这会导致键盘在游戏中工作不正常) +该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。 + [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 #### 按键重复 -默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。 +默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。 避免转发重复按键事件: @@ -571,10 +693,11 @@ scrcpy --prefer-text scrcpy --no-key-repeat ``` +该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。 #### 右键和中键 -默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: +默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: ```bash scrcpy --forward-all-clicks @@ -587,27 +710,27 @@ scrcpy --forward-all-clicks 将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。 -该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。 +不会有视觉反馈,终端会输出一条日志。 #### 将文件推送至设备 -要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 +要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 -该操作没有可见的响应,只会在控制台输出日志。 +不会有视觉反馈,终端会输出一条日志。 在启动时可以修改目标目录: ```bash -scrcpy --push-target /sdcard/foo/bar/ +scrcpy --push-target=/sdcard/Movies/ ``` ### 音频转发 -_Scrcpy_ 不支持音频。请使用 [sndcpy]. +_Scrcpy_ 不支持音频。请使用 [sndcpy]。 -另外请阅读 [issue #14]。 +另见 [issue #14]。 [sndcpy]: https://github.com/rom1v/sndcpy [issue #14]: https://github.com/Genymobile/scrcpy/issues/14 @@ -632,36 +755,46 @@ _[Super] 键通常是指 WindowsCmd 键。 [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - | 操作 | 快捷键 | - | --------------------------------- | :------------------------------------------- | - | 全屏 | MOD+f | - | 向左旋转屏幕 | MOD+ _(左箭头)_ | - | 向右旋转屏幕 | MOD+ _(右箭头)_ | - | 将窗口大小重置为1:1 (匹配像素) | MOD+g | - | 将窗口大小重置为消除黑边 | MOD+w \| _双击¹_ | - | 点按 `主屏幕` | MOD+h \| _鼠标中键_ | - | 点按 `返回` | MOD+b \| _鼠标右键²_ | - | 点按 `切换应用` | MOD+s | - | 点按 `菜单` (解锁屏幕) | MOD+m | - | 点按 `音量+` | MOD+ _(上箭头)_ | - | 点按 `音量-` | MOD+ _(下箭头)_ | - | 点按 `电源` | MOD+p | - | 打开屏幕 | _鼠标右键²_ | - | 关闭设备屏幕 (但继续在电脑上显示) | MOD+o | - | 打开设备屏幕 | MOD+Shift+o | - | 旋转设备屏幕 | MOD+r | - | 展开通知面板 | MOD+n | - | 收起通知面板 | MOD+Shift+n | - | 复制到剪贴板³ | MOD+c | - | 剪切到剪贴板³ | MOD+x | - | 同步剪贴板并粘贴³ | MOD+v | - | 注入电脑剪贴板文本 | MOD+Shift+v | - | 打开/关闭FPS显示 (在 stdout) | MOD+i | - | 捏拉缩放 | Ctrl+_按住并移动鼠标_ | - -_¹双击黑边可以去除黑边_ -_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_ -_³需要安卓版本 Android >= 7。_ + | 操作 | 快捷键 + | --------------------------------- | :------------------------------------------- + | 全屏 | MOD+f + | 向左旋转屏幕 | MOD+ _(左箭头)_ + | 向右旋转屏幕 | MOD+ _(右箭头)_ + | 将窗口大小重置为1:1 (匹配像素) | MOD+g + | 将窗口大小重置为消除黑边 | MOD+w \| _双击左键¹_ + | 点按 `主屏幕` | MOD+h \| _中键_ + | 点按 `返回` | MOD+b \| _右键²_ + | 点按 `切换应用` | MOD+s \| _第4键³_ + | 点按 `菜单` (解锁屏幕) | MOD+m + | 点按 `音量+` | MOD+ _(上箭头)_ + | 点按 `音量-` | MOD+ _(下箭头)_ + | 点按 `电源` | MOD+p + | 打开屏幕 | _鼠标右键²_ + | 关闭设备屏幕 (但继续在电脑上显示) | MOD+o + | 打开设备屏幕 | MOD+Shift+o + | 旋转设备屏幕 | MOD+r + | 展开通知面板 | MOD+n \| _第5键³_ + | 展开设置面板 | MOD+n+n \| _双击第5键³_ + | 收起通知面板 | MOD+Shift+n + | 复制到剪贴板⁴ | MOD+c + | 剪切到剪贴板⁴ | MOD+x + | 同步剪贴板并粘贴⁴ | MOD+v + | 注入电脑剪贴板文本 | MOD+Shift+v + | 打开/关闭FPS显示 (至标准输出) | MOD+i + | 捏拉缩放 | Ctrl+_按住并移动鼠标_ + | 拖放 APK 文件 | 从电脑安装 APK 文件 + | 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device) + +_¹双击黑边可以去除黑边。_ +_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_ +_³鼠标的第4键和第5键。_ +_⁴需要安卓版本 Android >= 7。_ + +有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”: + + 1. 按下 MOD 不放。 + 2. 双击 n。 + 3. 松开 MOD。 所有的 Ctrl+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。 @@ -670,18 +803,20 @@ _³需要安卓版本 Android >= 7。_ 要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`: - ADB=/path/to/adb scrcpy +```bash +ADB=/path/to/adb scrcpy +``` 要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。 -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 +要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。 ## 为什么叫 _scrcpy_ ? 一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。 -[`strcpy`] 复制一个 **str**ing; `scrcpy` 复制一个 **scr**een。 +[`strcpy`] 复制一个 **str**ing (字符串); `scrcpy` 复制一个 **scr**een (屏幕)。 [gnirehtet]: https://github.com/Genymobile/gnirehtet [`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html @@ -689,14 +824,12 @@ _³需要安卓版本 Android >= 7。_ ## 如何构建? -请查看[BUILD]。 - -[BUILD]: BUILD.md +请查看 [BUILD]。 ## 常见问题 -请查看[FAQ](FAQ.md)。 +请查看 [FAQ](FAQ.md)。 ## 开发者 From 67170437f1eb2c9f2780d720b3079d7d8722b9f4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 16 Nov 2021 22:10:34 +0100 Subject: [PATCH 0115/1133] Wrap settings management into a Settings class Until now, the code that needed to read/write the Android settings had to explicitly open and close a ContentProvider. Wrap these details into a Settings class. This paves the way to provide an alternative implementation of settings read/write for Android >= 12. PR #2802 --- .../java/com/genymobile/scrcpy/CleanUp.java | 18 ++++----- .../java/com/genymobile/scrcpy/Device.java | 6 +-- .../java/com/genymobile/scrcpy/Server.java | 35 ++++++++--------- .../java/com/genymobile/scrcpy/Settings.java | 39 +++++++++++++++++++ .../scrcpy/wrappers/ContentProvider.java | 8 ---- 5 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/Settings.java diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index ec61a1c0..9001eef7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -1,6 +1,5 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ServiceManager; import android.os.Parcel; @@ -166,15 +165,14 @@ public final class CleanUp { if (config.disableShowTouches || config.restoreStayOn != -1) { ServiceManager serviceManager = new ServiceManager(); - try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) { - if (config.disableShowTouches) { - Ln.i("Disabling \"show touches\""); - settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0"); - } - if (config.restoreStayOn != -1) { - Ln.i("Restoring \"stay awake\""); - settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); - } + Settings settings = new Settings(serviceManager); + if (config.disableShowTouches) { + Ln.i("Disabling \"show touches\""); + settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + } + if (config.restoreStayOn != -1) { + Ln.i("Restoring \"stay awake\""); + settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 3e71fe9c..093646e2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,7 +1,6 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ClipboardManager; -import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -29,6 +28,7 @@ public final class Device { public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); + private static final Settings SETTINGS = new Settings(SERVICE_MANAGER); public interface RotationListener { void onRotationChanged(int rotation); @@ -296,7 +296,7 @@ public final class Device { } } - public static ContentProvider createSettingsProvider() { - return SERVICE_MANAGER.getActivityManager().createSettingsProvider(); + public static Settings getSettings() { + return SETTINGS; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index fdd9db88..c900f872 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,7 +1,5 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.ContentProvider; - import android.graphics.Rect; import android.media.MediaCodec; import android.media.MediaCodecInfo; @@ -27,25 +25,24 @@ public final class Server { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; if (options.getShowTouches() || options.getStayAwake()) { - try (ContentProvider settings = Device.createSettingsProvider()) { - if (options.getShowTouches()) { - String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1"); - // If "show touches" was disabled, it must be disabled back on clean up - mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); - } + Settings settings = Device.getSettings(); + if (options.getShowTouches()) { + String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + } - if (options.getStayAwake()) { - int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; - String oldValue = settings.getAndPutValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); - try { - restoreStayOn = Integer.parseInt(oldValue); - if (restoreStayOn == stayOn) { - // No need to restore - restoreStayOn = -1; - } - } catch (NumberFormatException e) { - restoreStayOn = 0; + if (options.getStayAwake()) { + int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; + String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); + try { + restoreStayOn = Integer.parseInt(oldValue); + if (restoreStayOn == stayOn) { + // No need to restore + restoreStayOn = -1; } + } catch (NumberFormatException e) { + restoreStayOn = 0; } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java new file mode 100644 index 00000000..b59188d5 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Settings.java @@ -0,0 +1,39 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.ContentProvider; +import com.genymobile.scrcpy.wrappers.ServiceManager; + +public class Settings { + + public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; + public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE; + public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL; + + private final ServiceManager serviceManager; + + public Settings(ServiceManager serviceManager) { + this.serviceManager = serviceManager; + } + + public String getValue(String table, String key) { + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + return provider.getValue(table, key); + } + } + + public void putValue(String table, String key, String value) { + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + provider.putValue(table, key, value); + } + } + + public String getAndPutValue(String table, String key, String value) { + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + String oldValue = provider.getValue(table, key); + if (!value.equals(oldValue)) { + provider.putValue(table, key, value); + } + return oldValue; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 387c7a60..ab95f0df 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -160,12 +160,4 @@ public class ContentProvider implements Closeable { arg.putString(NAME_VALUE_TABLE_VALUE, value); call(method, key, arg); } - - public String getAndPutValue(String table, String key, String value) { - String oldValue = getValue(table, key); - if (!value.equals(oldValue)) { - putValue(table, key, value); - } - return oldValue; - } } From 94feae71f2bf603b1cbd78611a2fdecdfcf40b67 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 16 Nov 2021 22:40:53 +0100 Subject: [PATCH 0116/1133] Report settings errors via Exceptions Settings read/write errors were silently ignored. Report them via a SettingsException so that the caller can handle them. This allows to log a proper error message, and will also allow to fallback to a different settings method in case of failure. PR #2802 --- .../java/com/genymobile/scrcpy/CleanUp.java | 12 ++++++-- .../java/com/genymobile/scrcpy/Server.java | 28 +++++++++++------- .../java/com/genymobile/scrcpy/Settings.java | 6 ++-- .../genymobile/scrcpy/SettingsException.java | 11 +++++++ .../scrcpy/wrappers/ContentProvider.java | 29 +++++++++++++------ 5 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/SettingsException.java diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 9001eef7..319a957d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -168,11 +168,19 @@ public final class CleanUp { Settings settings = new Settings(serviceManager); if (config.disableShowTouches) { Ln.i("Disabling \"show touches\""); - settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + try { + settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + } catch (SettingsException e) { + Ln.e("Could not restore \"show_touches\"", e); + } } if (config.restoreStayOn != -1) { Ln.i("Restoring \"stay awake\""); - settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); + try { + settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); + } catch (SettingsException e) { + Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); + } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c900f872..efb295b3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -27,22 +27,30 @@ public final class Server { if (options.getShowTouches() || options.getStayAwake()) { Settings settings = Device.getSettings(); if (options.getShowTouches()) { - String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); - // If "show touches" was disabled, it must be disabled back on clean up - mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + try { + String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); + } catch (SettingsException e) { + Ln.e("Could not change \"show_touches\"", e); + } } if (options.getStayAwake()) { int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; - String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); try { - restoreStayOn = Integer.parseInt(oldValue); - if (restoreStayOn == stayOn) { - // No need to restore - restoreStayOn = -1; + String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); + try { + restoreStayOn = Integer.parseInt(oldValue); + if (restoreStayOn == stayOn) { + // No need to restore + restoreStayOn = -1; + } + } catch (NumberFormatException e) { + restoreStayOn = 0; } - } catch (NumberFormatException e) { - restoreStayOn = 0; + } catch (SettingsException e) { + Ln.e("Could not change \"stay_on_while_plugged_in\"", e); } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java index b59188d5..83b63477 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Settings.java +++ b/server/src/main/java/com/genymobile/scrcpy/Settings.java @@ -15,19 +15,19 @@ public class Settings { this.serviceManager = serviceManager; } - public String getValue(String table, String key) { + public String getValue(String table, String key) throws SettingsException { try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { return provider.getValue(table, key); } } - public void putValue(String table, String key, String value) { + public void putValue(String table, String key, String value) throws SettingsException { try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { provider.putValue(table, key, value); } } - public String getAndPutValue(String table, String key, String value) { + public String getAndPutValue(String table, String key, String value) throws SettingsException { try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { String oldValue = provider.getValue(table, key); if (!value.equals(oldValue)) { diff --git a/server/src/main/java/com/genymobile/scrcpy/SettingsException.java b/server/src/main/java/com/genymobile/scrcpy/SettingsException.java new file mode 100644 index 00000000..36ef63ee --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/SettingsException.java @@ -0,0 +1,11 @@ +package com.genymobile.scrcpy; + +public class SettingsException extends Exception { + private static String createMessage(String method, String table, String key, String value) { + return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : ""); + } + + public SettingsException(String method, String table, String key, String value, Throwable cause) { + super(createMessage(method, table, key, value), cause); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index ab95f0df..47eae64d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; +import com.genymobile.scrcpy.SettingsException; import android.annotation.SuppressLint; import android.os.Bundle; @@ -87,7 +88,8 @@ public class ContentProvider implements Closeable { return attributionSource; } - private Bundle call(String callMethod, String arg, Bundle extras) { + private Bundle call(String callMethod, String arg, Bundle extras) + throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { try { Method method = getCallMethod(); Object[] args; @@ -108,7 +110,7 @@ public class ContentProvider implements Closeable { return (Bundle) method.invoke(provider, args); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) { Ln.e("Could not invoke method", e); - return null; + throw e; } } @@ -142,22 +144,31 @@ public class ContentProvider implements Closeable { } } - public String getValue(String table, String key) { + public String getValue(String table, String key) throws SettingsException { String method = getGetMethod(table); Bundle arg = new Bundle(); arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); - Bundle bundle = call(method, key, arg); - if (bundle == null) { - return null; + try { + Bundle bundle = call(method, key, arg); + if (bundle == null) { + return null; + } + return bundle.getString("value"); + } catch (Exception e) { + throw new SettingsException(table, "get", key, null, e); } - return bundle.getString("value"); + } - public void putValue(String table, String key, String value) { + public void putValue(String table, String key, String value) throws SettingsException { String method = getPutMethod(table); Bundle arg = new Bundle(); arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); arg.putString(NAME_VALUE_TABLE_VALUE, value); - call(method, key, arg); + try { + call(method, key, arg); + } catch (Exception e) { + throw new SettingsException(table, "put", key, value, e); + } } } From 48b572c272901af1b32afa204ae011ebb9344b40 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 10:01:13 +0100 Subject: [PATCH 0117/1133] Add throwable parameter to Log.w() When an exception occurs, we might want to log a warning instead of an error. PR #2802 --- server/src/main/java/com/genymobile/scrcpy/Ln.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 061cda95..c39fc621 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -57,13 +57,20 @@ public final class Ln { } } - public static void w(String message) { + public static void w(String message, Throwable throwable) { if (isEnabled(Level.WARN)) { - Log.w(TAG, message); + Log.w(TAG, message, throwable); System.out.println(PREFIX + "WARN: " + message); + if (throwable != null) { + throwable.printStackTrace(); + } } } + public static void w(String message) { + w(message, null); + } + public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); From cc0902b13c87fc98b1ed90b0700cc53ac4d7ee3c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 10:05:10 +0100 Subject: [PATCH 0118/1133] Read/write settings via command on Android >= 12 Before Android 8, executing the "settings" command from a shell was very slow (~1 second), because it spawned a new app_process to execute Java code. Therefore, to access settings without performance issues, scrcpy used private APIs to read from and write to settings. However, since Android 12, this is not possible anymore, due to permissions changes. To make it work again, execute the "settings" command on Android 12 (or on previous version if the other method failed). This method is faster than before Android 8 (~100ms). Fixes #2671 Fixes #2788 PR #2802 --- .../java/com/genymobile/scrcpy/Command.java | 33 ++++++++++ .../java/com/genymobile/scrcpy/Settings.java | 63 ++++++++++++++++--- 2 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/Command.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Command.java b/server/src/main/java/com/genymobile/scrcpy/Command.java new file mode 100644 index 00000000..0ef976a6 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Command.java @@ -0,0 +1,33 @@ +package com.genymobile.scrcpy; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Scanner; + +public final class Command { + private Command() { + // not instantiable + } + + public static void exec(String... cmd) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec(cmd); + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + } + + public static String execReadLine(String... cmd) throws IOException, InterruptedException { + String result = null; + Process process = Runtime.getRuntime().exec(cmd); + Scanner scanner = new Scanner(process.getInputStream()); + if (scanner.hasNextLine()) { + result = scanner.nextLine(); + } + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + return result; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java index 83b63477..cb15ebb4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Settings.java +++ b/server/src/main/java/com/genymobile/scrcpy/Settings.java @@ -3,6 +3,10 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.os.Build; + +import java.io.IOException; + public class Settings { public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; @@ -15,25 +19,66 @@ public class Settings { this.serviceManager = serviceManager; } + private static void execSettingsPut(String table, String key, String value) throws SettingsException { + try { + Command.exec("settings", "put", table, key, value); + } catch (IOException | InterruptedException e) { + throw new SettingsException("put", table, key, value, e); + } + } + + private static String execSettingsGet(String table, String key) throws SettingsException { + try { + return Command.execReadLine("settings", "get", table, key); + } catch (IOException | InterruptedException e) { + throw new SettingsException("get", table, key, null, e); + } + } + public String getValue(String table, String key) throws SettingsException { - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { - return provider.getValue(table, key); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + // on Android >= 12, it always fails: + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + return provider.getValue(table, key); + } catch (SettingsException e) { + Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e); + } } + + return execSettingsGet(table, key); } public void putValue(String table, String key, String value) throws SettingsException { - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { - provider.putValue(table, key, value); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + // on Android >= 12, it always fails: + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + provider.putValue(table, key, value); + } catch (SettingsException e) { + Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e); + } } + + execSettingsPut(table, key, value); } public String getAndPutValue(String table, String key, String value) throws SettingsException { - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { - String oldValue = provider.getValue(table, key); - if (!value.equals(oldValue)) { - provider.putValue(table, key, value); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + // on Android >= 12, it always fails: + try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + String oldValue = provider.getValue(table, key); + if (!value.equals(oldValue)) { + provider.putValue(table, key, value); + } + return oldValue; + } catch (SettingsException e) { + Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e); } - return oldValue; } + + String oldValue = getValue(table, key); + if (!value.equals(oldValue)) { + putValue(table, key, value); + } + return oldValue; } } From 411bb0d18e4d09f10480d2a60dcb7187b4fc62aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 10:16:11 +0100 Subject: [PATCH 0119/1133] Move init and cleanup to a separate method PR #2802 --- .../main/java/com/genymobile/scrcpy/Server.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index efb295b3..488a877f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -17,11 +17,7 @@ public final class Server { // not instantiable } - private static void scrcpy(Options options) throws IOException { - Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); - final Device device = new Device(options); - List codecOptions = CodecOption.parse(options.getCodecOptions()); - + private static void initAndCleanUp(Options options) throws IOException { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; if (options.getShowTouches() || options.getStayAwake()) { @@ -56,6 +52,14 @@ public final class Server { } CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); + } + + private static void scrcpy(Options options) throws IOException { + Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + final Device device = new Device(options); + List codecOptions = CodecOption.parse(options.getCodecOptions()); + + initAndCleanUp(options); boolean tunnelForward = options.isTunnelForward(); From c29a0bf675dc94fd350ea696d3ffa772651ff4bb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 10:21:42 +0100 Subject: [PATCH 0120/1133] Do not quit on cleanup configuration failure Cleanup is used for some options like --show-touches to restore the state on exit. If the configuration fails, do not crash the whole process. Just log an error. PR #2802 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 488a877f..db5bc7ec 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -17,7 +17,7 @@ public final class Server { // not instantiable } - private static void initAndCleanUp(Options options) throws IOException { + private static void initAndCleanUp(Options options) { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; if (options.getShowTouches() || options.getStayAwake()) { @@ -51,7 +51,11 @@ public final class Server { } } - CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); + try { + CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); + } catch (IOException e) { + Ln.e("Could not configure cleanup", e); + } } private static void scrcpy(Options options) throws IOException { From ee93d2aac17ddb996ba42e1f898e34a970ade88a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 10:29:41 +0100 Subject: [PATCH 0121/1133] Configure init and cleanup asynchronously Accessing the settings (like --show-touches) on start should not delay screen mirroring. PR #2802 --- .../main/java/com/genymobile/scrcpy/Server.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index db5bc7ec..5a1f4619 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -63,7 +63,7 @@ public final class Server { final Device device = new Device(options); List codecOptions = CodecOption.parse(options.getCodecOptions()); - initAndCleanUp(options); + Thread initThread = startInitThread(options); boolean tunnelForward = options.isTunnelForward(); @@ -95,6 +95,7 @@ public final class Server { // this is expected on close Ln.d("Screen streaming stopped"); } finally { + initThread.interrupt(); if (controllerThread != null) { controllerThread.interrupt(); } @@ -105,6 +106,17 @@ public final class Server { } } + private static Thread startInitThread(final Options options) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + initAndCleanUp(options); + } + }); + thread.start(); + return thread; + } + private static Thread startController(final Controller controller) { Thread thread = new Thread(new Runnable() { @Override From de5084690524a9f01ef3012e8e87b116f65625d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 18:31:36 +0100 Subject: [PATCH 0122/1133] Close process on check success The interruptible version of the function to check process success (sc_process_check_success_intr()) did not accept a close parameter to avoid a race condition. But as the result, the processes were not closed at all. Add a close parameter, and close the process separately to avoid the race condition. --- app/src/adb_tunnel.c | 10 ++++++---- app/src/server.c | 2 +- app/src/util/process_intr.c | 8 +++++++- app/src/util/process_intr.h | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index f02eb83e..df5a3d1f 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -13,27 +13,29 @@ static bool enable_tunnel_reverse(struct sc_intr *intr, const char *serial, uint16_t local_port) { sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port); - return sc_process_check_success_intr(intr, pid, "adb reverse"); + return sc_process_check_success_intr(intr, pid, "adb reverse", true); } static bool disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb reverse --remove"); + return sc_process_check_success_intr(intr, pid, "adb reverse --remove", + true); } static bool enable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb forward"); + return sc_process_check_success_intr(intr, pid, "adb forward", true); } static bool disable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { sc_pid pid = adb_forward_remove(serial, local_port); - return sc_process_check_success_intr(intr, pid, "adb forward --remove"); + return sc_process_check_success_intr(intr, pid, "adb forward --remove", + true); } static bool diff --git a/app/src/server.c b/app/src/server.c index d792364d..82a6af40 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -114,7 +114,7 @@ push_server(struct sc_intr *intr, const char *serial) { } sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH); free(server_path); - return sc_process_check_success_intr(intr, pid, "adb push"); + return sc_process_check_success_intr(intr, pid, "adb push", true); } static const char * diff --git a/app/src/util/process_intr.c b/app/src/util/process_intr.c index bb483123..ddf94e97 100644 --- a/app/src/util/process_intr.c +++ b/app/src/util/process_intr.c @@ -2,7 +2,7 @@ bool sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, - const char *name) { + const char *name, bool close) { if (!sc_intr_set_process(intr, pid)) { // Already interrupted return false; @@ -12,5 +12,11 @@ sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, bool ret = sc_process_check_success(pid, name, false); sc_intr_set_process(intr, SC_PROCESS_NONE); + + if (close) { + // Close separately + sc_process_close(pid); + } + return ret; } diff --git a/app/src/util/process_intr.h b/app/src/util/process_intr.h index ff0dfc76..a1574ce3 100644 --- a/app/src/util/process_intr.h +++ b/app/src/util/process_intr.h @@ -8,6 +8,6 @@ bool sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, - const char *name); + const char *name, bool close); #endif From 632bd5697b6e699e8945a79642a11fd3ffdcc289 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 18:36:03 +0100 Subject: [PATCH 0123/1133] Add missing error handling If "adb get-serialno" fails, attempting to read from the uninitialized pipe is incorrect. --- app/src/adb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 6251174e..4f50ee5f 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -239,6 +239,9 @@ adb_execute_for_output(const char *serial, const char *const adb_cmd[], const char *name) { sc_pipe pout; sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL); + if (pid == SC_PROCESS_NONE) { + return -1; + } ssize_t r = sc_pipe_read_all(pout, buf, buf_len); sc_pipe_close(pout); From b30c3a429f63bfa83079d2af48378cd0dbc0bdb8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 21:58:36 +0100 Subject: [PATCH 0124/1133] Always retrieve device serial This allows to execute all adb commands with the specific -s parameter, even if it is not provided by the user. In practice, calling adb without -s works if there is exactly one device connected. But some adb commands (for example "adb push" on drag & drop) could be executed after another device is connected, so the actual device serial must be known. --- app/src/scrcpy.c | 19 ++++--------------- app/src/server.c | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9643b04e..40bf72a8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -394,8 +394,11 @@ scrcpy(struct scrcpy_options *options) { // It is necessarily initialized here, since the device is connected struct sc_server_info *info = &s->server.info; + const char *serial = s->server.params.serial; + assert(serial); + if (options->display && options->control) { - if (!file_handler_init(&s->file_handler, options->serial, + if (!file_handler_init(&s->file_handler, serial, options->push_target)) { goto end; } @@ -516,21 +519,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_AOA_HID bool aoa_hid_ok = false; - char *serialno = NULL; - - const char *serial = options->serial; - if (!serial) { - serialno = adb_get_serialno(); - if (!serialno) { - LOGE("Could not get device serial"); - goto aoa_hid_end; - } - serial = serialno; - LOGI("Device serial: %s", serial); - } - bool ok = sc_aoa_init(&s->aoa, serial); - free(serialno); if (!ok) { goto aoa_hid_end; } diff --git a/app/src/server.c b/app/src/server.c index 82a6af40..56ba6b68 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -422,12 +422,36 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } +static bool +sc_server_fill_serial(struct sc_server *server) { + // Retrieve the actual device immediately if not provided, so that all + // future adb commands are executed for this specific device, even if other + // devices are connected afterwards (without "more than one + // device/emulator" error) + if (!server->params.serial) { + // The serial is owned by sc_server_params, and will be freed on destroy + server->params.serial = adb_get_serialno(); + if (!server->params.serial) { + LOGE("Could not get device serial"); + return false; + } + } + + return true; +} + static int run_server(void *data) { struct sc_server *server = data; + if (!sc_server_fill_serial(server)) { + goto error_connection_failed; + } + const struct sc_server_params *params = &server->params; + LOGD("Device serial: %s", params->serial); + bool ok = push_server(&server->intr, params->serial); if (!ok) { goto error_connection_failed; From 443cb14d6e8c37cb6f633bfd486ff669af9d4121 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 18:48:11 +0100 Subject: [PATCH 0125/1133] Assume non-NULL serial in file_handler The previous commit guarantees to always initialize the serial, so the file_handle may assume it is never NULL. --- app/src/file_handler.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index fe0ab857..b067d2f1 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -16,6 +16,7 @@ file_handler_request_destroy(struct file_handler_request *req) { bool file_handler_init(struct file_handler *file_handler, const char *serial, const char *push_target) { + assert(serial); cbuf_init(&file_handler->queue); @@ -30,16 +31,12 @@ file_handler_init(struct file_handler *file_handler, const char *serial, return false; } - if (serial) { - file_handler->serial = strdup(serial); - if (!file_handler->serial) { - LOGW("Could not strdup serial"); - sc_cond_destroy(&file_handler->event_cond); - sc_mutex_destroy(&file_handler->mutex); - return false; - } - } else { - file_handler->serial = NULL; + file_handler->serial = strdup(serial); + if (!file_handler->serial) { + LOGE("Could not strdup serial"); + sc_cond_destroy(&file_handler->event_cond); + sc_mutex_destroy(&file_handler->mutex); + return false; } // lazy initialization From f2781a8b6d5c37d1fa3c0d0291a01a8bd918dddd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 18:25:56 +0100 Subject: [PATCH 0126/1133] Expose util function to truncate first line Move the local implementation from adb functions to the string util functions. --- app/src/adb.c | 13 +------------ app/src/util/str.c | 11 +++++++++++ app/src/util/str.h | 11 +++++++++++ app/tests/test_str.c | 9 +++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 4f50ee5f..efeef4f8 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -253,17 +253,6 @@ adb_execute_for_output(const char *serial, const char *const adb_cmd[], return r; } -static size_t -truncate_first_line(char *data, size_t len) { - data[len - 1] = '\0'; - char *eol = strpbrk(data, "\r\n"); - if (eol) { - *eol = '\0'; - len = eol - data; - } - return len; -} - char * adb_get_serialno(void) { char buf[128]; @@ -275,6 +264,6 @@ adb_get_serialno(void) { return NULL; } - truncate_first_line(buf, r); + sc_str_truncate_first_line(buf, r); return strdup(buf); } diff --git a/app/src/util/str.c b/app/src/util/str.c index 7935c6bb..e63b0270 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -291,3 +291,14 @@ error: free(buf.s); return NULL; } + +size_t +sc_str_truncate_first_line(char *data, size_t len) { + data[len - 1] = '\0'; + char *eol = strpbrk(data, "\r\n"); + if (eol) { + *eol = '\0'; + len = eol - data; + } + return len; +} diff --git a/app/src/util/str.h b/app/src/util/str.h index 54e32808..77017e90 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -103,4 +103,15 @@ sc_str_from_wchars(const wchar_t *s); char * sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); +/** + * Truncate the data after the first line + * + * An '\0' is always written at the end of the data, even if no newline + * character is encountered. + * + * Return the size of the resulting line. + */ +size_t +sc_str_truncate_first_line(char *data, size_t len); + #endif diff --git a/app/tests/test_str.c b/app/tests/test_str.c index 2b030885..1cd9a37d 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -337,6 +337,14 @@ static void test_wrap_lines(void) { free(formatted); } +static void test_truncate_first_line(void) { + char s[] = "hello\nworld\n!"; + size_t len = sc_str_truncate_first_line(s, sizeof(s)); + + assert(len == 5); + assert(!strcmp("hello", s)); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -356,5 +364,6 @@ int main(int argc, char *argv[]) { test_parse_integer_with_suffix(); test_strlist_contains(); test_wrap_lines(); + test_truncate_first_line(); return 0; } From 9619ade706824a92ea387bdc9d0d27816bf79da5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 21:38:59 +0100 Subject: [PATCH 0127/1133] Generalize string trunctation util function Add an additional argument to let the client pass the possible end chars. --- app/src/adb.c | 2 +- app/src/util/str.c | 4 ++-- app/src/util/str.h | 4 ++-- app/tests/test_str.c | 24 +++++++++++++++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index efeef4f8..a8712bf4 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -264,6 +264,6 @@ adb_get_serialno(void) { return NULL; } - sc_str_truncate_first_line(buf, r); + sc_str_truncate(buf, r, "\r\n"); return strdup(buf); } diff --git a/app/src/util/str.c b/app/src/util/str.c index e63b0270..fdc1a8b3 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -293,9 +293,9 @@ error: } size_t -sc_str_truncate_first_line(char *data, size_t len) { +sc_str_truncate(char *data, size_t len, const char *endchars) { data[len - 1] = '\0'; - char *eol = strpbrk(data, "\r\n"); + char *eol = strpbrk(data, endchars); if (eol) { *eol = '\0'; len = eol - data; diff --git a/app/src/util/str.h b/app/src/util/str.h index 77017e90..521dfff5 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -104,7 +104,7 @@ char * sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); /** - * Truncate the data after the first line + * Truncate the data after any of the characters from `endchars` * * An '\0' is always written at the end of the data, even if no newline * character is encountered. @@ -112,6 +112,6 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); * Return the size of the resulting line. */ size_t -sc_str_truncate_first_line(char *data, size_t len); +sc_str_truncate(char *data, size_t len, const char *endchars); #endif diff --git a/app/tests/test_str.c b/app/tests/test_str.c index 1cd9a37d..770ddd95 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -337,12 +337,30 @@ static void test_wrap_lines(void) { free(formatted); } -static void test_truncate_first_line(void) { +static void test_truncate(void) { char s[] = "hello\nworld\n!"; - size_t len = sc_str_truncate_first_line(s, sizeof(s)); + size_t len = sc_str_truncate(s, sizeof(s), "\n"); assert(len == 5); assert(!strcmp("hello", s)); + + char s2[] = "hello\r\nworkd\r\n!"; + len = sc_str_truncate(s2, sizeof(s2), "\n\r"); + + assert(len == 5); + assert(!strcmp("hello", s)); + + char s3[] = "hello world\n!"; + len = sc_str_truncate(s3, sizeof(s3), " \n\r"); + + assert(len == 5); + assert(!strcmp("hello", s3)); + + char s4[] = "hello "; + len = sc_str_truncate(s4, sizeof(s4), " \n\r"); + + assert(len == 5); + assert(!strcmp("hello", s4)); } int main(int argc, char *argv[]) { @@ -364,6 +382,6 @@ int main(int argc, char *argv[]) { test_parse_integer_with_suffix(); test_strlist_contains(); test_wrap_lines(); - test_truncate_first_line(); + test_truncate(); return 0; } From cb655315330f5435b7849d256e33f8c148fd38d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 09:34:54 +0100 Subject: [PATCH 0128/1133] Simplify sc_str_truncate() Use strcspn() to get the prefix length directly. --- app/src/util/str.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/util/str.c b/app/src/util/str.c index fdc1a8b3..3bd2752f 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -295,10 +295,7 @@ error: size_t sc_str_truncate(char *data, size_t len, const char *endchars) { data[len - 1] = '\0'; - char *eol = strpbrk(data, endchars); - if (eol) { - *eol = '\0'; - len = eol - data; - } - return len; + size_t idx = strcspn(data, endchars); + data[idx] = '\0'; + return idx; } From ea454e9cee1cedb1fb7b8aed7fde46bd76bb031b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 18:30:54 +0100 Subject: [PATCH 0129/1133] Add interruptible function to read from pipe This will avoid to block Ctrl+c if the process the pipe is read from takes too much time. --- app/src/util/process_intr.c | 28 ++++++++++++++++++++++++++++ app/src/util/process_intr.h | 8 ++++++++ 2 files changed, 36 insertions(+) diff --git a/app/src/util/process_intr.c b/app/src/util/process_intr.c index ddf94e97..dcb81100 100644 --- a/app/src/util/process_intr.c +++ b/app/src/util/process_intr.c @@ -20,3 +20,31 @@ sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, return ret; } + +ssize_t +sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, + size_t len) { + if (!sc_intr_set_process(intr, pid)) { + // Already interrupted + return false; + } + + ssize_t ret = sc_pipe_read(pipe, data, len); + + sc_intr_set_process(intr, SC_PROCESS_NONE); + return ret; +} + +ssize_t +sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, + char *data, size_t len) { + if (!sc_intr_set_process(intr, pid)) { + // Already interrupted + return false; + } + + ssize_t ret = sc_pipe_read_all(pipe, data, len); + + sc_intr_set_process(intr, SC_PROCESS_NONE); + return ret; +} diff --git a/app/src/util/process_intr.h b/app/src/util/process_intr.h index a1574ce3..9406d889 100644 --- a/app/src/util/process_intr.h +++ b/app/src/util/process_intr.h @@ -10,4 +10,12 @@ bool sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, bool close); +ssize_t +sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, + size_t len); + +ssize_t +sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, + char *data, size_t len); + #endif From 0426ae885c13e5901a4994f01086839a3bc13728 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 18:47:20 +0100 Subject: [PATCH 0130/1133] Make "adb get-serialno" interruptible All process executions must be interruptible, so that Ctrl+c reacts immediately. --- app/src/adb.c | 35 +++-------------------------------- app/src/adb.h | 10 +++++++--- app/src/server.c | 25 ++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index a8712bf4..d98efe6f 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -233,37 +233,8 @@ adb_install(const char *serial, const char *local) { return pid; } -static ssize_t -adb_execute_for_output(const char *serial, const char *const adb_cmd[], - size_t adb_cmd_len, char *buf, size_t buf_len, - const char *name) { - sc_pipe pout; - sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL); - if (pid == SC_PROCESS_NONE) { - return -1; - } - - ssize_t r = sc_pipe_read_all(pout, buf, buf_len); - sc_pipe_close(pout); - - if (!sc_process_check_success(pid, name, true)) { - return -1; - } - - return r; -} - -char * -adb_get_serialno(void) { - char buf[128]; - +sc_pid +adb_get_serialno(sc_pipe *pout) { const char *const adb_cmd[] = {"get-serialno"}; - ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd), - buf, sizeof(buf), "get-serialno"); - if (r <= 0) { - return NULL; - } - - sc_str_truncate(buf, r, "\r\n"); - return strdup(buf); + return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), NULL, pout, NULL); } diff --git a/app/src/adb.h b/app/src/adb.h index 085b3e6b..d3b3faae 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -35,8 +35,12 @@ adb_push(const char *serial, const char *local, const char *remote); sc_pid adb_install(const char *serial, const char *local); -// Return the result of "adb get-serialno". -char * -adb_get_serialno(void); +/** + * Execute `adb get-serialno` + * + * The result can be read from the output parameter `pout`. + */ +sc_pid +adb_get_serialno(sc_pipe *pout); #endif diff --git a/app/src/server.c b/app/src/server.c index 56ba6b68..5ad8d70f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -422,6 +422,29 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } +static char * +sc_server_get_serialno(struct sc_intr *intr) { + sc_pipe pout; + sc_pid pid = adb_get_serialno(&pout); + if (pid == SC_PROCESS_NONE) { + return false; + } + + char buf[128]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = + sc_process_check_success_intr(intr, pid, "adb get-serialno", true); + if (!ok) { + return NULL; + } + + sc_str_truncate(buf, r, " \r\n"); + + return strdup(buf); +} + static bool sc_server_fill_serial(struct sc_server *server) { // Retrieve the actual device immediately if not provided, so that all @@ -430,7 +453,7 @@ sc_server_fill_serial(struct sc_server *server) { // device/emulator" error) if (!server->params.serial) { // The serial is owned by sc_server_params, and will be freed on destroy - server->params.serial = adb_get_serialno(); + server->params.serial = sc_server_get_serialno(&server->intr); if (!server->params.serial) { LOGE("Could not get device serial"); return false; From 13fd693b501b59a95e4e72b7ccba7c267cdc8590 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Nov 2021 21:53:11 +0100 Subject: [PATCH 0131/1133] Simplify adb_execute_p() Only pass the stdout pipe as parameter, scrcpy never writes to stdin or reads from stderr of an adb process. --- app/src/adb.c | 8 ++++---- app/src/adb.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index d98efe6f..3a5faed3 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -111,7 +111,7 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { sc_pid adb_execute_p(const char *serial, const char *const adb_cmd[], - size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) { + size_t len, sc_pipe *pout) { int i; sc_pid pid; @@ -132,7 +132,7 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); argv[len + i] = NULL; enum sc_process_result r = - sc_process_execute_p(argv, &pid, pin, pout, perr); + sc_process_execute_p(argv, &pid, NULL, pout, NULL); if (r != SC_PROCESS_SUCCESS) { show_adb_err_msg(r, argv); pid = SC_PROCESS_NONE; @@ -144,7 +144,7 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { - return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL); + return adb_execute_p(serial, adb_cmd, len, NULL); } sc_pid @@ -236,5 +236,5 @@ adb_install(const char *serial, const char *local) { sc_pid adb_get_serialno(sc_pipe *pout) { const char *const adb_cmd[] = {"get-serialno"}; - return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), NULL, pout, NULL); + return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout); } diff --git a/app/src/adb.h b/app/src/adb.h index d3b3faae..0107e6d4 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -12,8 +12,8 @@ sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len); sc_pid -adb_execute_p(const char *serial, const char *const adb_cmd[], - size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr); +adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, + sc_pipe *pout); sc_pid adb_forward(const char *serial, uint16_t local_port, From 55648d4d6479efd1052091fda34e60a871a2c1c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 19:46:40 +0100 Subject: [PATCH 0132/1133] Improve file_handler readability Use local variables as short name aliases. --- app/src/file_handler.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index b067d2f1..1e8d2641 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -104,6 +104,12 @@ static int run_file_handler(void *data) { struct file_handler *file_handler = data; + const char *serial = file_handler->serial; + assert(serial); + + const char *push_target = file_handler->push_target; + assert(push_target); + for (;;) { sc_mutex_lock(&file_handler->mutex); file_handler->current_process = SC_PROCESS_NONE; @@ -123,11 +129,10 @@ run_file_handler(void *data) { sc_pid pid; if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - pid = install_apk(file_handler->serial, req.file); + pid = install_apk(serial, req.file); } else { LOGI("Pushing %s...", req.file); - pid = push_file(file_handler->serial, req.file, - file_handler->push_target); + pid = push_file(serial, req.file, push_target); } file_handler->current_process = pid; sc_mutex_unlock(&file_handler->mutex); @@ -140,11 +145,9 @@ run_file_handler(void *data) { } } else { if (sc_process_check_success(pid, "adb push", false)) { - LOGI("%s successfully pushed to %s", req.file, - file_handler->push_target); + LOGI("%s successfully pushed to %s", req.file, push_target); } else { - LOGE("Failed to push %s to %s", req.file, - file_handler->push_target); + LOGE("Failed to push %s to %s", req.file, push_target); } } From 0ba2686e1db2fff2e65fc53a478d1266aca6b776 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Nov 2021 22:43:22 +0100 Subject: [PATCH 0133/1133] Simplify file_handler Call the target functions directly. --- app/src/file_handler.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 1e8d2641..73b6a004 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -62,16 +62,6 @@ file_handler_destroy(struct file_handler *file_handler) { } } -static sc_pid -install_apk(const char *serial, const char *file) { - return adb_install(serial, file); -} - -static sc_pid -push_file(const char *serial, const char *file, const char *push_target) { - return adb_push(serial, file, push_target); -} - bool file_handler_request(struct file_handler *file_handler, file_handler_action_t action, char *file) { @@ -129,10 +119,10 @@ run_file_handler(void *data) { sc_pid pid; if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - pid = install_apk(serial, req.file); + pid = adb_install(serial, req.file); } else { LOGI("Pushing %s...", req.file); - pid = push_file(serial, req.file, push_target); + pid = adb_push(serial, req.file, push_target); } file_handler->current_process = pid; sc_mutex_unlock(&file_handler->mutex); From 84334cf7db68732eb92caf6cf9682c7ef2c426f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 21:33:25 +0100 Subject: [PATCH 0134/1133] Use sc_intr in file_handler Replace manual interruption handling by the recent sc_intr mechanism. --- app/src/file_handler.c | 34 +++++++++++++++++----------------- app/src/file_handler.h | 4 +++- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 73b6a004..d84ae4cb 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -5,6 +5,7 @@ #include "adb.h" #include "util/log.h" +#include "util/process_intr.h" #define DEFAULT_PUSH_TARGET "/sdcard/Download/" @@ -31,9 +32,17 @@ file_handler_init(struct file_handler *file_handler, const char *serial, return false; } + ok = sc_intr_init(&file_handler->intr); + if (!ok) { + LOGE("Could not create intr"); + sc_cond_destroy(&file_handler->event_cond); + sc_mutex_destroy(&file_handler->mutex); + } + file_handler->serial = strdup(serial); if (!file_handler->serial) { LOGE("Could not strdup serial"); + sc_intr_destroy(&file_handler->intr); sc_cond_destroy(&file_handler->event_cond); sc_mutex_destroy(&file_handler->mutex); return false; @@ -43,7 +52,6 @@ file_handler_init(struct file_handler *file_handler, const char *serial, file_handler->initialized = false; file_handler->stopped = false; - file_handler->current_process = SC_PROCESS_NONE; file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; @@ -54,6 +62,7 @@ void file_handler_destroy(struct file_handler *file_handler) { sc_cond_destroy(&file_handler->event_cond); sc_mutex_destroy(&file_handler->mutex); + sc_intr_destroy(&file_handler->intr); free(file_handler->serial); struct file_handler_request req; @@ -93,6 +102,7 @@ file_handler_request(struct file_handler *file_handler, static int run_file_handler(void *data) { struct file_handler *file_handler = data; + struct sc_intr *intr = &file_handler->intr; const char *serial = file_handler->serial; assert(serial); @@ -102,7 +112,6 @@ run_file_handler(void *data) { for (;;) { sc_mutex_lock(&file_handler->mutex); - file_handler->current_process = SC_PROCESS_NONE; while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); } @@ -115,6 +124,7 @@ run_file_handler(void *data) { bool non_empty = cbuf_take(&file_handler->queue, &req); assert(non_empty); (void) non_empty; + sc_mutex_unlock(&file_handler->mutex); sc_pid pid; if (req.action == ACTION_INSTALL_APK) { @@ -124,30 +134,24 @@ run_file_handler(void *data) { LOGI("Pushing %s...", req.file); pid = adb_push(serial, req.file, push_target); } - file_handler->current_process = pid; - sc_mutex_unlock(&file_handler->mutex); if (req.action == ACTION_INSTALL_APK) { - if (sc_process_check_success(pid, "adb install", false)) { + if (sc_process_check_success_intr(intr, pid, "adb install", + false)) { LOGI("%s successfully installed", req.file); } else { LOGE("Failed to install %s", req.file); } } else { - if (sc_process_check_success(pid, "adb push", false)) { + if (sc_process_check_success_intr(intr, pid, "adb push", false)) { LOGI("%s successfully pushed to %s", req.file, push_target); } else { LOGE("Failed to push %s to %s", req.file, push_target); } } - sc_mutex_lock(&file_handler->mutex); // Close the process (it is necessarily already terminated) - // Execute this call with mutex locked to avoid race conditions with - // file_handler_stop() - sc_process_close(file_handler->current_process); - file_handler->current_process = SC_PROCESS_NONE; - sc_mutex_unlock(&file_handler->mutex); + sc_process_close(pid); file_handler_request_destroy(&req); } @@ -173,11 +177,7 @@ file_handler_stop(struct file_handler *file_handler) { sc_mutex_lock(&file_handler->mutex); file_handler->stopped = true; sc_cond_signal(&file_handler->event_cond); - if (file_handler->current_process != SC_PROCESS_NONE) { - if (!sc_process_terminate(file_handler->current_process)) { - LOGW("Could not terminate push/install process"); - } - } + sc_intr_interrupt(&file_handler->intr); sc_mutex_unlock(&file_handler->mutex); } diff --git a/app/src/file_handler.h b/app/src/file_handler.h index e2067533..4c0313cc 100644 --- a/app/src/file_handler.h +++ b/app/src/file_handler.h @@ -8,6 +8,7 @@ #include "adb.h" #include "util/cbuf.h" #include "util/thread.h" +#include "util/intr.h" typedef enum { ACTION_INSTALL_APK, @@ -29,8 +30,9 @@ struct file_handler { sc_cond event_cond; bool stopped; bool initialized; - sc_pid current_process; struct file_handler_request_queue queue; + + struct sc_intr intr; }; bool From afb5a5e80f8946dc4fb7d68d753ed3d0cd6bc877 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 21:47:17 +0100 Subject: [PATCH 0135/1133] Rename adb functions to adb_exec_* This paves the way to replace them by more user-friendly functions that will call them internally. --- app/src/adb.c | 18 +++++++++--------- app/src/adb.h | 18 +++++++++--------- app/src/adb_tunnel.c | 8 ++++---- app/src/file_handler.c | 4 ++-- app/src/server.c | 4 ++-- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 3a5faed3..608abca1 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -148,8 +148,8 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { } sc_pid -adb_forward(const char *serial, uint16_t local_port, - const char *device_socket_name) { +adb_exec_forward(const char *serial, uint16_t local_port, + const char *device_socket_name) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); @@ -159,7 +159,7 @@ adb_forward(const char *serial, uint16_t local_port, } sc_pid -adb_forward_remove(const char *serial, uint16_t local_port) { +adb_exec_forward_remove(const char *serial, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); const char *const adb_cmd[] = {"forward", "--remove", local}; @@ -167,8 +167,8 @@ adb_forward_remove(const char *serial, uint16_t local_port) { } sc_pid -adb_reverse(const char *serial, const char *device_socket_name, - uint16_t local_port) { +adb_exec_reverse(const char *serial, const char *device_socket_name, + uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); @@ -178,7 +178,7 @@ adb_reverse(const char *serial, const char *device_socket_name, } sc_pid -adb_reverse_remove(const char *serial, const char *device_socket_name) { +adb_exec_reverse_remove(const char *serial, const char *device_socket_name) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", "--remove", remote}; @@ -186,7 +186,7 @@ adb_reverse_remove(const char *serial, const char *device_socket_name) { } sc_pid -adb_push(const char *serial, const char *local, const char *remote) { +adb_exec_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) @@ -213,7 +213,7 @@ adb_push(const char *serial, const char *local, const char *remote) { } sc_pid -adb_install(const char *serial, const char *local) { +adb_exec_install(const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) @@ -234,7 +234,7 @@ adb_install(const char *serial, const char *local) { } sc_pid -adb_get_serialno(sc_pipe *pout) { +adb_exec_get_serialno(sc_pipe *pout) { const char *const adb_cmd[] = {"get-serialno"}; return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout); } diff --git a/app/src/adb.h b/app/src/adb.h index 0107e6d4..c2c21343 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -16,24 +16,24 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, sc_pipe *pout); sc_pid -adb_forward(const char *serial, uint16_t local_port, - const char *device_socket_name); +adb_exec_forward(const char *serial, uint16_t local_port, + const char *device_socket_name); sc_pid -adb_forward_remove(const char *serial, uint16_t local_port); +adb_exec_forward_remove(const char *serial, uint16_t local_port); sc_pid -adb_reverse(const char *serial, const char *device_socket_name, - uint16_t local_port); +adb_exec_reverse(const char *serial, const char *device_socket_name, + uint16_t local_port); sc_pid -adb_reverse_remove(const char *serial, const char *device_socket_name); +adb_exec_reverse_remove(const char *serial, const char *device_socket_name); sc_pid -adb_push(const char *serial, const char *local, const char *remote); +adb_exec_push(const char *serial, const char *local, const char *remote); sc_pid -adb_install(const char *serial, const char *local); +adb_exec_install(const char *serial, const char *local); /** * Execute `adb get-serialno` @@ -41,6 +41,6 @@ adb_install(const char *serial, const char *local); * The result can be read from the output parameter `pout`. */ sc_pid -adb_get_serialno(sc_pipe *pout); +adb_exec_get_serialno(sc_pipe *pout); #endif diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index df5a3d1f..b5850bf8 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -12,13 +12,13 @@ static bool enable_tunnel_reverse(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port); + sc_pid pid = adb_exec_reverse(serial, SC_SOCKET_NAME, local_port); return sc_process_check_success_intr(intr, pid, "adb reverse", true); } static bool disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { - sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME); + sc_pid pid = adb_exec_reverse_remove(serial, SC_SOCKET_NAME); return sc_process_check_success_intr(intr, pid, "adb reverse --remove", true); } @@ -26,14 +26,14 @@ disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { static bool enable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME); + sc_pid pid = adb_exec_forward(serial, local_port, SC_SOCKET_NAME); return sc_process_check_success_intr(intr, pid, "adb forward", true); } static bool disable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_forward_remove(serial, local_port); + sc_pid pid = adb_exec_forward_remove(serial, local_port); return sc_process_check_success_intr(intr, pid, "adb forward --remove", true); } diff --git a/app/src/file_handler.c b/app/src/file_handler.c index d84ae4cb..f7c79cff 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -129,10 +129,10 @@ run_file_handler(void *data) { sc_pid pid; if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - pid = adb_install(serial, req.file); + pid = adb_exec_install(serial, req.file); } else { LOGI("Pushing %s...", req.file); - pid = adb_push(serial, req.file, push_target); + pid = adb_exec_push(serial, req.file, push_target); } if (req.action == ACTION_INSTALL_APK) { diff --git a/app/src/server.c b/app/src/server.c index 5ad8d70f..6d535202 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -112,7 +112,7 @@ push_server(struct sc_intr *intr, const char *serial) { free(server_path); return false; } - sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH); + sc_pid pid = adb_exec_push(serial, server_path, SC_DEVICE_SERVER_PATH); free(server_path); return sc_process_check_success_intr(intr, pid, "adb push", true); } @@ -425,7 +425,7 @@ sc_server_on_terminated(void *userdata) { static char * sc_server_get_serialno(struct sc_intr *intr) { sc_pipe pout; - sc_pid pid = adb_get_serialno(&pout); + sc_pid pid = adb_exec_get_serialno(&pout); if (pid == SC_PROCESS_NONE) { return false; } From b7559744a7164124e7d358d2cb3bd744993487a2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 22:08:15 +0100 Subject: [PATCH 0136/1133] Expose new user-friendly adb functions Expose interruptible adb functions which return the expected result directly, without exposing the process (sc_pid) to the caller. --- app/src/adb.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ app/src/adb.h | 32 ++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 608abca1..92a52b5b 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -7,6 +7,7 @@ #include "util/file.h" #include "util/log.h" +#include "util/process_intr.h" #include "util/str.h" static const char *adb_command; @@ -238,3 +239,70 @@ adb_exec_get_serialno(sc_pipe *pout) { const char *const adb_cmd[] = {"get-serialno"}; return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout); } + +bool +adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, + const char *device_socket_name) { + sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name); + return sc_process_check_success_intr(intr, pid, "adb forward", true); +} + +bool +adb_forward_remove(struct sc_intr *intr, const char *serial, + uint16_t local_port) { + sc_pid pid = adb_exec_forward_remove(serial, local_port); + return sc_process_check_success_intr(intr, pid, "adb forward --remove", + true); +} + +bool +adb_reverse(struct sc_intr *intr, const char *serial, + const char *device_socket_name, uint16_t local_port) { + sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port); + return sc_process_check_success_intr(intr, pid, "adb reverse", true); +} + +bool +adb_reverse_remove(struct sc_intr *intr, const char *serial, + const char *device_socket_name) { + sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name); + return sc_process_check_success_intr(intr, pid, "adb reverse --remove", + true); +} + +bool +adb_push(struct sc_intr *intr, const char *serial, const char *local, + const char *remote) { + sc_pid pid = adb_exec_push(serial, local, remote); + return sc_process_check_success_intr(intr, pid, "adb push", true); +} + +bool +adb_install(struct sc_intr *intr, const char *serial, const char *local) { + sc_pid pid = adb_exec_install(serial, local); + return sc_process_check_success_intr(intr, pid, "adb install", true); +} + +char * +adb_get_serialno(struct sc_intr *intr) { + sc_pipe pout; + sc_pid pid = adb_exec_get_serialno(&pout); + if (pid == SC_PROCESS_NONE) { + LOGE("Could not execute \"adb get-serialno\""); + return NULL; + } + + char buf[128]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = + sc_process_check_success_intr(intr, pid, "adb get-serialno", true); + if (!ok) { + return NULL; + } + + sc_str_truncate(buf, r, " \r\n"); + + return strdup(buf); +} diff --git a/app/src/adb.h b/app/src/adb.h index c2c21343..3714c17f 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -7,6 +7,7 @@ #include #include "util/process.h" +#include "util/intr.h" sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len); @@ -43,4 +44,35 @@ adb_exec_install(const char *serial, const char *local); sc_pid adb_exec_get_serialno(sc_pipe *pout); +bool +adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, + const char *device_socket_name); + +bool +adb_forward_remove(struct sc_intr *intr, const char *serial, + uint16_t local_port); + +bool +adb_reverse(struct sc_intr *intr, const char *serial, + const char *device_socket_name, uint16_t local_port); + +bool +adb_reverse_remove(struct sc_intr *intr, const char *serial, + const char *device_socket_name); + +bool +adb_push(struct sc_intr *intr, const char *serial, const char *local, + const char *remote); + +bool +adb_install(struct sc_intr *intr, const char *serial, const char *local); + +/** + * Execute `adb get-serialno` + * + * Return the result, to be freed by the caller, or NULL on error. + */ +char * +adb_get_serialno(struct sc_intr *intr); + #endif From ce225f019accf3c9e043a04a7a405d76fb5b0888 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 22:11:19 +0100 Subject: [PATCH 0137/1133] Use new user-friendly adb API Replace the adb_exec_*() calls by the new adb functions to simplify. --- app/src/adb_tunnel.c | 14 ++++---------- app/src/file_handler.c | 19 +++++-------------- app/src/server.c | 29 +++-------------------------- 3 files changed, 12 insertions(+), 50 deletions(-) diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index b5850bf8..b94609e2 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -12,30 +12,24 @@ static bool enable_tunnel_reverse(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_exec_reverse(serial, SC_SOCKET_NAME, local_port); - return sc_process_check_success_intr(intr, pid, "adb reverse", true); + return adb_reverse(intr, serial, SC_SOCKET_NAME, local_port); } static bool disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { - sc_pid pid = adb_exec_reverse_remove(serial, SC_SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb reverse --remove", - true); + return adb_reverse_remove(intr, serial, SC_SOCKET_NAME); } static bool enable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_exec_forward(serial, local_port, SC_SOCKET_NAME); - return sc_process_check_success_intr(intr, pid, "adb forward", true); + return adb_forward(intr, serial, local_port, SC_SOCKET_NAME); } static bool disable_tunnel_forward(struct sc_intr *intr, const char *serial, uint16_t local_port) { - sc_pid pid = adb_exec_forward_remove(serial, local_port); - return sc_process_check_success_intr(intr, pid, "adb forward --remove", - true); + return adb_forward_remove(intr, serial, local_port); } static bool diff --git a/app/src/file_handler.c b/app/src/file_handler.c index f7c79cff..eead2117 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -126,33 +126,24 @@ run_file_handler(void *data) { (void) non_empty; sc_mutex_unlock(&file_handler->mutex); - sc_pid pid; if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - pid = adb_exec_install(serial, req.file); - } else { - LOGI("Pushing %s...", req.file); - pid = adb_exec_push(serial, req.file, push_target); - } - - if (req.action == ACTION_INSTALL_APK) { - if (sc_process_check_success_intr(intr, pid, "adb install", - false)) { + bool ok = adb_install(intr, serial, req.file); + if (ok) { LOGI("%s successfully installed", req.file); } else { LOGE("Failed to install %s", req.file); } } else { - if (sc_process_check_success_intr(intr, pid, "adb push", false)) { + LOGI("Pushing %s...", req.file); + bool ok = adb_push(intr, serial, req.file, push_target); + if (ok) { LOGI("%s successfully pushed to %s", req.file, push_target); } else { LOGE("Failed to push %s to %s", req.file, push_target); } } - // Close the process (it is necessarily already terminated) - sc_process_close(pid); - file_handler_request_destroy(&req); } return 0; diff --git a/app/src/server.c b/app/src/server.c index 6d535202..3f12db62 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -112,9 +112,9 @@ push_server(struct sc_intr *intr, const char *serial) { free(server_path); return false; } - sc_pid pid = adb_exec_push(serial, server_path, SC_DEVICE_SERVER_PATH); + bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH); free(server_path); - return sc_process_check_success_intr(intr, pid, "adb push", true); + return ok; } static const char * @@ -422,29 +422,6 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } -static char * -sc_server_get_serialno(struct sc_intr *intr) { - sc_pipe pout; - sc_pid pid = adb_exec_get_serialno(&pout); - if (pid == SC_PROCESS_NONE) { - return false; - } - - char buf[128]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); - sc_pipe_close(pout); - - bool ok = - sc_process_check_success_intr(intr, pid, "adb get-serialno", true); - if (!ok) { - return NULL; - } - - sc_str_truncate(buf, r, " \r\n"); - - return strdup(buf); -} - static bool sc_server_fill_serial(struct sc_server *server) { // Retrieve the actual device immediately if not provided, so that all @@ -453,7 +430,7 @@ sc_server_fill_serial(struct sc_server *server) { // device/emulator" error) if (!server->params.serial) { // The serial is owned by sc_server_params, and will be freed on destroy - server->params.serial = sc_server_get_serialno(&server->intr); + server->params.serial = adb_get_serialno(&server->intr); if (!server->params.serial) { LOGE("Could not get device serial"); return false; From 3fdbd994e0f0a2608eed28ca8047b8bc1ec3f92c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 22:12:56 +0100 Subject: [PATCH 0138/1133] Privatize low-level adb functions Only expose the interruptible user-friendly API. --- app/src/adb.c | 16 ++++++++-------- app/src/adb.h | 33 --------------------------------- 2 files changed, 8 insertions(+), 41 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 92a52b5b..630a1952 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -110,7 +110,7 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { free(buf); } -sc_pid +static sc_pid adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, sc_pipe *pout) { int i; @@ -148,7 +148,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { return adb_execute_p(serial, adb_cmd, len, NULL); } -sc_pid +static sc_pid adb_exec_forward(const char *serial, uint16_t local_port, const char *device_socket_name) { char local[4 + 5 + 1]; // tcp:PORT @@ -159,7 +159,7 @@ adb_exec_forward(const char *serial, uint16_t local_port, return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -sc_pid +static sc_pid adb_exec_forward_remove(const char *serial, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); @@ -167,7 +167,7 @@ adb_exec_forward_remove(const char *serial, uint16_t local_port) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -sc_pid +static sc_pid adb_exec_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT @@ -178,7 +178,7 @@ adb_exec_reverse(const char *serial, const char *device_socket_name, return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -sc_pid +static sc_pid adb_exec_reverse_remove(const char *serial, const char *device_socket_name) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); @@ -186,7 +186,7 @@ adb_exec_reverse_remove(const char *serial, const char *device_socket_name) { return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); } -sc_pid +static sc_pid adb_exec_push(const char *serial, const char *local, const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted @@ -213,7 +213,7 @@ adb_exec_push(const char *serial, const char *local, const char *remote) { return pid; } -sc_pid +static sc_pid adb_exec_install(const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted @@ -234,7 +234,7 @@ adb_exec_install(const char *serial, const char *local) { return pid; } -sc_pid +static sc_pid adb_exec_get_serialno(sc_pipe *pout) { const char *const adb_cmd[] = {"get-serialno"}; return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout); diff --git a/app/src/adb.h b/app/src/adb.h index 3714c17f..f58bc165 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -6,44 +6,11 @@ #include #include -#include "util/process.h" #include "util/intr.h" sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len); -sc_pid -adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, - sc_pipe *pout); - -sc_pid -adb_exec_forward(const char *serial, uint16_t local_port, - const char *device_socket_name); - -sc_pid -adb_exec_forward_remove(const char *serial, uint16_t local_port); - -sc_pid -adb_exec_reverse(const char *serial, const char *device_socket_name, - uint16_t local_port); - -sc_pid -adb_exec_reverse_remove(const char *serial, const char *device_socket_name); - -sc_pid -adb_exec_push(const char *serial, const char *local, const char *remote); - -sc_pid -adb_exec_install(const char *serial, const char *local); - -/** - * Execute `adb get-serialno` - * - * The result can be read from the output parameter `pout`. - */ -sc_pid -adb_exec_get_serialno(sc_pipe *pout); - bool adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name); From 2fc80eae2db098f613b45b99b6d49ea9a455724d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 22:25:15 +0100 Subject: [PATCH 0139/1133] Simplify adb_tunnel With the new adb functions, the static adb_tunnel functions become unnecessary. --- app/src/adb_tunnel.c | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index b94609e2..fa86a8a5 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -9,29 +9,6 @@ #define SC_SOCKET_NAME "scrcpy" -static bool -enable_tunnel_reverse(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - return adb_reverse(intr, serial, SC_SOCKET_NAME, local_port); -} - -static bool -disable_tunnel_reverse(struct sc_intr *intr, const char *serial) { - return adb_reverse_remove(intr, serial, SC_SOCKET_NAME); -} - -static bool -enable_tunnel_forward(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - return adb_forward(intr, serial, local_port, SC_SOCKET_NAME); -} - -static bool -disable_tunnel_forward(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - return adb_forward_remove(intr, serial, local_port); -} - static bool listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); @@ -43,7 +20,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, struct sc_port_range port_range) { uint16_t port = port_range.first; for (;;) { - if (!enable_tunnel_reverse(intr, serial, port)) { + if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port)) { // the command itself failed, it will fail on any port return false; } @@ -74,7 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, } // failure, disable tunnel and try another port - if (!disable_tunnel_reverse(intr, serial)) { + if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -104,7 +81,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, uint16_t port = port_range.first; for (;;) { - if (enable_tunnel_forward(intr, serial, port)) { + if (adb_forward(intr, serial, port, SC_SOCKET_NAME)) { // success tunnel->local_port = port; tunnel->enabled = true; @@ -169,9 +146,9 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, bool ret; if (tunnel->forward) { - ret = disable_tunnel_forward(intr, serial, tunnel->local_port); + ret = adb_forward_remove(intr, serial, tunnel->local_port); } else { - ret = disable_tunnel_reverse(intr, serial); + ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME); assert(tunnel->server_socket != SC_SOCKET_NONE); if (!net_close(tunnel->server_socket)) { From 4cfc1cd70a269c8a27f8d2ef0ac5e7eaebaae8b5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 08:06:23 +0100 Subject: [PATCH 0140/1133] Assert that long options are correctly set --- app/src/cli.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 1550c706..dabcaaf5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -569,6 +569,10 @@ sc_getopt_adapter_create_longopts(void) { size_t out_idx = 0; for (size_t i = 0; i < ARRAY_LEN(options); ++i) { const struct sc_option *in = &options[i]; + + // If longopt_id is set, then longopt must be set + assert(!in->longopt_id || in->longopt); + if (!in->longopt) { // The longopts array must only contain long options continue; From b25404ee4b6f2cbdd41992fa3e087dd8a73412c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 08:15:20 +0100 Subject: [PATCH 0141/1133] Print help to stdout The output of -h/--help was printed on stderr, although it is not an error. Printing on stdout allows to pipe the result directly: scrcpy --help | less Instead of (in bash): scrcpy --help |& less --- app/src/cli.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index dabcaaf5..aa71c659 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -675,12 +675,12 @@ print_option_usage_header(const struct sc_option *opt) { } } - fprintf(stderr, "\n %s\n", buf.s); + printf("\n %s\n", buf.s); free(buf.s); return; error: - fprintf(stderr, "\n"); + printf("\n"); } static void @@ -696,11 +696,11 @@ print_option_usage(const struct sc_option *opt, unsigned cols) { char *text = sc_str_wrap_lines(opt->text, cols, 8); if (!text) { - fprintf(stderr, "\n"); + printf("\n"); return; } - fprintf(stderr, "%s\n", text); + printf("%s\n", text); free(text); } @@ -711,11 +711,11 @@ print_shortcuts_intro(unsigned cols) { "(left) Alt or (left) Super, but it can be configured by " "--shortcut-mod (see above).", cols, 4); if (!intro) { - fprintf(stderr, "\n"); + printf("\n"); return; } - fprintf(stderr, "%s\n", intro); + printf("%s\n", intro); free(intro); } @@ -725,21 +725,21 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { assert(shortcut->shortcuts[0]); // At least one shortcut assert(shortcut->text); - fprintf(stderr, "\n"); + printf("\n"); unsigned i = 0; while (shortcut->shortcuts[i]) { - fprintf(stderr, " %s\n", shortcut->shortcuts[i]); + printf(" %s\n", shortcut->shortcuts[i]); ++i; }; char *text = sc_str_wrap_lines(shortcut->text, cols, 8); if (!text) { - fprintf(stderr, "\n"); + printf("\n"); return; } - fprintf(stderr, "%s\n", text); + printf("%s\n", text); free(text); } @@ -763,14 +763,14 @@ scrcpy_print_usage(const char *arg0) { } } - fprintf(stderr, "Usage: %s [options]\n\n" - "Options:\n", arg0); + printf("Usage: %s [options]\n\n" + "Options:\n", arg0); for (size_t i = 0; i < ARRAY_LEN(options); ++i) { print_option_usage(&options[i], cols); } // Print shortcuts section - fprintf(stderr, "\nShortcuts:\n\n"); + printf("\nShortcuts:\n\n"); print_shortcuts_intro(cols); for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { print_shortcut(&shortcuts[i], cols); From 6da6d905c2aeac17255347037d44c32a62c4c504 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 09:14:38 +0100 Subject: [PATCH 0142/1133] Print scrcpy header first Inconditionnally print the scrcpy version first, without using LOGI(). The log level is configured only after parsing the command line parameters (it may be changed via -V), and command line parsing logs should not appear before the scrcpy version. --- app/src/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index 831b98fa..690e4070 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -47,6 +47,9 @@ main(int argc, char *argv[]) { setbuf(stderr, NULL); #endif + printf("scrcpy " SCRCPY_VERSION + " \n"); + struct scrcpy_cli_args args = { .opts = scrcpy_options_default, .help = false, @@ -73,8 +76,6 @@ main(int argc, char *argv[]) { return 0; } - LOGI("scrcpy " SCRCPY_VERSION " "); - #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL av_register_all(); #endif From 2d6a96776c9e4afeba74e98f70dd98d29e226b0d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 19:02:43 +0100 Subject: [PATCH 0143/1133] Improve SSH tunnel documentation in README --- README.md | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c9080861..e0dfa5b3 100644 --- a/README.md +++ b/README.md @@ -416,17 +416,27 @@ autoadb scrcpy -s '{}' To connect to a remote device, it is possible to connect a local `adb` client to a remote `adb` server (provided they use the same version of the _adb_ -protocol): +protocol). + +First, make sure the ADB server is running on the remote computer: + +```bash +adb start-server +``` + +Then, establish a SSH tunnel: ```bash -adb kill-server # kill the local adb server on 5037 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +# local 5038 --> remote 5037 +# local 27183 <-- remote 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer # keep this open ``` -From another terminal: +From another terminal, run scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy ``` @@ -434,14 +444,16 @@ To avoid enabling remote port forwarding, you could force a forward connection instead (notice the `-L` instead of `-R`): ```bash -adb kill-server # kill the local adb server on 5037 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# local 5038 --> remote 5037 +# local 27183 --> remote 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer # keep this open ``` -From another terminal: +From another terminal, run scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy --force-adb-forward ``` From 226f3b2c915075266088d5483e385a7ce0d8b654 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Nov 2021 00:12:10 +0100 Subject: [PATCH 0144/1133] Add missing include config.h --- app/src/compat.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 9f66ce95..92b0c43f 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -8,6 +8,8 @@ # define _DARWIN_C_SOURCE #endif +#include "config.h" + #include #include From ba547e3895397e3710e7eb14faafbabbd7e3a077 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 21:24:56 +0100 Subject: [PATCH 0145/1133] Configure feature test macros in meson Refs #2807 Co-authored-by: RipleyTom --- app/meson.build | 12 ++++++++++-- app/src/compat.h | 7 ------- app/src/sys/win/process.c | 3 --- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/meson.build b/app/meson.build index 4894babc..befe1658 100644 --- a/app/meson.build +++ b/app/meson.build @@ -39,16 +39,26 @@ src = [ 'src/util/tick.c', ] +conf = configuration_data() + if host_machine.system() == 'windows' src += [ 'src/sys/win/file.c', 'src/sys/win/process.c', ] + conf.set('_WIN32_WINNT', '0x0600') + conf.set('WINVER', '0x0600') else src += [ 'src/sys/unix/file.c', 'src/sys/unix/process.c', ] + conf.set('_POSIX_C_SOURCE', '200809L') + conf.set('_XOPEN_SOURCE', '700') + conf.set('_GNU_SOURCE', true) + if host_machine.system() == 'darwin' + conf.set('_DARWIN_C_SOURCE', true) + endif endif v4l2_support = host_machine.system() == 'linux' @@ -128,8 +138,6 @@ if host_machine.system() == 'windows' dependencies += cc.find_library('ws2_32') endif -conf = configuration_data() - foreach f : check_functions if cc.has_function(f) define = 'HAVE_' + f.underscorify().to_upper() diff --git a/app/src/compat.h b/app/src/compat.h index 92b0c43f..32759c01 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -1,13 +1,6 @@ #ifndef COMPAT_H #define COMPAT_H -#define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 700 -#define _GNU_SOURCE -#ifdef __APPLE__ -# define _DARWIN_C_SOURCE -#endif - #include "config.h" #include diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 6566b80e..326a3d99 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -1,6 +1,3 @@ -// -#define _WIN32_WINNT 0x0600 // For extended process API - #include "util/process.h" #include From 52e5181c841580b582e44dbdbdf08df533637d07 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Thu, 18 Nov 2021 01:02:53 +0100 Subject: [PATCH 0146/1133] Add function to parse IPv4 addresses PR #2807 Signed-off-by: Romain Vimont --- app/src/util/net.c | 13 +++++++++++++ app/src/util/net.h | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/app/src/util/net.c b/app/src/util/net.c index 8595bc79..824d4886 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -7,6 +7,7 @@ #include "log.h" #ifdef __WINDOWS__ +# include typedef int socklen_t; typedef SOCKET sc_raw_socket; #else @@ -225,3 +226,15 @@ net_close(sc_socket socket) { return !close(raw_sock); #endif } + +bool +net_parse_ipv4(const char *s, uint32_t *ipv4) { + struct in_addr addr; + if (!inet_pton(AF_INET, s, &addr)) { + LOGE("Invalid IPv4 address: %s", s); + return false; + } + + *ipv4 = ntohl(addr.s_addr); + return true; +} diff --git a/app/src/util/net.h b/app/src/util/net.h index 57fd6c5e..15979cf9 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -68,4 +68,10 @@ net_interrupt(sc_socket socket); bool net_close(sc_socket socket); +/** + * Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation + */ +bool +net_parse_ipv4(const char *ip, uint32_t *ipv4); + #endif From 7bdbde7363836b79864f876f6a4cc107785a7147 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Thu, 18 Nov 2021 01:02:53 +0100 Subject: [PATCH 0147/1133] Add options to configure tunnel host and port In "adb forward" mode, by default, scrcpy connects to localhost:PORT, where PORT is the local port passed to "adb forward". This assumes that the tunnel is established on the local host with a local adb server (which is the common case). For advanced usage, add --tunnel-host and --tunnel-port to force the connection to a different destination. Fixes #2801 PR #2807 Signed-off-by: Romain Vimont --- app/meson.build | 1 + app/scrcpy.1 | 12 ++++++++++++ app/src/cli.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ app/src/options.c | 2 ++ app/src/options.h | 2 ++ app/src/scrcpy.c | 2 ++ app/src/server.c | 28 ++++++++++++++++++++-------- app/src/server.h | 2 ++ 8 files changed, 88 insertions(+), 8 deletions(-) diff --git a/app/meson.build b/app/meson.build index befe1658..3a5cb12a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -207,6 +207,7 @@ if get_option('buildtype') == 'debug' 'tests/test_cli.c', 'src/cli.c', 'src/options.c', + 'src/util/net.c', 'src/util/str.c', 'src/util/strbuf.c', 'src/util/term.c', diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 399fd172..7438118b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -203,6 +203,18 @@ Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy). +.TP +.BI "\-\-tunnel\-host " ip +Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. + +Default is localhost. + +.TP +.BI "\-\-tunnel\-port " port +Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. + +Default is 0 (not forced): the local port used for establishing the tunnel will be used. + .TP .BI "\-\-v4l2-sink " /dev/videoN Output to v4l2loopback device. diff --git a/app/src/cli.c b/app/src/cli.c index aa71c659..f00611a7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -9,6 +9,7 @@ #include "options.h" #include "util/log.h" +#include "util/net.h" #include "util/str.h" #include "util/strbuf.h" #include "util/term.h" @@ -46,6 +47,8 @@ #define OPT_V4L2_SINK 1027 #define OPT_DISPLAY_BUFFER 1028 #define OPT_V4L2_BUFFER 1029 +#define OPT_TUNNEL_HOST 1030 +#define OPT_TUNNEL_PORT 1031 struct sc_option { char shortopt; @@ -330,6 +333,25 @@ static const struct sc_option options[] = { "on exit.\n" "It only shows physical touches (not clicks from scrcpy).", }, + { + .longopt_id = OPT_TUNNEL_HOST, + .longopt = "tunnel-host", + .argdesc = "ip", + .text = "Set the IP address of the adb tunnel to reach the scrcpy " + "server. This option automatically enables " + "--force-adb-forward.\n" + "Default is localhost.", + }, + { + .longopt_id = OPT_TUNNEL_PORT, + .longopt = "tunnel-port", + .argdesc = "port", + .text = "Set the TCP port of the adb tunnel to reach the scrcpy " + "server. This option automatically enables " + "--force-adb-forward.\n" + "Default is 0 (not forced): the local port used for " + "establishing the tunnel will be used.", + }, #ifdef HAVE_V4L2 { .longopt_id = OPT_V4L2_SINK, @@ -1127,6 +1149,21 @@ parse_record_format(const char *optarg, enum sc_record_format *format) { return false; } +static bool +parse_ip(const char *optarg, uint32_t *ipv4) { + return net_parse_ipv4(optarg, ipv4); +} + +static bool +parse_port(const char *optarg, uint16_t *port) { + long value; + if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) { + return false; + } + *port = (uint16_t) value; + return true; +} + static enum sc_record_format guess_record_format(const char *filename) { size_t len = strlen(filename); @@ -1199,6 +1236,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_TUNNEL_HOST: + if (!parse_ip(optarg, &opts->tunnel_host)) { + return false; + } + break; + case OPT_TUNNEL_PORT: + if (!parse_port(optarg, &opts->tunnel_port)) { + return false; + } + break; case 'n': opts->control = false; break; diff --git a/app/src/options.c b/app/src/options.c index 82f25342..074bdf08 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -19,6 +19,8 @@ const struct scrcpy_options scrcpy_options_default = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST, }, + .tunnel_host = 0, + .tunnel_port = 0, .shortcut_mods = { .data = {SC_MOD_LALT, SC_MOD_LSUPER}, .count = 2, diff --git a/app/src/options.h b/app/src/options.h index 434225b9..82d094cd 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -77,6 +77,8 @@ struct scrcpy_options { enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; struct sc_port_range port_range; + uint32_t tunnel_host; + uint16_t tunnel_port; struct sc_shortcut_mods shortcut_mods; uint16_t max_size; uint32_t bit_rate; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 40bf72a8..61087681 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -345,6 +345,8 @@ scrcpy(struct scrcpy_options *options) { .log_level = options->log_level, .crop = options->crop, .port_range = options->port_range, + .tunnel_host = options->tunnel_host, + .tunnel_port = options->tunnel_port, .max_size = options->max_size, .bit_rate = options->bit_rate, .max_fps = options->max_fps, diff --git a/app/src/server.c b/app/src/server.c index 3f12db62..78127d2d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -202,8 +202,9 @@ execute_server(struct sc_server *server, } static bool -connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { - bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port); +connect_and_read_byte(struct sc_intr *intr, sc_socket socket, + uint32_t tunnel_host, uint16_t tunnel_port) { + bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port); if (!ok) { return false; } @@ -220,13 +221,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { } static sc_socket -connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) { - uint16_t port = server->tunnel.local_port; +connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay, + uint32_t host, uint16_t port) { do { LOGD("Remaining connection attempts: %d", (int) attempts); sc_socket socket = net_socket(); if (socket != SC_SOCKET_NONE) { - bool ok = connect_and_read_byte(&server->intr, socket, port); + bool ok = connect_and_read_byte(&server->intr, socket, host, port); if (ok) { // it worked! return socket; @@ -352,9 +353,20 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { goto fail; } } else { + uint32_t tunnel_host = server->params.tunnel_host; + if (!tunnel_host) { + tunnel_host = IPV4_LOCALHOST; + } + + uint16_t tunnel_port = server->params.tunnel_port; + if (!tunnel_port) { + tunnel_port = tunnel->local_port; + } + uint32_t attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); - video_socket = connect_to_server(server, attempts, delay); + video_socket = connect_to_server(server, attempts, delay, tunnel_host, + tunnel_port); if (video_socket == SC_SOCKET_NONE) { goto fail; } @@ -364,8 +376,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { if (control_socket == SC_SOCKET_NONE) { goto fail; } - bool ok = net_connect_intr(&server->intr, control_socket, - IPV4_LOCALHOST, tunnel->local_port); + bool ok = net_connect_intr(&server->intr, control_socket, tunnel_host, + tunnel_port); if (!ok) { goto fail; } diff --git a/app/src/server.h b/app/src/server.h index 3859c328..cb1fadbb 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -29,6 +29,8 @@ struct sc_server_params { const char *codec_options; const char *encoder_name; struct sc_port_range port_range; + uint32_t tunnel_host; + uint16_t tunnel_port; uint16_t max_size; uint32_t bit_rate; uint16_t max_fps; From 68eaee5bb086383e601ac64e9ff0aa65830599e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 21:07:22 +0100 Subject: [PATCH 0148/1133] Force adb forward if tunnel host/port is provided Tunnel host and port are only meaningful in "adb forward" mode. They indicate where the client must connect to communicate with the server. In "adb reverse" mode, the client _listens_, it does not _connect_. Refs #2807 --- app/src/cli.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index f00611a7..5256d50e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1405,6 +1405,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif + if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { + LOGI("Tunnel host/port is set, " + "--force-adb-forward automatically enabled."); + opts->force_adb_forward = true; + } + int index = optind; if (index < argc) { LOGE("Unexpected additional argument: %s", argv[index]); From 44721ed98235b262fc77d686ec5020c72a8dcb42 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 21:11:56 +0100 Subject: [PATCH 0149/1133] Document remote ADB server in README Refs #2807 --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0dfa5b3..f355c7b6 100644 --- a/README.md +++ b/README.md @@ -412,12 +412,47 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -#### SSH tunnel +#### Tunnels To connect to a remote device, it is possible to connect a local `adb` client to a remote `adb` server (provided they use the same version of the _adb_ protocol). +##### Remote ADB server + +To connect to a remote ADB server, make the server listen on all interfaces: + +```bash +adb kill-server +adb -a nodaemon server start +# keep this open +``` + +**Warning: all communications between clients and ADB server are unencrypted.** + +Suppose that this server is accessible at 192.168.1.2. Then, from another +terminal, run scrcpy: + +```bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +By default, scrcpy uses the local port used for `adb forward` tunnel +establishment (typically `27183`, see `--port`). It is also possible to force a +different tunnel port (it may be useful in more complex situations, when more +redirections are involved): + +``` +scrcpy --tunnel-port=1234 +``` + + +##### SSH tunnel + +To communicate with a remote ADB server securely, it is preferable to use a SSH +tunnel. + First, make sure the ADB server is running on the remote computer: ```bash From 0427a981e59ff5c5f188de5d34722c7f098ecf43 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 12:15:15 +0100 Subject: [PATCH 0150/1133] Use UINT64_C macro for uint64_t constant in tests A long constant might not be sufficient. --- app/tests/test_control_msg_serialize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index ef9247ca..b237e28e 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -78,7 +78,7 @@ static void test_serialize_inject_touch_event(void) { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_DOWN, - .pointer_id = 0x1234567887654321L, + .pointer_id = UINT64_C(0x1234567887654321), .position = { .point = { .x = 100, From ea8028332ced7ef61e755742ad9e91586e3193e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 17:39:19 +0100 Subject: [PATCH 0151/1133] Synchronize computer-to-device empty clipboard Set the device clipboard to empty string if necessary. Otherwise, the current device clipboard will be pasted on Ctrl+v. PR #2814 --- app/src/input_manager.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b84f3bea..6158f6d2 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -215,11 +215,6 @@ set_device_clipboard(struct controller *controller, bool paste) { LOGW("Could not get clipboard text: %s", SDL_GetError()); return; } - if (!*text) { - // empty text - SDL_free(text); - return; - } char *text_dup = strdup(text); SDL_free(text); From 854de9659a503291fc310ed6702bd4278bf2a725 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 17:44:00 +0100 Subject: [PATCH 0152/1133] Do not inject Ctrl+v if clipboard sync failed This prevents to paste the current Android clipboard, which would be unexpected. PR #2814 --- app/src/input_manager.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 6158f6d2..c15e0427 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -208,19 +208,19 @@ collapse_panels(struct controller *controller) { } } -static void +static bool set_device_clipboard(struct controller *controller, bool paste) { char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); - return; + return false; } char *text_dup = strdup(text); SDL_free(text); if (!text_dup) { LOGW("Could not strdup input text"); - return; + return false; } struct control_msg msg; @@ -231,7 +231,10 @@ set_device_clipboard(struct controller *controller, bool paste) { if (!controller_push_msg(controller, &msg)) { free(text_dup); LOGW("Could not request 'set device clipboard'"); + return false; } + + return true; } static void @@ -515,7 +518,11 @@ input_manager_process_key(struct input_manager *im, } // Synchronize the computer clipboard to the device clipboard before // sending Ctrl+v, to allow seamless copy-paste. - set_device_clipboard(controller, false); + bool ok = set_device_clipboard(controller, false); + if (!ok) { + LOGW("Clipboard could not be synchronized, Ctrl+v not injected"); + return; + } } im->kp->ops->process_key(im->kp, event); From 5b3856c3b6bed67abfd20585daa8eb43fa863582 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Nov 2021 17:24:34 +0100 Subject: [PATCH 0153/1133] Explicitly indicate when device clipboard is set Pass the information that device clipboard has been set to the key processor. This avoids the keyprocessor to "guess", and paves the way to implement a proper acknowledgement mechanism. PR #2814 --- app/src/hid_keyboard.c | 9 +++------ app/src/input_manager.c | 5 +++-- app/src/keyboard_inject.c | 8 +++++++- app/src/trait/key_processor.h | 11 ++++++++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 3ac1a441..2809ccd6 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -278,7 +278,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) { static void sc_key_processor_process_key(struct sc_key_processor *kp, - const SDL_KeyboardEvent *event) { + const SDL_KeyboardEvent *event, + bool device_clipboard_set) { if (event->repeat) { // In USB HID protocol, key repeat is handled by the host (Android), so // just ignore key repeat here. @@ -298,11 +299,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } } - 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) { + if (device_clipboard_set) { // 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 diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c15e0427..16c82234 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -510,7 +510,8 @@ input_manager_process_key(struct input_manager *im, return; } - if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { + bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat; + if (is_ctrl_v) { if (im->legacy_paste) { // inject the text as input events clipboard_paste(controller); @@ -525,7 +526,7 @@ input_manager_process_key(struct input_manager *im, } } - im->kp->ops->process_key(im->kp, event); + im->kp->ops->process_key(im->kp, event, is_ctrl_v); } static void diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index bcc85da8..e59b5520 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -188,7 +188,13 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, static void sc_key_processor_process_key(struct sc_key_processor *kp, - const SDL_KeyboardEvent *event) { + const SDL_KeyboardEvent *event, + bool device_clipboard_set) { + // The device clipboard synchronization and the key event messages are + // serialized, there is nothing special to do to ensure that the clipboard + // is set before injecting Ctrl+v. + (void) device_clipboard_set; + struct sc_keyboard_inject *ki = DOWNCAST(kp); if (event->repeat) { diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index 5790310b..1f8132f2 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -18,8 +18,17 @@ struct sc_key_processor { }; struct sc_key_processor_ops { + + /** + * Process the keyboard event + * + * The flag `device_clipboard_set` indicates that the input manager sent a + * control message to synchronize the device clipboard as a result of this + * key event. + */ void - (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event); + (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, + bool device_clipboard_set); void (*process_text)(struct sc_key_processor *kp, From aba1fc03c3702404b30b7a8aebd2489f8b7fed1c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 14:33:13 +0100 Subject: [PATCH 0154/1133] Add acksync helper to wait for acks This will allow to send requests with sequence numbers to the server and wait for acknowledgements. PR #2814 --- app/meson.build | 1 + app/src/util/acksync.c | 76 ++++++++++++++++++++++++++++++++++++++++++ app/src/util/acksync.h | 61 +++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 app/src/util/acksync.c create mode 100644 app/src/util/acksync.h diff --git a/app/meson.build b/app/meson.build index 3a5cb12a..d6fac580 100644 --- a/app/meson.build +++ b/app/meson.build @@ -25,6 +25,7 @@ src = [ 'src/server.c', 'src/stream.c', 'src/video_buffer.c', + 'src/util/acksync.c', 'src/util/file.c', 'src/util/intr.c', 'src/util/log.c', diff --git a/app/src/util/acksync.c b/app/src/util/acksync.c new file mode 100644 index 00000000..2899cdcb --- /dev/null +++ b/app/src/util/acksync.c @@ -0,0 +1,76 @@ +#include "acksync.h" + +#include +#include "util/log.h" + +bool +sc_acksync_init(struct sc_acksync *as) { + bool ok = sc_mutex_init(&as->mutex); + if (!ok) { + return false; + } + + ok = sc_cond_init(&as->cond); + if (!ok) { + sc_mutex_destroy(&as->mutex); + return false; + } + + as->stopped = false; + as->ack = SC_SEQUENCE_INVALID; + + return true; +} + +void +sc_acksync_destroy(struct sc_acksync *as) { + sc_cond_destroy(&as->cond); + sc_mutex_destroy(&as->mutex); +} + +void +sc_acksync_ack(struct sc_acksync *as, uint64_t sequence) { + sc_mutex_lock(&as->mutex); + + // Acknowledgements must be monotonic + assert(sequence >= as->ack); + + as->ack = sequence; + sc_cond_signal(&as->cond); + + sc_mutex_unlock(&as->mutex); +} + +enum sc_acksync_wait_result +sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline) { + sc_mutex_lock(&as->mutex); + + bool timed_out = false; + while (!as->stopped && as->ack < ack && !timed_out) { + timed_out = !sc_cond_timedwait(&as->cond, &as->mutex, deadline); + } + + enum sc_acksync_wait_result ret; + if (as->stopped) { + ret = SC_ACKSYNC_WAIT_INTR; + } else if (as->ack >= ack) { + ret = SC_ACKSYNC_WAIT_OK; + } else { + assert(timed_out); + ret = SC_ACKSYNC_WAIT_TIMEOUT; + } + sc_mutex_unlock(&as->mutex); + + return ret; +} + +/** + * Interrupt any `sc_acksync_wait()` + */ +void +sc_acksync_interrupt(struct sc_acksync *as) { + sc_mutex_lock(&as->mutex); + as->stopped = true; + sc_cond_signal(&as->cond); + sc_mutex_unlock(&as->mutex); +} diff --git a/app/src/util/acksync.h b/app/src/util/acksync.h new file mode 100644 index 00000000..1fd34444 --- /dev/null +++ b/app/src/util/acksync.h @@ -0,0 +1,61 @@ +#ifndef SC_ACK_SYNC_H +#define SC_ACK_SYNC_H + +#include "common.h" + +#include "thread.h" + +#define SC_SEQUENCE_INVALID 0 + +/** + * Helper to wait for acknowledgments + */ +struct sc_acksync { + sc_mutex mutex; + sc_cond cond; + + bool stopped; + + // Last acked value, initially SC_SEQUENCE_INVALID + uint64_t ack; +}; + +enum sc_acksync_wait_result { + // Acknowledgment received + SC_ACKSYNC_WAIT_OK, + + // Timeout expired + SC_ACKSYNC_WAIT_TIMEOUT, + + // Interrupted from another thread by sc_acksync_interrupt() + SC_ACKSYNC_WAIT_INTR, +}; + +bool +sc_acksync_init(struct sc_acksync *as); + +void +sc_acksync_destroy(struct sc_acksync *as); + +/** + * Acknowledge `sequence` + * + * The `sequence` must be greater than (or equal to) any previous acknowledged + * sequence. + */ +void +sc_acksync_ack(struct sc_acksync *as, uint64_t sequence); + +/** + * Wait for acknowledgment of sequence `ack` (or higher) + */ +enum sc_acksync_wait_result +sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline); + +/** + * Interrupt any `sc_acksync_wait()` + */ +void +sc_acksync_interrupt(struct sc_acksync *as); + +#endif From 901d8371655582b432d5d92430177a59df8058b9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 11:50:33 +0100 Subject: [PATCH 0155/1133] Add sequence number to set_clipboard request This will allow the client to request an acknowledgement. PR #2814 --- app/src/control_msg.c | 10 ++++++---- app/src/control_msg.h | 1 + app/src/input_manager.c | 1 + app/tests/test_control_msg_serialize.c | 4 +++- .../java/com/genymobile/scrcpy/ControlMessage.java | 8 +++++++- .../com/genymobile/scrcpy/ControlMessageReader.java | 7 ++++--- .../genymobile/scrcpy/ControlMessageReaderTest.java | 4 ++++ 7 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 74e3315c..83ab0b7b 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -118,11 +118,12 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { buf[1] = msg->inject_keycode.action; return 2; case CONTROL_MSG_TYPE_SET_CLIPBOARD: { - buf[1] = !!msg->set_clipboard.paste; + buffer_write64be(&buf[1], msg->set_clipboard.sequence); + buf[9] = !!msg->set_clipboard.paste; size_t len = write_string(msg->set_clipboard.text, CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, - &buf[2]); - return 2 + len; + &buf[10]); + return 10 + len; } case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: buf[1] = msg->set_screen_power_mode.mode; @@ -199,7 +200,8 @@ control_msg_log(const struct control_msg *msg) { KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); break; case CONTROL_MSG_TYPE_SET_CLIPBOARD: - LOG_CMSG("clipboard %s \"%s\"", + LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"", + msg->set_clipboard.sequence, msg->set_clipboard.paste ? "paste" : "copy", msg->set_clipboard.text); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 16492849..7352defe 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -70,6 +70,7 @@ struct control_msg { // screen may only be turned on on ACTION_DOWN } back_or_screen_on; struct { + uint64_t sequence; char *text; // owned, to be freed by free() bool paste; } set_clipboard; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 16c82234..e7afee77 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -225,6 +225,7 @@ set_device_clipboard(struct controller *controller, bool paste) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; + msg.set_clipboard.sequence = 0; // unused for now msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index b237e28e..5cd7056b 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -226,6 +226,7 @@ static void test_serialize_set_clipboard(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { + .sequence = UINT64_C(0x0102030405060708), .paste = true, .text = "hello, world!", }, @@ -233,10 +234,11 @@ static void test_serialize_set_clipboard(void) { unsigned char buf[CONTROL_MSG_MAX_SIZE]; size_t size = control_msg_serialize(&msg, buf); - assert(size == 19); + assert(size == 27); const unsigned char expected[] = { CONTROL_MSG_TYPE_SET_CLIPBOARD, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence 1, // paste 0x00, 0x00, 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index f8edd53c..a536ed91 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -31,6 +31,7 @@ public final class ControlMessage { private int vScroll; private boolean paste; private int repeat; + private long sequence; private ControlMessage() { } @@ -79,9 +80,10 @@ public final class ControlMessage { return msg; } - public static ControlMessage createSetClipboard(String text, boolean paste) { + public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; + msg.sequence = sequence; msg.text = text; msg.paste = paste; return msg; @@ -154,4 +156,8 @@ public final class ControlMessage { public int getRepeat() { return repeat; } + + public long getSequence() { + return sequence; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index e4ab8402..80931e94 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -13,11 +13,11 @@ public class ControlMessageReader { static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; - static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; + static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k - public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes + public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes public static final int INJECT_TEXT_MAX_LENGTH = 300; private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE]; @@ -166,12 +166,13 @@ public class ControlMessageReader { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { return null; } + long sequence = buffer.getLong(); boolean paste = buffer.get() != 0; String text = parseString(); if (text == null) { return null; } - return ControlMessage.createSetClipboard(text, paste); + return ControlMessage.createSetClipboard(sequence, text, paste); } private ControlMessage parseSetScreenPowerMode() { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 7f3d3f61..3b0b6c0d 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -235,6 +235,7 @@ public class ControlMessageReaderTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); + dos.writeLong(0x0102030405060708L); // sequence dos.writeByte(1); // paste byte[] text = "testé".getBytes(StandardCharsets.UTF_8); dos.writeInt(text.length); @@ -246,6 +247,7 @@ public class ControlMessageReaderTest { ControlMessage event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); + Assert.assertEquals(0x0102030405060708L, event.getSequence()); Assert.assertEquals("testé", event.getText()); Assert.assertTrue(event.getPaste()); } @@ -259,6 +261,7 @@ public class ControlMessageReaderTest { dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD); byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH]; + dos.writeLong(0x0807060504030201L); // sequence dos.writeByte(1); // paste Arrays.fill(rawText, (byte) 'a'); String text = new String(rawText, 0, rawText.length); @@ -272,6 +275,7 @@ public class ControlMessageReaderTest { ControlMessage event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); + Assert.assertEquals(0x0807060504030201L, event.getSequence()); Assert.assertEquals(text, event.getText()); Assert.assertTrue(event.getPaste()); } From 2a0730ee9bacc8df92c7c38ce83a735182d685e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 12:10:09 +0100 Subject: [PATCH 0156/1133] Add device clipboard set acknowledgement Add a device message type so that the device could send acknowledgements for SET_CLIPBOARD requests. PR #2814 --- app/src/device_msg.c | 6 ++++++ app/src/device_msg.h | 4 ++++ app/src/receiver.c | 3 +++ app/tests/test_device_msg_deserialize.c | 15 ++++++++++++++ .../com/genymobile/scrcpy/DeviceMessage.java | 13 ++++++++++++ .../scrcpy/DeviceMessageWriter.java | 6 +++++- .../scrcpy/DeviceMessageWriterTest.java | 20 +++++++++++++++++++ 7 files changed, 66 insertions(+), 1 deletion(-) diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 827f4213..4163b9fc 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -1,5 +1,6 @@ #include "device_msg.h" +#include #include #include @@ -34,6 +35,11 @@ device_msg_deserialize(const unsigned char *buf, size_t len, msg->clipboard.text = text; return 5 + clipboard_len; } + case DEVICE_MSG_TYPE_ACK_CLIPBOARD: { + uint64_t sequence = buffer_read64be(&buf[1]); + msg->ack_clipboard.sequence = sequence; + return 9; + } default: LOGW("Unknown device message type: %d", (int) msg->type); return -1; // error, we cannot recover diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 888d9216..a0c989e3 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -13,6 +13,7 @@ enum device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, + DEVICE_MSG_TYPE_ACK_CLIPBOARD, }; struct device_msg { @@ -21,6 +22,9 @@ struct device_msg { struct { char *text; // owned, to be freed by free() } clipboard; + struct { + uint64_t sequence; + } ack_clipboard; }; }; diff --git a/app/src/receiver.c b/app/src/receiver.c index b5cf9b39..52e6034d 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -37,6 +37,9 @@ process_msg(struct device_msg *msg) { SDL_SetClipboardText(msg->clipboard.text); break; } + case DEVICE_MSG_TYPE_ACK_CLIPBOARD: + // TODO + break; } } diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 3427d640..835096c0 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -47,11 +47,26 @@ static void test_deserialize_clipboard_big(void) { device_msg_destroy(&msg); } +static void test_deserialize_ack_set_clipboard(void) { + const unsigned char input[] = { + DEVICE_MSG_TYPE_ACK_CLIPBOARD, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence + }; + + struct device_msg msg; + ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); + assert(r == 9); + + assert(msg.type == DEVICE_MSG_TYPE_ACK_CLIPBOARD); + assert(msg.ack_clipboard.sequence == UINT64_C(0x0102030405060708)); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; test_deserialize_clipboard(); test_deserialize_clipboard_big(); + test_deserialize_ack_set_clipboard(); return 0; } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java index c6eebd38..2e333e3f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -3,9 +3,11 @@ package com.genymobile.scrcpy; public final class DeviceMessage { public static final int TYPE_CLIPBOARD = 0; + public static final int TYPE_ACK_CLIPBOARD = 1; private int type; private String text; + private long sequence; private DeviceMessage() { } @@ -17,6 +19,13 @@ public final class DeviceMessage { return event; } + public static DeviceMessage createAckClipboard(long sequence) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_ACK_CLIPBOARD; + event.sequence = sequence; + return event; + } + public int getType() { return type; } @@ -24,4 +33,8 @@ public final class DeviceMessage { public String getText() { return text; } + + public long getSequence() { + return sequence; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index 15d91a35..bcd8d206 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -15,7 +15,7 @@ public class DeviceMessageWriter { public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { buffer.clear(); - buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD); + buffer.put((byte) msg.getType()); switch (msg.getType()) { case DeviceMessage.TYPE_CLIPBOARD: String text = msg.getText(); @@ -25,6 +25,10 @@ public class DeviceMessageWriter { buffer.put(raw, 0, len); output.write(rawBuffer, 0, buffer.position()); break; + case DeviceMessage.TYPE_ACK_CLIPBOARD: + buffer.putLong(msg.getSequence()); + output.write(rawBuffer, 0, buffer.position()); + break; default: Ln.w("Unknown device message: " + msg.getType()); break; diff --git a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java index 88bf2af9..7b917d33 100644 --- a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java @@ -32,4 +32,24 @@ public class DeviceMessageWriterTest { Assert.assertArrayEquals(expected, actual); } + + @Test + public void testSerializeAckSetClipboard() throws IOException { + DeviceMessageWriter writer = new DeviceMessageWriter(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD); + dos.writeLong(0x0102030405060708L); + + byte[] expected = bos.toByteArray(); + + DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L); + bos = new ByteArrayOutputStream(); + writer.writeTo(msg, bos); + + byte[] actual = bos.toByteArray(); + + Assert.assertArrayEquals(expected, actual); + } } From 41abe021e2a73efd4899b0efcd0b9eef9ec68c9b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Nov 2021 12:23:45 +0100 Subject: [PATCH 0157/1133] Make the device acknowledge device clipboard If the client provided a sequence number on SET_CLIPBOARD request, make the device send back an acknowledgement once the clipboard is set. PR #2814 --- .../com/genymobile/scrcpy/ControlMessage.java | 2 ++ .../com/genymobile/scrcpy/Controller.java | 5 ++++ .../com/genymobile/scrcpy/DeviceMessage.java | 2 ++ .../scrcpy/DeviceMessageSender.java | 24 ++++++++++++++++--- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index a536ed91..2cd80191 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -18,6 +18,8 @@ public final class ControlMessage { public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_ROTATE_DEVICE = 11; + public static final long SEQUENCE_INVALID = 0; + private int type; private String text; private int metaState; // KeyEvent.META_* diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 45882bb9..8b24b300 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -120,7 +120,12 @@ public class Controller { } break; case ControlMessage.TYPE_SET_CLIPBOARD: + long sequence = msg.getSequence(); setClipboard(msg.getText(), msg.getPaste()); + if (sequence != ControlMessage.SEQUENCE_INVALID) { + // Acknowledgement requested + sender.pushAckClipboard(sequence); + } break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java index 2e333e3f..5b7c4de5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -5,6 +5,8 @@ public final class DeviceMessage { public static final int TYPE_CLIPBOARD = 0; public static final int TYPE_ACK_CLIPBOARD = 1; + public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID; + private int type; private String text; private long sequence; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index bbf4dd2e..4ebccacc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -8,6 +8,8 @@ public final class DeviceMessageSender { private String clipboardText; + private long ack; + public DeviceMessageSender(DesktopConnection connection) { this.connection = connection; } @@ -17,18 +19,34 @@ public final class DeviceMessageSender { notify(); } + public synchronized void pushAckClipboard(long sequence) { + ack = sequence; + notify(); + } + public void loop() throws IOException, InterruptedException { while (true) { String text; + long sequence; synchronized (this) { - while (clipboardText == null) { + while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) { wait(); } text = clipboardText; clipboardText = null; + + sequence = ack; + ack = DeviceMessage.SEQUENCE_INVALID; + } + + if (sequence != DeviceMessage.SEQUENCE_INVALID) { + DeviceMessage event = DeviceMessage.createAckClipboard(sequence); + connection.sendDeviceMessage(event); + } + if (text != null) { + DeviceMessage event = DeviceMessage.createClipboard(text); + connection.sendDeviceMessage(event); } - DeviceMessage event = DeviceMessage.createClipboard(text); - connection.sendDeviceMessage(event); } } } From 2d5525eac128c6bb02344d733ab487b3aedd2431 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Nov 2021 22:11:08 +0100 Subject: [PATCH 0158/1133] Move PRIu64 Windows workaround to compat.h So that we can use it from several files. PR #2814 --- app/src/compat.h | 6 ++++++ app/src/control_msg.c | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/compat.h b/app/src/compat.h index 32759c01..c06c23fa 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -6,6 +6,12 @@ #include #include +#ifndef __WIN32 +# define PRIu64_ PRIu64 +#else +# define PRIu64_ "I64u" // Windows... +#endif + // In ffmpeg/doc/APIchanges: // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // Deprecate use of av_register_input_format(), av_register_output_format(), diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 83ab0b7b..90cde0cf 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -171,11 +171,6 @@ control_msg_log(const struct control_msg *msg) { (long) msg->inject_touch_event.buttons); } else { // numeric pointer id -#ifndef __WIN32 -# define PRIu64_ PRIu64 -#else -# define PRIu64_ "I64u" // Windows... -#endif LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" PRIi32 " pressure=%g buttons=%06lx", id, From 5d17bcf1bc38690739c88ad2b0a069a67c8bedce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Nov 2021 17:40:11 +0100 Subject: [PATCH 0159/1133] Wait SET_CLIPBOARD ack before Ctrl+v via HID To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is performed before injecting Ctrl+v. But when HID keyboard is enabled, the Ctrl+v injection is not sent on the same channel as the clipboard request, so they are not serialized, and may occur in any order. If Ctrl+v happens to be injected before the new clipboard content is set, then the old content is pasted instead, which is incorrect. To minimize the probability of occurrence of the wrong order, a delay of 2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even with 5ms, the wrong behavior sometimes happens. To handle it properly, add an acknowledgement mechanism, so that Ctrl+v is injected over AOA only after the SET_CLIPBOARD request has been performed and acknowledged by the server. Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0 Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a PR #2814 --- app/src/aoa_hid.c | 41 ++++++++++++++++++++++------------- app/src/aoa_hid.h | 7 ++++-- app/src/controller.c | 5 +++-- app/src/controller.h | 4 +++- app/src/hid_keyboard.c | 16 +++++++++----- app/src/input_manager.c | 30 ++++++++++++++++++++----- app/src/input_manager.h | 2 ++ app/src/keyboard_inject.c | 6 +++-- app/src/receiver.c | 19 +++++++++++----- app/src/receiver.h | 6 ++++- app/src/scrcpy.c | 23 ++++++++++++++++++-- app/src/trait/key_processor.h | 18 +++++++++++---- app/src/util/acksync.h | 5 +++++ 13 files changed, 135 insertions(+), 47 deletions(-) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index 4c0b2bda..6fd32610 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -35,7 +35,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, hid_event->accessory_id = accessory_id; hid_event->buffer = buffer; hid_event->size = buffer_size; - hid_event->delay = 0; + hid_event->ack_to_wait = SC_SEQUENCE_INVALID; } void @@ -118,7 +118,10 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) { } bool -sc_aoa_init(struct sc_aoa *aoa, const char *serial) { +sc_aoa_init(struct sc_aoa *aoa, const char *serial, + struct sc_acksync *acksync) { + assert(acksync); + cbuf_init(&aoa->queue); if (!sc_mutex_init(&aoa->mutex)) { @@ -155,6 +158,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) { } aoa->stopped = false; + aoa->acksync = acksync; return true; } @@ -332,23 +336,28 @@ run_aoa_thread(void *data) { 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); + uint64_t ack_to_wait = event.ack_to_wait; + sc_mutex_unlock(&aoa->mutex); + + if (ack_to_wait != SC_SEQUENCE_INVALID) { + LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); + // Do not block the loop indefinitely if the ack never comes (it should + // never happen) + sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); + enum sc_acksync_wait_result result = + sc_acksync_wait(aoa->acksync, ack_to_wait, deadline); + + if (result == SC_ACKSYNC_WAIT_TIMEOUT) { + LOGW("Ack not received after 500ms, discarding HID event"); + sc_hid_event_destroy(&event); + continue; + } else if (result == SC_ACKSYNC_WAIT_INTR) { + // stopped + sc_hid_event_destroy(&event); break; } } - sc_mutex_unlock(&aoa->mutex); - bool ok = sc_aoa_send_hid_event(aoa, &event); sc_hid_event_destroy(&event); if (!ok) { @@ -377,6 +386,8 @@ sc_aoa_stop(struct sc_aoa *aoa) { aoa->stopped = true; sc_cond_signal(&aoa->event_cond); sc_mutex_unlock(&aoa->mutex); + + sc_acksync_interrupt(aoa->acksync); } void diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h index 24cef502..e8fb9708 100644 --- a/app/src/aoa_hid.h +++ b/app/src/aoa_hid.h @@ -6,6 +6,7 @@ #include +#include "util/acksync.h" #include "util/cbuf.h" #include "util/thread.h" #include "util/tick.h" @@ -14,7 +15,7 @@ struct sc_hid_event { uint16_t accessory_id; unsigned char *buffer; uint16_t size; - sc_tick delay; + uint64_t ack_to_wait; }; // Takes ownership of buffer @@ -36,10 +37,12 @@ struct sc_aoa { sc_cond event_cond; bool stopped; struct sc_hid_event_queue queue; + + struct sc_acksync *acksync; }; bool -sc_aoa_init(struct sc_aoa *aoa, const char *serial); +sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync); void sc_aoa_destroy(struct sc_aoa *aoa); diff --git a/app/src/controller.c b/app/src/controller.c index e486ea72..6cf3d20e 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -5,10 +5,11 @@ #include "util/log.h" bool -controller_init(struct controller *controller, sc_socket control_socket) { +controller_init(struct controller *controller, sc_socket control_socket, + struct sc_acksync *acksync) { cbuf_init(&controller->queue); - bool ok = receiver_init(&controller->receiver, control_socket); + bool ok = receiver_init(&controller->receiver, control_socket, acksync); if (!ok) { return false; } diff --git a/app/src/controller.h b/app/src/controller.h index e7004131..00267878 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -7,6 +7,7 @@ #include "control_msg.h" #include "receiver.h" +#include "util/acksync.h" #include "util/cbuf.h" #include "util/net.h" #include "util/thread.h" @@ -24,7 +25,8 @@ struct controller { }; bool -controller_init(struct controller *controller, sc_socket control_socket); +controller_init(struct controller *controller, sc_socket control_socket, + struct sc_acksync *acksync); void controller_destroy(struct controller *controller); diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 2809ccd6..2afc09b2 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -279,7 +279,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) { static void sc_key_processor_process_key(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, - bool device_clipboard_set) { + uint64_t ack_to_wait) { if (event->repeat) { // In USB HID protocol, key repeat is handled by the host (Android), so // just ignore key repeat here. @@ -299,12 +299,12 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } } - if (device_clipboard_set) { + if (ack_to_wait) { // 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(5); + // requested. Wait until clipboard synchronization is acknowledged + // by the server, otherwise it could paste the old clipboard + // content. + hid_event.ack_to_wait = ack_to_wait; } if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { @@ -345,6 +345,10 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { .process_text = sc_key_processor_process_text, }; + // Clipboard synchronization is requested over the control socket, while HID + // events are sent over AOA, so it must wait for clipboard synchronization + // to be acknowledged by the device before injecting Ctrl+v. + kb->key_processor.async_paste = true; kb->key_processor.ops = &ops; return true; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e7afee77..f1715b79 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -82,6 +82,8 @@ input_manager_init(struct input_manager *im, struct controller *controller, im->last_keycode = SDLK_UNKNOWN; im->last_mod = 0; im->key_repeat = 0; + + im->next_sequence = 1; // 0 is reserved for SC_SEQUENCE_INVALID } static void @@ -209,7 +211,8 @@ collapse_panels(struct controller *controller) { } static bool -set_device_clipboard(struct controller *controller, bool paste) { +set_device_clipboard(struct controller *controller, bool paste, + uint64_t sequence) { char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); @@ -225,7 +228,7 @@ set_device_clipboard(struct controller *controller, bool paste) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; - msg.set_clipboard.sequence = 0; // unused for now + msg.set_clipboard.sequence = sequence; msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; @@ -461,8 +464,10 @@ input_manager_process_key(struct input_manager *im, // inject the text as input events clipboard_paste(controller); } else { - // store the text in the device clipboard and paste - set_device_clipboard(controller, true); + // store the text in the device clipboard and paste, + // without requesting an acknowledgment + set_device_clipboard(controller, true, + SC_SEQUENCE_INVALID); } } return; @@ -511,6 +516,7 @@ input_manager_process_key(struct input_manager *im, return; } + uint64_t ack_to_wait = SC_SEQUENCE_INVALID; bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat; if (is_ctrl_v) { if (im->legacy_paste) { @@ -518,16 +524,28 @@ input_manager_process_key(struct input_manager *im, clipboard_paste(controller); return; } + + // Request an acknowledgement only if necessary + uint64_t sequence = im->kp->async_paste ? im->next_sequence + : SC_SEQUENCE_INVALID; + // Synchronize the computer clipboard to the device clipboard before // sending Ctrl+v, to allow seamless copy-paste. - bool ok = set_device_clipboard(controller, false); + bool ok = set_device_clipboard(controller, false, sequence); if (!ok) { LOGW("Clipboard could not be synchronized, Ctrl+v not injected"); return; } + + if (im->kp->async_paste) { + // The key processor must wait for this ack before injecting Ctrl+v + ack_to_wait = sequence; + // Increment only when the request succeeded + ++im->next_sequence; + } } - im->kp->ops->process_key(im->kp, event, is_ctrl_v); + im->kp->ops->process_key(im->kp, event, ack_to_wait); } static void diff --git a/app/src/input_manager.h b/app/src/input_manager.h index f018f98a..22d77381 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -38,6 +38,8 @@ struct input_manager { unsigned key_repeat; SDL_Keycode last_keycode; uint16_t last_mod; + + uint64_t next_sequence; // used for request acknowledgements }; void diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index e59b5520..4112fd4c 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -189,11 +189,11 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, static void sc_key_processor_process_key(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, - bool device_clipboard_set) { + uint64_t ack_to_wait) { // The device clipboard synchronization and the key event messages are // serialized, there is nothing special to do to ensure that the clipboard // is set before injecting Ctrl+v. - (void) device_clipboard_set; + (void) ack_to_wait; struct sc_keyboard_inject *ki = DOWNCAST(kp); @@ -256,5 +256,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki, .process_text = sc_key_processor_process_text, }; + // Key injection and clipboard synchronization are serialized + ki->key_processor.async_paste = false; ki->key_processor.ops = &ops; } diff --git a/app/src/receiver.c b/app/src/receiver.c index 52e6034d..eeb206f1 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -7,12 +7,16 @@ #include "util/log.h" bool -receiver_init(struct receiver *receiver, sc_socket control_socket) { +receiver_init(struct receiver *receiver, sc_socket control_socket, + struct sc_acksync *acksync) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { return false; } + receiver->control_socket = control_socket; + receiver->acksync = acksync; + return true; } @@ -22,7 +26,7 @@ receiver_destroy(struct receiver *receiver) { } static void -process_msg(struct device_msg *msg) { +process_msg(struct receiver *receiver, struct device_msg *msg) { switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { char *current = SDL_GetClipboardText(); @@ -38,13 +42,16 @@ process_msg(struct device_msg *msg) { break; } case DEVICE_MSG_TYPE_ACK_CLIPBOARD: - // TODO + assert(receiver->acksync); + LOGD("Ack device clipboard sequence=%" PRIu64_, + msg->ack_clipboard.sequence); + sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence); break; } } static ssize_t -process_msgs(const unsigned char *buf, size_t len) { +process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) { size_t head = 0; for (;;) { struct device_msg msg; @@ -56,7 +63,7 @@ process_msgs(const unsigned char *buf, size_t len) { return head; } - process_msg(&msg); + process_msg(receiver, &msg); device_msg_destroy(&msg); head += r; @@ -84,7 +91,7 @@ run_receiver(void *data) { } head += r; - ssize_t consumed = process_msgs(buf, head); + ssize_t consumed = process_msgs(receiver, buf, head); if (consumed == -1) { // an error occurred break; diff --git a/app/src/receiver.h b/app/src/receiver.h index 99f128a4..3c4e8c64 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -5,6 +5,7 @@ #include +#include "util/acksync.h" #include "util/net.h" #include "util/thread.h" @@ -14,10 +15,13 @@ struct receiver { sc_socket control_socket; sc_thread thread; sc_mutex mutex; + + struct sc_acksync *acksync; }; bool -receiver_init(struct receiver *receiver, sc_socket control_socket); +receiver_init(struct receiver *receiver, sc_socket control_socket, + struct sc_acksync *acksync); void receiver_destroy(struct receiver *receiver); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 61087681..04239bf5 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -27,6 +27,7 @@ #include "screen.h" #include "server.h" #include "stream.h" +#include "util/acksync.h" #include "util/log.h" #include "util/net.h" #ifdef HAVE_V4L2 @@ -46,6 +47,8 @@ struct scrcpy { struct file_handler file_handler; #ifdef HAVE_AOA_HID struct sc_aoa aoa; + // sequence/ack helper to synchronize clipboard and Ctrl+v via HID + struct sc_acksync acksync; #endif union { struct sc_keyboard_inject keyboard_inject; @@ -340,6 +343,8 @@ scrcpy(struct scrcpy_options *options) { bool controller_started = false; bool screen_initialized = false; + struct sc_acksync *acksync = NULL; + struct sc_server_params params = { .serial = options->serial, .log_level = options->log_level, @@ -445,7 +450,18 @@ scrcpy(struct scrcpy_options *options) { } if (options->control) { - if (!controller_init(&s->controller, s->server.control_socket)) { +#ifdef HAVE_AOA_HID + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { + bool ok = sc_acksync_init(&s->acksync); + if (!ok) { + goto end; + } + + acksync = &s->acksync; + } +#endif + if (!controller_init(&s->controller, s->server.control_socket, + acksync)) { goto end; } controller_initialized = true; @@ -521,7 +537,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_AOA_HID bool aoa_hid_ok = false; - bool ok = sc_aoa_init(&s->aoa, serial); + bool ok = sc_aoa_init(&s->aoa, serial, acksync); if (!ok) { goto aoa_hid_end; } @@ -586,6 +602,9 @@ end: sc_hid_keyboard_destroy(&s->keyboard_hid); sc_aoa_stop(&s->aoa); } + if (acksync) { + sc_acksync_destroy(acksync); + } #endif if (controller_started) { controller_stop(&s->controller); diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index 1f8132f2..f4afe27b 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -14,6 +14,15 @@ * Component able to process and inject keys should implement this trait. */ struct sc_key_processor { + /** + * Set by the implementation to indicate that it must explicitly wait for + * the clipboard to be set on the device before injecting Ctrl+v to avoid + * race conditions. If it is set, the input_manager will pass a valid + * ack_to_wait to process_key() in case of clipboard synchronization + * resulting of the key event. + */ + bool async_paste; + const struct sc_key_processor_ops *ops; }; @@ -22,13 +31,14 @@ struct sc_key_processor_ops { /** * Process the keyboard event * - * The flag `device_clipboard_set` indicates that the input manager sent a - * control message to synchronize the device clipboard as a result of this - * key event. + * The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates + * the acknowledgement number to wait for before injecting this event. + * This allows to ensure that the device clipboard is set before injecting + * Ctrl+v on the device. */ void (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, - bool device_clipboard_set); + uint64_t ack_to_wait); void (*process_text)(struct sc_key_processor *kp, diff --git a/app/src/util/acksync.h b/app/src/util/acksync.h index 1fd34444..58ab1b35 100644 --- a/app/src/util/acksync.h +++ b/app/src/util/acksync.h @@ -9,6 +9,11 @@ /** * Helper to wait for acknowledgments + * + * In practice, it is used to wait for device clipboard acknowledgement from the + * server before injecting Ctrl+v via AOA HID, in order to avoid pasting the + * content of the old device clipboard (if Ctrl+v was injected before the + * clipboard content was actually set). */ struct sc_acksync { sc_mutex mutex; From 6abff46c9f8414bfc62cc8bb85c10555ab62eff9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 22 Nov 2021 08:49:10 +0100 Subject: [PATCH 0160/1133] Add option to disable clipboard autosync By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes. This new option --no-clipboard-autosync disables this automatic synchronization. Fixes #2228 PR #2817 --- README.md | 3 +++ app/scrcpy.1 | 6 ++++++ app/src/cli.c | 13 +++++++++++++ app/src/input_manager.c | 3 ++- app/src/input_manager.h | 1 + app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 1 + app/src/server.h | 1 + .../src/main/java/com/genymobile/scrcpy/Device.java | 4 ++-- .../main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../src/main/java/com/genymobile/scrcpy/Server.java | 5 ++++- 13 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f355c7b6..fa4aff68 100644 --- a/README.md +++ b/README.md @@ -730,6 +730,9 @@ of Ctrl+v and MOD+v so that they also inject the computer clipboard text as a sequence of key events (the same way as MOD+Shift+v). +To disable automatic clipboard synchronization, use +`--no-clipboard-autosync`. + #### Pinch-to-zoom To simulate "pinch-to-zoom": Ctrl+_click-and-move_. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7438118b..122af757 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -114,6 +114,12 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension Default is 0 (unlimited). +.TP +.B \-\-no\-clipboard\-autosync +By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes. + +This option disables this automatic synchronization. + .TP .B \-n, \-\-no\-control Disable device control (mirror the device in read\-only). diff --git a/app/src/cli.c b/app/src/cli.c index 5256d50e..29cb8932 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -49,6 +49,7 @@ #define OPT_V4L2_BUFFER 1029 #define OPT_TUNNEL_HOST 1030 #define OPT_TUNNEL_PORT 1031 +#define OPT_NO_CLIPBOARD_AUTOSYNC 1032 struct sc_option { char shortopt; @@ -208,6 +209,15 @@ static const struct sc_option options[] = { "is preserved.\n" "Default is 0 (unlimited).", }, + { + .longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC, + .longopt = "no-clipboard-autosync", + .text = "By default, scrcpy automatically synchronizes the computer " + "clipboard to the device clipboard before injecting Ctrl+v, " + "and the device clipboard to the computer clipboard whenever " + "it changes.\n" + "This option disables this automatic synchronization." + }, { .shortopt = 'n', .longopt = "no-control", @@ -1364,6 +1374,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_NO_CLIPBOARD_AUTOSYNC: + opts->clipboard_autosync = false; + break; #ifdef HAVE_V4L2 case OPT_V4L2_SINK: opts->v4l2_device = optarg; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f1715b79..31d6540f 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -66,6 +66,7 @@ input_manager_init(struct input_manager *im, struct controller *controller, im->control = options->control; im->forward_all_clicks = options->forward_all_clicks; im->legacy_paste = options->legacy_paste; + im->clipboard_autosync = options->clipboard_autosync; const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; assert(shortcut_mods->count); @@ -518,7 +519,7 @@ input_manager_process_key(struct input_manager *im, uint64_t ack_to_wait = SC_SEQUENCE_INVALID; bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat; - if (is_ctrl_v) { + if (im->clipboard_autosync && is_ctrl_v) { if (im->legacy_paste) { // inject the text as input events clipboard_paste(controller); diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 22d77381..5e02b457 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -24,6 +24,7 @@ struct input_manager { bool control; bool forward_all_clicks; bool legacy_paste; + bool clipboard_autosync; struct { unsigned data[SC_MAX_SHORTCUT_MODS]; diff --git a/app/src/options.c b/app/src/options.c index 074bdf08..a99b09da 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -53,4 +53,5 @@ const struct scrcpy_options scrcpy_options_default = { .forward_all_clicks = false, .legacy_paste = false, .power_off_on_close = false, + .clipboard_autosync = true, }; diff --git a/app/src/options.h b/app/src/options.h index 82d094cd..d10b2e8a 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -108,6 +108,7 @@ struct scrcpy_options { bool forward_all_clicks; bool legacy_paste; bool power_off_on_close; + bool clipboard_autosync; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 04239bf5..061a4e76 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -364,6 +364,7 @@ scrcpy(struct scrcpy_options *options) { .encoder_name = options->encoder_name, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, + .clipboard_autosync = options->clipboard_autosync, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index 78127d2d..546216e9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -186,6 +186,7 @@ execute_server(struct sc_server *server, params->codec_options ? params->codec_options : "-", params->encoder_name ? params->encoder_name : "-", params->power_off_on_close ? "true" : "false", + params->clipboard_autosync ? "true" : "false", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index cb1fadbb..5b25ff46 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -41,6 +41,7 @@ struct sc_server_params { bool stay_awake; bool force_adb_forward; bool power_off_on_close; + bool clipboard_autosync; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 093646e2..03ae9b22 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -82,8 +82,8 @@ public final class Device { } }, displayId); - if (options.getControl()) { - // If control is enabled, synchronize Android clipboard to the computer automatically + if (options.getControl() && options.getClipboardAutosync()) { + // If control and autosync are enabled, synchronize Android clipboard to the computer automatically ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); if (clipboardManager != null) { clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index cf11df0f..74467c7c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -18,6 +18,7 @@ public class Options { private String codecOptions; private String encoderName; private boolean powerOffScreenOnClose; + private boolean clipboardAutosync; public Ln.Level getLogLevel() { return logLevel; @@ -138,4 +139,12 @@ public class Options { public boolean getPowerOffScreenOnClose() { return this.powerOffScreenOnClose; } + + public boolean getClipboardAutosync() { + return clipboardAutosync; + } + + public void setClipboardAutosync(boolean clipboardAutosync) { + this.clipboardAutosync = clipboardAutosync; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5a1f4619..188974ff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -160,7 +160,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 16; + final int expectedParameters = 17; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -213,6 +213,9 @@ public final class Server { boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]); options.setPowerOffScreenOnClose(powerOffScreenOnClose); + boolean clipboardAutosync = Boolean.parseBoolean(args[16]); + options.setClipboardAutosync(clipboardAutosync); + return options; } From dc0ac01e0089c42703e422e00af439c57e41b9a4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 19:36:33 +0100 Subject: [PATCH 0161/1133] Define common feature test macros for all systems _POSIX_C_SOURCE, _XOPEN_SOURCE and _GNU_SOURCE are also used on Windows. Fix regression introduced by ba547e3895397e3710e7eb14faafbabbd7e3a077. --- app/meson.build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/meson.build b/app/meson.build index d6fac580..71a0b2e3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -42,6 +42,10 @@ src = [ conf = configuration_data() +conf.set('_POSIX_C_SOURCE', '200809L') +conf.set('_XOPEN_SOURCE', '700') +conf.set('_GNU_SOURCE', true) + if host_machine.system() == 'windows' src += [ 'src/sys/win/file.c', @@ -54,9 +58,6 @@ else 'src/sys/unix/file.c', 'src/sys/unix/process.c', ] - conf.set('_POSIX_C_SOURCE', '200809L') - conf.set('_XOPEN_SOURCE', '700') - conf.set('_GNU_SOURCE', true) if host_machine.system() == 'darwin' conf.set('_DARWIN_C_SOURCE', true) endif From 6f487a28928c396fb6d1d5bb8d58f5b5f5df4d29 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 19:37:33 +0100 Subject: [PATCH 0162/1133] Add missing includes in compat implementation These includes are necessary for the strdup() compat implementation. --- app/src/compat.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/compat.c b/app/src/compat.c index b3b98bf1..4d084517 100644 --- a/app/src/compat.c +++ b/app/src/compat.c @@ -2,6 +2,9 @@ #include "config.h" +#include +#include + #ifndef HAVE_STRDUP char *strdup(const char *s) { size_t size = strlen(s) + 1; From d6c0054545dda7b105ecda51c4f99a221dc36f79 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 19:38:33 +0100 Subject: [PATCH 0163/1133] Move check_functions in meson script Move the variable to the place it is actually used. --- app/meson.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/meson.build b/app/meson.build index 71a0b2e3..32adf18b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -76,10 +76,6 @@ if aoa_hid_support ] endif -check_functions = [ - 'strdup' -] - cc = meson.get_compiler('c') if not get_option('crossbuild_windows') @@ -140,6 +136,10 @@ if host_machine.system() == 'windows' dependencies += cc.find_library('ws2_32') endif +check_functions = [ + 'strdup', +] + foreach f : check_functions if cc.has_function(f) define = 'HAVE_' + f.underscorify().to_upper() From d5f6697f3a592245e8bd12a0ea8dbd24d3129296 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 19:55:00 +0100 Subject: [PATCH 0164/1133] Add (v)asprintf compatibility functions In case they are not available on the platform. --- app/meson.build | 2 ++ app/src/compat.c | 36 ++++++++++++++++++++++++++++++++++++ app/src/compat.h | 8 ++++++++ 3 files changed, 46 insertions(+) diff --git a/app/meson.build b/app/meson.build index 32adf18b..1561fdcd 100644 --- a/app/meson.build +++ b/app/meson.build @@ -138,6 +138,8 @@ endif check_functions = [ 'strdup', + 'asprintf', + 'vasprintf', ] foreach f : check_functions diff --git a/app/src/compat.c b/app/src/compat.c index 4d084517..11ddd3cb 100644 --- a/app/src/compat.c +++ b/app/src/compat.c @@ -2,7 +2,10 @@ #include "config.h" +#include #include +#include +#include #include #ifndef HAVE_STRDUP @@ -15,3 +18,36 @@ char *strdup(const char *s) { return dup; } #endif + +#ifndef HAVE_ASPRINTF +int asprintf(char **strp, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + int ret = vasprintf(strp, fmt, va); + va_end(va); + return ret; +} +#endif + +#ifndef HAVE_VASPRINTF +int vasprintf(char **strp, const char *fmt, va_list ap) { + va_list va; + va_copy(va, ap); + int len = vsnprintf(NULL, 0, fmt, va); + va_end(va); + + char *str = malloc(len + 1); + if (!str) { + return -1; + } + + va_copy(va, ap); + int len2 = vsnprintf(str, len + 1, fmt, va); + (void) len2; + assert(len == len2); + va_end(va); + + *strp = str; + return len; +} +#endif diff --git a/app/src/compat.h b/app/src/compat.h index c06c23fa..3ab56049 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -58,4 +58,12 @@ char *strdup(const char *s); #endif +#ifndef HAVE_ASPRINTF +int asprintf(char **strp, const char *fmt, ...); +#endif + +#ifndef HAVE_VASPRINTF +int vasprintf(char **strp, const char *fmt, va_list ap); +#endif + #endif From 5434ea543cf94fcec389483b1fc021c2f13a34b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:01:25 +0100 Subject: [PATCH 0165/1133] Remove local "serial" variable In execute_server(), the serial is used only once. Moreover, it can be retrieved from the `params` argument directly. --- app/src/server.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 546216e9..f70c0640 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -139,8 +139,6 @@ log_level_to_server_string(enum sc_log_level level) { static sc_pid execute_server(struct sc_server *server, const struct sc_server_params *params) { - const char *serial = server->params.serial; - char max_size_string[6]; char bit_rate_string[11]; char max_fps_string[6]; @@ -199,7 +197,7 @@ execute_server(struct sc_server *server, // Port: 5005 // Then click on "Debug" #endif - return adb_execute(serial, cmd, ARRAY_LEN(cmd)); + return adb_execute(params->serial, cmd, ARRAY_LEN(cmd)); } static bool From 2c3099e2de13913332f3e0085faf646e19bb976b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:06:02 +0100 Subject: [PATCH 0166/1133] Parse codec options early For consistency with other options, parse the codec options on the server before storing them in the Options instance. --- server/src/main/java/com/genymobile/scrcpy/Options.java | 8 +++++--- server/src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 74467c7c..20a579ed 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -2,6 +2,8 @@ package com.genymobile.scrcpy; import android.graphics.Rect; +import java.util.List; + public class Options { private Ln.Level logLevel; private int maxSize; @@ -15,7 +17,7 @@ public class Options { private int displayId; private boolean showTouches; private boolean stayAwake; - private String codecOptions; + private List codecOptions; private String encoderName; private boolean powerOffScreenOnClose; private boolean clipboardAutosync; @@ -116,11 +118,11 @@ public class Options { this.stayAwake = stayAwake; } - public String getCodecOptions() { + public List getCodecOptions() { return codecOptions; } - public void setCodecOptions(String codecOptions) { + public void setCodecOptions(List codecOptions) { this.codecOptions = codecOptions; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 188974ff..bcdaf6bb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -61,7 +61,7 @@ public final class Server { private static void scrcpy(Options options) throws IOException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); - List codecOptions = CodecOption.parse(options.getCodecOptions()); + List codecOptions = options.getCodecOptions(); Thread initThread = startInitThread(options); @@ -204,7 +204,7 @@ public final class Server { boolean stayAwake = Boolean.parseBoolean(args[12]); options.setStayAwake(stayAwake); - String codecOptions = args[13]; + List codecOptions = CodecOption.parse(args[13]); options.setCodecOptions(codecOptions); String encoderName = "-".equals(args[14]) ? null : args[14]; From 2eb881c5f13ae2c3a8c9ca68188c1abce77b48bd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:10:18 +0100 Subject: [PATCH 0167/1133] Allocate and format server command args This simplifies formatting. --- app/src/server.c | 90 ++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index f70c0640..2ea37af6 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -139,23 +139,17 @@ log_level_to_server_string(enum sc_log_level level) { static sc_pid execute_server(struct sc_server *server, const struct sc_server_params *params) { - char max_size_string[6]; - char bit_rate_string[11]; - char max_fps_string[6]; - char lock_video_orientation_string[5]; - char display_id_string[11]; - sprintf(max_size_string, "%"PRIu16, params->max_size); - sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); - sprintf(max_fps_string, "%"PRIu16, params->max_fps); - sprintf(lock_video_orientation_string, "%"PRIi8, - params->lock_video_orientation); - sprintf(display_id_string, "%"PRIu32, params->display_id); - const char *const cmd[] = { - "shell", - "CLASSPATH=" SC_DEVICE_SERVER_PATH, - "app_process", + sc_pid pid = SC_PROCESS_NONE; + + const char *cmd[128]; + unsigned count = 0; + cmd[count++] = "shell"; + cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; + cmd[count++] = "app_process"; + #ifdef SERVER_DEBUGGER # define SERVER_DEBUGGER_PORT "5005" + cmd[count++] = # ifdef SERVER_DEBUGGER_METHOD_NEW /* Android 9 and above */ "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y," @@ -164,28 +158,43 @@ execute_server(struct sc_server *server, /* Android 8 and below */ "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=" # endif - SERVER_DEBUGGER_PORT, + SERVER_DEBUGGER_PORT; #endif - "/", // unused - "com.genymobile.scrcpy.Server", - SCRCPY_VERSION, - log_level_to_server_string(params->log_level), - max_size_string, - bit_rate_string, - max_fps_string, - lock_video_orientation_string, - server->tunnel.forward ? "true" : "false", - params->crop ? params->crop : "-", - "true", // always send frame meta (packet boundaries + timestamp) - params->control ? "true" : "false", - display_id_string, - params->show_touches ? "true" : "false", - params->stay_awake ? "true" : "false", - params->codec_options ? params->codec_options : "-", - params->encoder_name ? params->encoder_name : "-", - params->power_off_on_close ? "true" : "false", - params->clipboard_autosync ? "true" : "false", - }; + cmd[count++] = "/"; // unused + cmd[count++] = "com.genymobile.scrcpy.Server"; + cmd[count++] = SCRCPY_VERSION; + cmd[count++] = log_level_to_server_string(params->log_level); + + unsigned dyn_idx = count; // from there, the strings are allocated +#define ADD_PARAM(fmt, ...) { \ + char *p = (char *) &cmd[count]; \ + if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \ + goto end; \ + } \ + cmd[count++] = p; \ + } +#define STRBOOL(v) (v ? "true" : "false") + + ADD_PARAM("%" PRIu16, params->max_size); + ADD_PARAM("%" PRIu32, params->bit_rate); + ADD_PARAM("%" PRIu16, params->max_fps); + ADD_PARAM("%" PRIi8, params->lock_video_orientation); + ADD_PARAM("%s", STRBOOL(server->tunnel.forward)); + ADD_PARAM("%s", params->crop ? params->crop : "-"); + // always send frame meta (packet boundaries + timestamp) + ADD_PARAM("true"); + ADD_PARAM("%s", STRBOOL(params->control)); + ADD_PARAM("%" PRIu32, params->display_id); + ADD_PARAM("%s", STRBOOL(params->show_touches)); + ADD_PARAM("%s", STRBOOL(params->stay_awake)); + ADD_PARAM("%s", params->codec_options ? params->codec_options : "-"); + ADD_PARAM("%s", params->encoder_name ? params->encoder_name : "-"); + ADD_PARAM("%s", STRBOOL(params->power_off_on_close)); + ADD_PARAM("%s", STRBOOL(params->clipboard_autosync)); + +#undef ADD_PARAM +#undef STRBOOL + #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "..."); @@ -197,7 +206,14 @@ execute_server(struct sc_server *server, // Port: 5005 // Then click on "Debug" #endif - return adb_execute(params->serial, cmd, ARRAY_LEN(cmd)); + pid = adb_execute(params->serial, cmd, count); + +end: + for (unsigned i = dyn_idx; i < count; ++i) { + free((char *) cmd[i]); + } + + return pid; } static bool From 04e5537f8ce21dbae365dcd980efda911188c945 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:23:20 +0100 Subject: [PATCH 0168/1133] Pass server parameters as key=value pairs The options values to configure the server were identified by their command-line argument index. Now that there are a lot of arguments, many of them being booleans, it became unreadable and error-prone. Identify the arguments by a key string instead, and make them optional. This will also simplify running the server manually for debugging. --- app/src/server.c | 34 ++--- .../com/genymobile/scrcpy/CodecOption.java | 2 +- .../java/com/genymobile/scrcpy/Options.java | 12 +- .../java/com/genymobile/scrcpy/Server.java | 134 +++++++++++------- 4 files changed, 105 insertions(+), 77 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 2ea37af6..98f0a188 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -163,7 +163,6 @@ execute_server(struct sc_server *server, cmd[count++] = "/"; // unused cmd[count++] = "com.genymobile.scrcpy.Server"; cmd[count++] = SCRCPY_VERSION; - cmd[count++] = log_level_to_server_string(params->log_level); unsigned dyn_idx = count; // from there, the strings are allocated #define ADD_PARAM(fmt, ...) { \ @@ -175,22 +174,25 @@ execute_server(struct sc_server *server, } #define STRBOOL(v) (v ? "true" : "false") - ADD_PARAM("%" PRIu16, params->max_size); - ADD_PARAM("%" PRIu32, params->bit_rate); - ADD_PARAM("%" PRIu16, params->max_fps); - ADD_PARAM("%" PRIi8, params->lock_video_orientation); - ADD_PARAM("%s", STRBOOL(server->tunnel.forward)); - ADD_PARAM("%s", params->crop ? params->crop : "-"); + ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); + ADD_PARAM("max_size=%" PRIu16, params->max_size); + ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + ADD_PARAM("max_fps=%" PRIu16, params->max_fps); + ADD_PARAM("lock_video_orientation=%" PRIi8, params->lock_video_orientation); + ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward)); + ADD_PARAM("crop=%s", params->crop ? params->crop : ""); // always send frame meta (packet boundaries + timestamp) - ADD_PARAM("true"); - ADD_PARAM("%s", STRBOOL(params->control)); - ADD_PARAM("%" PRIu32, params->display_id); - ADD_PARAM("%s", STRBOOL(params->show_touches)); - ADD_PARAM("%s", STRBOOL(params->stay_awake)); - ADD_PARAM("%s", params->codec_options ? params->codec_options : "-"); - ADD_PARAM("%s", params->encoder_name ? params->encoder_name : "-"); - ADD_PARAM("%s", STRBOOL(params->power_off_on_close)); - ADD_PARAM("%s", STRBOOL(params->clipboard_autosync)); + ADD_PARAM("send_frame_meta=true"); + ADD_PARAM("control=%s", STRBOOL(params->control)); + ADD_PARAM("display_id=%" PRIu32, params->display_id); + ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches)); + ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake)); + ADD_PARAM("codec_options=%s", + params->codec_options ? params->codec_options : ""); + ADD_PARAM("encoder_name=%s", + params->encoder_name ? params->encoder_name : ""); + ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); + ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); #undef ADD_PARAM #undef STRBOOL diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java index 1897bda3..12f2a889 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java @@ -21,7 +21,7 @@ public class CodecOption { } public static List parse(String codecOptions) { - if ("-".equals(codecOptions)) { + if (codecOptions.isEmpty()) { return null; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 20a579ed..f649d776 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -5,22 +5,22 @@ import android.graphics.Rect; import java.util.List; public class Options { - private Ln.Level logLevel; + private Ln.Level logLevel = Ln.Level.DEBUG; private int maxSize; - private int bitRate; + private int bitRate = 8000000; private int maxFps; - private int lockedVideoOrientation; + private int lockedVideoOrientation = -1; private boolean tunnelForward; private Rect crop; - private boolean sendFrameMeta; // send PTS so that the client may record properly - private boolean control; + private boolean sendFrameMeta = true; // send PTS so that the client may record properly + private boolean control = true; private int displayId; private boolean showTouches; private boolean stayAwake; private List codecOptions; private String encoderName; private boolean powerOffScreenOnClose; - private boolean clipboardAutosync; + private boolean clipboardAutosync = true; public Ln.Level getLogLevel() { return logLevel; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index bcdaf6bb..71920627 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -160,67 +160,93 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 17; - if (args.length != expectedParameters) { - throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); - } - Options options = new Options(); - Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH)); - options.setLogLevel(level); - - int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8 - options.setMaxSize(maxSize); - - int bitRate = Integer.parseInt(args[3]); - options.setBitRate(bitRate); - - int maxFps = Integer.parseInt(args[4]); - options.setMaxFps(maxFps); - - int lockedVideoOrientation = Integer.parseInt(args[5]); - options.setLockedVideoOrientation(lockedVideoOrientation); - - // use "adb forward" instead of "adb tunnel"? (so the server must listen) - boolean tunnelForward = Boolean.parseBoolean(args[6]); - options.setTunnelForward(tunnelForward); - - Rect crop = parseCrop(args[7]); - options.setCrop(crop); - - boolean sendFrameMeta = Boolean.parseBoolean(args[8]); - options.setSendFrameMeta(sendFrameMeta); - - boolean control = Boolean.parseBoolean(args[9]); - options.setControl(control); - - int displayId = Integer.parseInt(args[10]); - options.setDisplayId(displayId); - - boolean showTouches = Boolean.parseBoolean(args[11]); - options.setShowTouches(showTouches); - - boolean stayAwake = Boolean.parseBoolean(args[12]); - options.setStayAwake(stayAwake); - - List codecOptions = CodecOption.parse(args[13]); - options.setCodecOptions(codecOptions); - - String encoderName = "-".equals(args[14]) ? null : args[14]; - options.setEncoderName(encoderName); - - boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]); - options.setPowerOffScreenOnClose(powerOffScreenOnClose); - - boolean clipboardAutosync = Boolean.parseBoolean(args[16]); - options.setClipboardAutosync(clipboardAutosync); + for (int i = 1; i < args.length; ++i) { + String arg = args[i]; + int equalIndex = arg.indexOf('='); + if (equalIndex == -1) { + throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\""); + } + String key = arg.substring(0, equalIndex); + String value = arg.substring(equalIndex + 1); + switch (key) { + case "log_level": + Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); + options.setLogLevel(level); + break; + case "max_size": + int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 + options.setMaxSize(maxSize); + break; + case "bit_rate": + int bitRate = Integer.parseInt(value); + options.setBitRate(bitRate); + break; + case "max_fps": + int maxFps = Integer.parseInt(value); + options.setMaxFps(maxFps); + break; + case "lock_video_orientation": + int lockedVideoOrientation = Integer.parseInt(value); + options.setLockedVideoOrientation(lockedVideoOrientation); + break; + case "tunnel_forward": + boolean tunnelForward = Boolean.parseBoolean(value); + options.setTunnelForward(tunnelForward); + break; + case "crop": + Rect crop = parseCrop(value); + options.setCrop(crop); + break; + case "send_frame_meta": + boolean sendFrameMeta = Boolean.parseBoolean(value); + options.setSendFrameMeta(sendFrameMeta); + break; + case "control": + boolean control = Boolean.parseBoolean(value); + options.setControl(control); + break; + case "display_id": + int displayId = Integer.parseInt(value); + options.setDisplayId(displayId); + break; + case "show_touches": + boolean showTouches = Boolean.parseBoolean(value); + options.setShowTouches(showTouches); + break; + case "stay_awake": + boolean stayAwake = Boolean.parseBoolean(value); + options.setStayAwake(stayAwake); + break; + case "codec_options": + List codecOptions = CodecOption.parse(value); + options.setCodecOptions(codecOptions); + break; + case "encoder_name": + if (!value.isEmpty()) { + options.setEncoderName(value); + } + break; + case "power_off_on_close": + boolean powerOffScreenOnClose = Boolean.parseBoolean(value); + options.setPowerOffScreenOnClose(powerOffScreenOnClose); + break; + case "clipboard_autosync": + boolean clipboardAutosync = Boolean.parseBoolean(value); + options.setClipboardAutosync(clipboardAutosync); + break; + default: + Ln.w("Unknown server option: " + key); + break; + } + } return options; } private static Rect parseCrop(String crop) { - if ("-".equals(crop)) { + if (crop.isEmpty()) { return null; } // input format: "width:height:x:y" From 7b29f5fd2de8f302e03365013093f5665efd3cfd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:31:12 +0100 Subject: [PATCH 0169/1133] Do not pass default values to the server By default, only pass the version and the bit rate (the default bitrate is a client compilation option). --- app/src/server.c | 60 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 98f0a188..2bb7d717 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -175,24 +175,50 @@ execute_server(struct sc_server *server, #define STRBOOL(v) (v ? "true" : "false") ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); - ADD_PARAM("max_size=%" PRIu16, params->max_size); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); - ADD_PARAM("max_fps=%" PRIu16, params->max_fps); - ADD_PARAM("lock_video_orientation=%" PRIi8, params->lock_video_orientation); - ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward)); - ADD_PARAM("crop=%s", params->crop ? params->crop : ""); - // always send frame meta (packet boundaries + timestamp) - ADD_PARAM("send_frame_meta=true"); - ADD_PARAM("control=%s", STRBOOL(params->control)); - ADD_PARAM("display_id=%" PRIu32, params->display_id); - ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches)); - ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake)); - ADD_PARAM("codec_options=%s", - params->codec_options ? params->codec_options : ""); - ADD_PARAM("encoder_name=%s", - params->encoder_name ? params->encoder_name : ""); - ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); - ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); + + if (params->max_size) { + ADD_PARAM("max_size=%" PRIu16, params->max_size); + } + if (params->max_fps) { + ADD_PARAM("max_fps=%" PRIu16, params->max_fps); + } + if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { + ADD_PARAM("lock_video_orientation=%" PRIi8, + params->lock_video_orientation); + } + if (server->tunnel.forward) { + ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward)); + } + if (params->crop) { + ADD_PARAM("crop=%s", params->crop); + } + if (!params->control) { + // By default, control is true + ADD_PARAM("control=%s", STRBOOL(params->control)); + } + if (params->display_id) { + ADD_PARAM("display_id=%" PRIu32, params->display_id); + } + if (params->show_touches) { + ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches)); + } + if (params->stay_awake) { + ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake)); + } + if (params->codec_options) { + ADD_PARAM("codec_options=%s", params->codec_options); + } + if (params->encoder_name) { + ADD_PARAM("encoder_name=%s", params->encoder_name); + } + if (params->power_off_on_close) { + ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); + } + if (!params->clipboard_autosync) { + // By defaut, clipboard_autosync is true + ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); + } #undef ADD_PARAM #undef STRBOOL From 4c4759886518f8c6fed10eefe31043d871b293af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:33:58 +0100 Subject: [PATCH 0170/1133] Make lockVideoOrientation option name uniform On the server, the option was named lockedVideoOrientation. --- server/src/main/java/com/genymobile/scrcpy/Device.java | 2 +- .../src/main/java/com/genymobile/scrcpy/Options.java | 10 +++++----- server/src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 03ae9b22..35f4efd4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -65,7 +65,7 @@ public final class Device { int displayInfoFlags = displayInfo.getFlags(); - screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation()); layerStack = displayInfo.getLayerStack(); SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index f649d776..1ac17176 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -9,7 +9,7 @@ public class Options { private int maxSize; private int bitRate = 8000000; private int maxFps; - private int lockedVideoOrientation = -1; + private int lockVideoOrientation = -1; private boolean tunnelForward; private Rect crop; private boolean sendFrameMeta = true; // send PTS so that the client may record properly @@ -54,12 +54,12 @@ public class Options { this.maxFps = maxFps; } - public int getLockedVideoOrientation() { - return lockedVideoOrientation; + public int getLockVideoOrientation() { + return lockVideoOrientation; } - public void setLockedVideoOrientation(int lockedVideoOrientation) { - this.lockedVideoOrientation = lockedVideoOrientation; + public void setLockVideoOrientation(int lockVideoOrientation) { + this.lockVideoOrientation = lockVideoOrientation; } public boolean isTunnelForward() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 71920627..0f0abab0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -188,8 +188,8 @@ public final class Server { options.setMaxFps(maxFps); break; case "lock_video_orientation": - int lockedVideoOrientation = Integer.parseInt(value); - options.setLockedVideoOrientation(lockedVideoOrientation); + int lockVideoOrientation = Integer.parseInt(value); + options.setLockVideoOrientation(lockVideoOrientation); break; case "tunnel_forward": boolean tunnelForward = Boolean.parseBoolean(value); From 007f616302d08993835588e98f2836e0bf6d732d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:44:29 +0100 Subject: [PATCH 0171/1133] Add missing includes Include these headers explicitly instead of relying on transitivity. --- app/src/sys/unix/file.c | 1 + app/src/v4l2_sink.c | 2 ++ app/tests/test_str.c | 1 + app/tests/test_strbuf.c | 1 + 4 files changed, 5 insertions(+) diff --git a/app/src/sys/unix/file.c b/app/src/sys/unix/file.c index 4e9e45b3..47b5d8f9 100644 --- a/app/src/sys/unix/file.c +++ b/app/src/sys/unix/file.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 753d5b6a..429d3f1a 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -1,5 +1,7 @@ #include "v4l2_sink.h" +#include + #include "util/log.h" #include "util/str.h" diff --git a/app/tests/test_str.c b/app/tests/test_str.c index 770ddd95..c66bb2f4 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "util/str.h" diff --git a/app/tests/test_strbuf.c b/app/tests/test_strbuf.c index 97417677..58562522 100644 --- a/app/tests/test_strbuf.c +++ b/app/tests/test_strbuf.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "util/strbuf.h" From 73e0311d149e8fc64b6ca6d6ac79e5dee219c18c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:46:02 +0100 Subject: [PATCH 0172/1133] Add missing return on file_handler failure Mistake introduced by 84334cf7db68732eb92caf6cf9682c7ef2c426f6. --- app/src/file_handler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/file_handler.c b/app/src/file_handler.c index eead2117..a758f575 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -37,6 +37,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial, LOGE("Could not create intr"); sc_cond_destroy(&file_handler->event_cond); sc_mutex_destroy(&file_handler->mutex); + return false; } file_handler->serial = strdup(serial); From 92a458e846410ff7990229be86a5931046229c80 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 21:48:57 +0100 Subject: [PATCH 0173/1133] Remove unreachable return statements --- app/src/v4l2_sink.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 429d3f1a..439d111a 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -217,7 +217,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { if (!vs->format_ctx->url) { LOGE("Could not strdup v4l2 device name"); goto error_avformat_free_context; - return false; } #else strncpy(vs->format_ctx->filename, vs->device_name, @@ -228,7 +227,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { if (!ostream) { LOGE("Could not allocate new v4l2 stream"); goto error_avformat_free_context; - return false; } ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; From 3653fb6b152af502885734e6d6cadbdda3421773 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 22:06:11 +0100 Subject: [PATCH 0174/1133] Add OutOfMemory log helper Add a special LOG_OOM() function to log all OutOfMemory errors (i.e. allocations returning NULL). --- app/src/adb.c | 4 +++- app/src/aoa_hid.c | 2 ++ app/src/cli.c | 1 + app/src/decoder.c | 4 ++-- app/src/device_msg.c | 2 +- app/src/file_handler.c | 3 +-- app/src/frame_buffer.c | 2 ++ app/src/hid_keyboard.c | 1 + app/src/icon.c | 12 ++++++------ app/src/recorder.c | 12 ++++++------ app/src/scrcpy.c | 2 +- app/src/screen.c | 5 +---- app/src/server.c | 9 +++------ app/src/stream.c | 12 ++++++------ app/src/sys/unix/file.c | 5 +++++ app/src/sys/win/file.c | 2 +- app/src/sys/win/process.c | 4 +++- app/src/util/file.c | 2 +- app/src/util/intr.c | 2 +- app/src/util/log.h | 6 ++++++ app/src/util/net.c | 1 + app/src/util/str.c | 7 ++++++- app/src/util/strbuf.c | 5 ++++- app/src/util/thread.c | 3 +++ app/src/v4l2_sink.c | 18 +++++++----------- app/src/video_buffer.c | 7 +++---- 26 files changed, 77 insertions(+), 56 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 630a1952..176f50b2 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -86,7 +86,8 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { #define MAX_COMMAND_STRING_LEN 1024 char *buf = malloc(MAX_COMMAND_STRING_LEN); if (!buf) { - LOGE("Failed to execute (could not allocate error message)"); + LOG_OOM(); + LOGE("Failed to execute"); return; } @@ -118,6 +119,7 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], const char **argv = malloc((len + 4) * sizeof(*argv)); if (!argv) { + LOG_OOM(); return SC_PROCESS_NONE; } diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index 6fd32610..a3fc3bd4 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -4,6 +4,7 @@ #include #include "aoa_hid.h" +#include "util/log.h" // See . #define ACCESSORY_REGISTER_HID 54 @@ -20,6 +21,7 @@ sc_hid_event_log(const struct sc_hid_event *event) { unsigned buffer_size = event->size * 3 + 1; char *buffer = malloc(buffer_size); if (!buffer) { + LOG_OOM(); return; } for (unsigned i = 0; i < event->size; ++i) { diff --git a/app/src/cli.c b/app/src/cli.c index 29cb8932..7da3b904 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -595,6 +595,7 @@ sc_getopt_adapter_create_longopts(void) { struct option *longopts = malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts)); if (!longopts) { + LOG_OOM(); return NULL; } diff --git a/app/src/decoder.c b/app/src/decoder.c index 7c67e836..7107e01d 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -42,7 +42,7 @@ static bool decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); if (!decoder->codec_ctx) { - LOGC("Could not allocate decoder context"); + LOG_OOM(); return false; } @@ -54,7 +54,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder->frame = av_frame_alloc(); if (!decoder->frame) { - LOGE("Could not create decoder frame"); + LOG_OOM(); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); return false; diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 4163b9fc..9a93036b 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -24,7 +24,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len, } char *text = malloc(clipboard_len + 1); if (!text) { - LOGW("Could not allocate text for clipboard"); + LOG_OOM(); return -1; } if (clipboard_len) { diff --git a/app/src/file_handler.c b/app/src/file_handler.c index a758f575..12498ccf 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -34,7 +34,6 @@ file_handler_init(struct file_handler *file_handler, const char *serial, ok = sc_intr_init(&file_handler->intr); if (!ok) { - LOGE("Could not create intr"); sc_cond_destroy(&file_handler->event_cond); sc_mutex_destroy(&file_handler->mutex); return false; @@ -42,7 +41,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial, file_handler->serial = strdup(serial); if (!file_handler->serial) { - LOGE("Could not strdup serial"); + LOG_OOM(); sc_intr_destroy(&file_handler->intr); sc_cond_destroy(&file_handler->event_cond); sc_mutex_destroy(&file_handler->mutex); diff --git a/app/src/frame_buffer.c b/app/src/frame_buffer.c index 33ca6227..d177d4fa 100644 --- a/app/src/frame_buffer.c +++ b/app/src/frame_buffer.c @@ -10,11 +10,13 @@ bool sc_frame_buffer_init(struct sc_frame_buffer *fb) { fb->pending_frame = av_frame_alloc(); if (!fb->pending_frame) { + LOG_OOM(); return false; } fb->tmp_frame = av_frame_alloc(); if (!fb->tmp_frame) { + LOG_OOM(); av_frame_free(&fb->pending_frame); return false; } diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 2afc09b2..20fe8d51 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -160,6 +160,7 @@ static bool sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE); if (!buffer) { + LOG_OOM(); return false; } diff --git a/app/src/icon.c b/app/src/icon.c index 2616007e..1d670242 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -31,7 +31,7 @@ get_icon_path(void) { char *icon_path = strdup(icon_path_env); #endif if (!icon_path) { - LOGE("Could not allocate memory"); + LOG_OOM(); return NULL; } LOGD("Using SCRCPY_ICON_PATH: %s", icon_path); @@ -42,7 +42,7 @@ get_icon_path(void) { LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH); char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH); if (!icon_path) { - LOGE("Could not allocate memory"); + LOG_OOM(); return NULL; } #else @@ -63,7 +63,7 @@ decode_image(const char *path) { AVFormatContext *ctx = avformat_alloc_context(); if (!ctx) { - LOGE("Could not allocate image decoder context"); + LOG_OOM(); return NULL; } @@ -93,7 +93,7 @@ decode_image(const char *path) { AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { - LOGE("Could not allocate codec context"); + LOG_OOM(); goto close_input; } @@ -109,13 +109,13 @@ decode_image(const char *path) { AVFrame *frame = av_frame_alloc(); if (!frame) { - LOGE("Could not allocate frame"); + LOG_OOM(); goto close_codec; } AVPacket *packet = av_packet_alloc(); if (!packet) { - LOGE("Could not allocate packet"); + LOG_OOM(); av_frame_free(&frame); goto close_codec; } diff --git a/app/src/recorder.c b/app/src/recorder.c index b9c585f4..4364b9a5 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -34,11 +34,13 @@ static struct record_packet * record_packet_new(const AVPacket *packet) { struct record_packet *rec = malloc(sizeof(*rec)); if (!rec) { + LOG_OOM(); return NULL; } rec->packet = av_packet_alloc(); if (!rec->packet) { + LOG_OOM(); free(rec); return NULL; } @@ -81,7 +83,7 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) { uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); if (!extradata) { - LOGC("Could not allocate extradata"); + LOG_OOM(); return false; } @@ -228,13 +230,11 @@ static bool recorder_open(struct recorder *recorder, const AVCodec *input_codec) { bool ok = sc_mutex_init(&recorder->mutex); if (!ok) { - LOGC("Could not create mutex"); return false; } ok = sc_cond_init(&recorder->queue_cond); if (!ok) { - LOGC("Could not create cond"); goto error_mutex_destroy; } @@ -254,7 +254,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { recorder->ctx = avformat_alloc_context(); if (!recorder->ctx) { - LOGE("Could not allocate output context"); + LOG_OOM(); goto error_cond_destroy; } @@ -338,7 +338,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { struct record_packet *rec = record_packet_new(packet); if (!rec) { - LOGC("Could not allocate record packet"); + LOG_OOM(); sc_mutex_unlock(&recorder->mutex); return false; } @@ -375,7 +375,7 @@ recorder_init(struct recorder *recorder, struct sc_size declared_frame_size) { recorder->filename = strdup(filename); if (!recorder->filename) { - LOGE("Could not strdup filename"); + LOG_OOM(); return false; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 061a4e76..f0142b46 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -271,7 +271,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { size_t fmt_len = strlen(fmt); char *local_fmt = malloc(fmt_len + 10); if (!local_fmt) { - LOGC("Could not allocate string"); + LOG_OOM(); return; } memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' diff --git a/app/src/screen.c b/app/src/screen.c index d402b402..34a2d5d9 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -366,18 +366,15 @@ screen_init(struct screen *screen, const struct screen_params *params) { bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs, screen); if (!ok) { - LOGE("Could not initialize video buffer"); return false; } ok = sc_video_buffer_start(&screen->vb); if (!ok) { - LOGE("Could not start video_buffer"); goto error_destroy_video_buffer; } if (!fps_counter_init(&screen->fps_counter)) { - LOGE("Could not initialize FPS counter"); goto error_stop_and_join_video_buffer; } @@ -478,7 +475,7 @@ screen_init(struct screen *screen, const struct screen_params *params) { screen->frame = av_frame_alloc(); if (!screen->frame) { - LOGC("Could not create screen frame"); + LOG_OOM(); goto error_destroy_texture; } diff --git a/app/src/server.c b/app/src/server.c index 2bb7d717..bce6c45b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -34,7 +34,7 @@ get_server_path(void) { char *server_path = strdup(server_path_env); #endif if (!server_path) { - LOGE("Could not allocate memory"); + LOG_OOM(); return NULL; } LOGD("Using SCRCPY_SERVER_PATH: %s", server_path); @@ -45,7 +45,7 @@ get_server_path(void) { LOGD("Using server: " SC_SERVER_PATH_DEFAULT); char *server_path = strdup(SC_SERVER_PATH_DEFAULT); if (!server_path) { - LOGE("Could not allocate memory"); + LOG_OOM(); return NULL; } #else @@ -309,20 +309,18 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, const struct sc_server_callbacks *cbs, void *cbs_userdata) { bool ok = sc_server_params_copy(&server->params, params); if (!ok) { - LOGE("Could not copy server params"); + LOG_OOM(); return false; } ok = sc_mutex_init(&server->mutex); if (!ok) { - LOGE("Could not create server mutex"); sc_server_params_destroy(&server->params); return false; } ok = sc_cond_init(&server->cond_stopped); if (!ok) { - LOGE("Could not create server cond_stopped"); sc_mutex_destroy(&server->mutex); sc_server_params_destroy(&server->params); return false; @@ -330,7 +328,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, ok = sc_intr_init(&server->intr); if (!ok) { - LOGE("Could not create intr"); sc_cond_destroy(&server->cond_stopped); sc_mutex_destroy(&server->mutex); sc_server_params_destroy(&server->params); diff --git a/app/src/stream.c b/app/src/stream.c index 4c770250..3ac0f5e1 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -42,7 +42,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) { assert(len); if (av_new_packet(packet, len)) { - LOGE("Could not allocate packet"); + LOG_OOM(); return false; } @@ -111,18 +111,18 @@ stream_push_packet(struct stream *stream, AVPacket *packet) { if (stream->pending) { offset = stream->pending->size; if (av_grow_packet(stream->pending, packet->size)) { - LOGE("Could not grow packet"); + LOG_OOM(); return false; } } else { offset = 0; stream->pending = av_packet_alloc(); if (!stream->pending) { - LOGE("Could not allocate packet"); + LOG_OOM(); return false; } if (av_new_packet(stream->pending, packet->size)) { - LOGE("Could not create packet"); + LOG_OOM(); av_packet_free(&stream->pending); return false; } @@ -200,7 +200,7 @@ run_stream(void *data) { stream->codec_ctx = avcodec_alloc_context3(codec); if (!stream->codec_ctx) { - LOGC("Could not allocate codec context"); + LOG_OOM(); goto end; } @@ -221,7 +221,7 @@ run_stream(void *data) { AVPacket *packet = av_packet_alloc(); if (!packet) { - LOGE("Could not allocate packet"); + LOG_OOM(); goto finally_close_parser; } diff --git a/app/src/sys/unix/file.c b/app/src/sys/unix/file.c index 47b5d8f9..9c3f7333 100644 --- a/app/src/sys/unix/file.c +++ b/app/src/sys/unix/file.c @@ -7,6 +7,8 @@ #include #include +#include "util/log.h" + bool sc_file_executable_exists(const char *file) { char *path = getenv("PATH"); @@ -24,7 +26,10 @@ sc_file_executable_exists(const char *file) { size_t dir_len = strlen(dir); char *fullpath = malloc(dir_len + file_len + 2); if (!fullpath) + { + LOG_OOM(); continue; + } memcpy(fullpath, dir, dir_len); fullpath[dir_len] = '/'; memcpy(fullpath + dir_len + 1, file, file_len + 1); diff --git a/app/src/sys/win/file.c b/app/src/sys/win/file.c index 5233b177..d3cf1760 100644 --- a/app/src/sys/win/file.c +++ b/app/src/sys/win/file.c @@ -26,7 +26,7 @@ bool sc_file_is_regular(const char *path) { wchar_t *wide_path = sc_str_to_wchars(path); if (!wide_path) { - LOGC("Could not allocate wide char string"); + LOG_OOM(); return false; } diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 326a3d99..cf82f014 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -100,6 +100,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, lpAttributeList = malloc(size); if (!lpAttributeList) { + LOG_OOM(); goto error_close_stderr; } @@ -133,13 +134,14 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, char *cmd = malloc(CMD_MAX_LEN); if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { + LOG_OOM(); goto error_free_attribute_list; } wchar_t *wide = sc_str_to_wchars(cmd); free(cmd); if (!wide) { - LOGC("Could not allocate wide char string"); + LOG_OOM(); goto error_free_attribute_list; } diff --git a/app/src/util/file.c b/app/src/util/file.c index 59be2d91..174e5efd 100644 --- a/app/src/util/file.c +++ b/app/src/util/file.c @@ -31,7 +31,7 @@ sc_file_get_local_path(const char *name) { size_t len = dirlen + namelen + 2; // +2: '/' and '\0' char *file_path = malloc(len); if (!file_path) { - LOGE("Could not alloc path"); + LOG_OOM(); free(executable_path); return NULL; } diff --git a/app/src/util/intr.c b/app/src/util/intr.c index 50d9abbe..22bd121a 100644 --- a/app/src/util/intr.c +++ b/app/src/util/intr.c @@ -8,7 +8,7 @@ bool sc_intr_init(struct sc_intr *intr) { bool ok = sc_mutex_init(&intr->mutex); if (!ok) { - LOGE("Could not init intr mutex"); + LOG_OOM(); return false; } diff --git a/app/src/util/log.h b/app/src/util/log.h index 4157d6e5..231e0846 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -7,6 +7,9 @@ #include "options.h" +#define LOG_STR_IMPL_(x) # x +#define LOG_STR(x) LOG_STR_IMPL_(x) + #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) @@ -14,6 +17,9 @@ #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) +#define LOG_OOM() \ + LOGC("OOM: %s:%d %s()", __FILE__, __LINE__, __func__) + void sc_set_log_level(enum sc_log_level level); diff --git a/app/src/util/net.c b/app/src/util/net.c index 824d4886..8c51d12b 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -52,6 +52,7 @@ wrap(sc_raw_socket sock) { struct sc_socket_windows *socket = malloc(sizeof(*socket)); if (!socket) { + LOG_OOM(); closesocket(sock); return SC_SOCKET_NONE; } diff --git a/app/src/util/str.c b/app/src/util/str.c index 3bd2752f..ab1c8783 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -5,13 +5,15 @@ #include #include #include -#include "util/strbuf.h" #ifdef _WIN32 # include # include #endif +#include "log.h" +#include "strbuf.h" + size_t sc_strncpy(char *dest, const char *src, size_t n) { size_t i; @@ -51,6 +53,7 @@ sc_str_quote(const char *src) { size_t len = strlen(src); char *quoted = malloc(len + 3); if (!quoted) { + LOG_OOM(); return NULL; } memcpy("ed[1], src, len); @@ -188,6 +191,7 @@ sc_str_to_wchars(const char *utf8) { wchar_t *wide = malloc(len * sizeof(wchar_t)); if (!wide) { + LOG_OOM(); return NULL; } @@ -204,6 +208,7 @@ sc_str_from_wchars(const wchar_t *ws) { char *utf8 = malloc(len); if (!utf8) { + LOG_OOM(); return NULL; } diff --git a/app/src/util/strbuf.c b/app/src/util/strbuf.c index b2b6f494..1892b46b 100644 --- a/app/src/util/strbuf.c +++ b/app/src/util/strbuf.c @@ -1,15 +1,17 @@ #include "strbuf.h" #include +#include #include #include -#include +#include "log.h" bool sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) { buf->s = malloc(init_cap + 1); // +1 for '\0' if (!buf->s) { + LOG_OOM(); return false; } @@ -25,6 +27,7 @@ sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) { char *s = realloc(buf->s, new_cap + 1); // +1 for '\0' if (!s) { // Leave the old buf->s + LOG_OOM(); return false; } buf->s = s; diff --git a/app/src/util/thread.c b/app/src/util/thread.c index 2c376e97..23eddf1d 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -10,6 +10,7 @@ sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, void *userdata) { SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata); if (!sdl_thread) { + LOG_OOM(); return false; } @@ -26,6 +27,7 @@ bool sc_mutex_init(sc_mutex *mutex) { SDL_mutex *sdl_mutex = SDL_CreateMutex(); if (!sdl_mutex) { + LOG_OOM(); return false; } @@ -94,6 +96,7 @@ bool sc_cond_init(sc_cond *cond) { SDL_cond *sdl_cond = SDL_CreateCond(); if (!sdl_cond) { + LOG_OOM(); return false; } diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 439d111a..dce11ce1 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -33,7 +33,7 @@ write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) { uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); if (!extradata) { - LOGC("Could not allocate extradata"); + LOG_OOM(); return false; } @@ -163,25 +163,21 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs); if (!ok) { - LOGE("Could not initialize video buffer"); return false; } ok = sc_video_buffer_start(&vs->vb); if (!ok) { - LOGE("Could not start video buffer"); goto error_video_buffer_destroy; } ok = sc_mutex_init(&vs->mutex); if (!ok) { - LOGC("Could not create mutex"); goto error_video_buffer_stop_and_join; } ok = sc_cond_init(&vs->cond); if (!ok) { - LOGC("Could not create cond"); goto error_mutex_destroy; } @@ -203,7 +199,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { vs->format_ctx = avformat_alloc_context(); if (!vs->format_ctx) { - LOGE("Could not allocate v4l2 output context"); + LOG_OOM(); return false; } @@ -215,7 +211,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { #ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL vs->format_ctx->url = strdup(vs->device_name); if (!vs->format_ctx->url) { - LOGE("Could not strdup v4l2 device name"); + LOG_OOM(); goto error_avformat_free_context; } #else @@ -225,7 +221,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder); if (!ostream) { - LOGE("Could not allocate new v4l2 stream"); + LOG_OOM(); goto error_avformat_free_context; } @@ -244,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { vs->encoder_ctx = avcodec_alloc_context3(encoder); if (!vs->encoder_ctx) { - LOGC("Could not allocate codec context for v4l2"); + LOG_OOM(); goto error_avio_close; } @@ -261,13 +257,13 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { vs->frame = av_frame_alloc(); if (!vs->frame) { - LOGE("Could not create v4l2 frame"); + LOG_OOM(); goto error_avcodec_close; } vs->packet = av_packet_alloc(); if (!vs->packet) { - LOGE("Could not allocate packet"); + LOG_OOM(); goto error_av_frame_free; } diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index f71a4e78..12e66cf1 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -14,11 +14,13 @@ static struct sc_video_buffer_frame * sc_video_buffer_frame_new(const AVFrame *frame) { struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame)); if (!vb_frame) { + LOG_OOM(); return NULL; } vb_frame->frame = av_frame_alloc(); if (!vb_frame->frame) { + LOG_OOM(); free(vb_frame); return NULL; } @@ -132,14 +134,12 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, if (buffering_time) { ok = sc_mutex_init(&vb->b.mutex); if (!ok) { - LOGC("Could not create mutex"); sc_frame_buffer_destroy(&vb->fb); return false; } ok = sc_cond_init(&vb->b.queue_cond); if (!ok) { - LOGC("Could not create cond"); sc_mutex_destroy(&vb->b.mutex); sc_frame_buffer_destroy(&vb->fb); return false; @@ -147,7 +147,6 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, ok = sc_cond_init(&vb->b.wait_cond); if (!ok) { - LOGC("Could not create wait cond"); sc_cond_destroy(&vb->b.queue_cond); sc_mutex_destroy(&vb->b.mutex); sc_frame_buffer_destroy(&vb->fb); @@ -234,7 +233,7 @@ sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame); if (!vb_frame) { sc_mutex_unlock(&vb->b.mutex); - LOGE("Could not allocate frame"); + LOG_OOM(); return false; } From b0eb1a55d6f1180df2d19fda91d08290dff4f687 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 21:35:07 +0100 Subject: [PATCH 0175/1133] Fix adb get-serialno error handling If pipe read fails, return. --- app/src/adb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 176f50b2..05516280 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -304,6 +304,10 @@ adb_get_serialno(struct sc_intr *intr) { return NULL; } + if (r == -1) { + return false; + } + sc_str_truncate(buf, r, " \r\n"); return strdup(buf); From d31725f07708908ced7a71ea53a4d8a3e6bbb4b6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 21:48:05 +0100 Subject: [PATCH 0176/1133] Reorder cli sanity checks Check unexpected additional arguments before other sanity checks. --- app/src/cli.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 7da3b904..791d3c43 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1394,6 +1394,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + int index = optind; + if (index < argc) { + LOGE("Unexpected additional argument: %s", argv[index]); + return false; + } + #ifdef HAVE_V4L2 if (!opts->display && !opts->record_filename && !opts->v4l2_device) { LOGE("-N/--no-display requires either screen recording (-r/--record)" @@ -1425,12 +1431,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->force_adb_forward = true; } - int index = optind; - if (index < argc) { - LOGE("Unexpected additional argument: %s", argv[index]); - return false; - } - if (opts->record_format && !opts->record_filename) { LOGE("Record format specified without recording"); return false; From 8ed3328055c52fd415954626c4a1f763b24a1927 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 21:54:08 +0100 Subject: [PATCH 0177/1133] Use unsigned for connection attempts count There is no reason to use an explicit uint32_t. --- app/src/server.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index bce6c45b..f0624a13 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -264,10 +264,10 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, } static sc_socket -connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay, +connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay, uint32_t host, uint16_t port) { do { - LOGD("Remaining connection attempts: %d", (int) attempts); + LOGD("Remaining connection attempts: %u", attempts); sc_socket socket = net_socket(); if (socket != SC_SOCKET_NONE) { bool ok = connect_and_read_byte(&server->intr, socket, host, port); @@ -300,7 +300,7 @@ connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay, break; } } - } while (--attempts > 0); + } while (--attempts); return SC_SOCKET_NONE; } @@ -403,7 +403,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { tunnel_port = tunnel->local_port; } - uint32_t attempts = 100; + unsigned attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); video_socket = connect_to_server(server, attempts, delay, tunnel_host, tunnel_port); From 680d2cc9400d371a8c26402aa49ab4cf6a3d6b7e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 08:57:31 +0100 Subject: [PATCH 0178/1133] Extract command argv building This simplifies adb_execute_p(). --- app/src/adb.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 05516280..aa0cef78 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -111,19 +111,16 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { free(buf); } -static sc_pid -adb_execute_p(const char *serial, const char *const adb_cmd[], - size_t len, sc_pipe *pout) { - int i; - sc_pid pid; - +static const char ** +adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) { const char **argv = malloc((len + 4) * sizeof(*argv)); if (!argv) { LOG_OOM(); - return SC_PROCESS_NONE; + return NULL; } argv[0] = get_adb_command(); + int i; if (serial) { argv[1] = "-s"; argv[2] = serial; @@ -134,6 +131,18 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); argv[len + i] = NULL; + return argv; +} + +static sc_pid +adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, + sc_pipe *pout) { + const char **argv = adb_create_argv(serial, adb_cmd, len); + if (!argv) { + return SC_PROCESS_NONE; + } + + sc_pid pid; enum sc_process_result r = sc_process_execute_p(argv, &pid, NULL, pout, NULL); if (r != SC_PROCESS_SUCCESS) { From 904f0ae61e7902973e862a6151ef972877fc3380 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 09:37:53 +0100 Subject: [PATCH 0179/1133] Check process success locally for adb commands Remove sc_process_check_success() from the process API, it is too specific. --- app/src/adb.c | 54 ++++++++++++++++++++++++++++++------- app/src/util/process.c | 19 ------------- app/src/util/process.h | 8 ------ app/src/util/process_intr.c | 21 --------------- app/src/util/process_intr.h | 4 --- 5 files changed, 44 insertions(+), 62 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index aa0cef78..7383d347 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -111,6 +111,43 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { free(buf); } +static bool +process_check_success_internal(sc_pid pid, const char *name, bool close) { + if (pid == SC_PROCESS_NONE) { + LOGE("Could not execute \"%s\"", name); + return false; + } + sc_exit_code exit_code = sc_process_wait(pid, close); + if (exit_code) { + if (exit_code != SC_EXIT_CODE_NONE) { + LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, + exit_code); + } else { + LOGE("\"%s\" exited unexpectedly", name); + } + return false; + } + return true; +} + +static bool +process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name) { + if (!sc_intr_set_process(intr, pid)) { + // Already interrupted + return false; + } + + // Always pass close=false, interrupting would be racy otherwise + bool ret = process_check_success_internal(pid, name, false); + + sc_intr_set_process(intr, SC_PROCESS_NONE); + + // Close separately + sc_process_close(pid); + + return ret; +} + static const char ** adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) { const char **argv = malloc((len + 4) * sizeof(*argv)); @@ -255,43 +292,41 @@ bool adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name) { sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name); - return sc_process_check_success_intr(intr, pid, "adb forward", true); + return process_check_success_intr(intr, pid, "adb forward"); } bool adb_forward_remove(struct sc_intr *intr, const char *serial, uint16_t local_port) { sc_pid pid = adb_exec_forward_remove(serial, local_port); - return sc_process_check_success_intr(intr, pid, "adb forward --remove", - true); + return process_check_success_intr(intr, pid, "adb forward --remove"); } bool adb_reverse(struct sc_intr *intr, const char *serial, const char *device_socket_name, uint16_t local_port) { sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port); - return sc_process_check_success_intr(intr, pid, "adb reverse", true); + return process_check_success_intr(intr, pid, "adb reverse"); } bool adb_reverse_remove(struct sc_intr *intr, const char *serial, const char *device_socket_name) { sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name); - return sc_process_check_success_intr(intr, pid, "adb reverse --remove", - true); + return process_check_success_intr(intr, pid, "adb reverse --remove"); } bool adb_push(struct sc_intr *intr, const char *serial, const char *local, const char *remote) { sc_pid pid = adb_exec_push(serial, local, remote); - return sc_process_check_success_intr(intr, pid, "adb push", true); + return process_check_success_intr(intr, pid, "adb push"); } bool adb_install(struct sc_intr *intr, const char *serial, const char *local) { sc_pid pid = adb_exec_install(serial, local); - return sc_process_check_success_intr(intr, pid, "adb install", true); + return process_check_success_intr(intr, pid, "adb install"); } char * @@ -307,8 +342,7 @@ adb_get_serialno(struct sc_intr *intr) { ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); sc_pipe_close(pout); - bool ok = - sc_process_check_success_intr(intr, pid, "adb get-serialno", true); + bool ok = process_check_success_intr(intr, pid, "adb get-serialno"); if (!ok) { return NULL; } diff --git a/app/src/util/process.c b/app/src/util/process.c index 28f51edd..38931d9c 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -9,25 +9,6 @@ sc_process_execute(const char *const argv[], sc_pid *pid) { return sc_process_execute_p(argv, pid, NULL, NULL, NULL); } -bool -sc_process_check_success(sc_pid pid, const char *name, bool close) { - if (pid == SC_PROCESS_NONE) { - LOGE("Could not execute \"%s\"", name); - return false; - } - sc_exit_code exit_code = sc_process_wait(pid, close); - if (exit_code) { - if (exit_code != SC_EXIT_CODE_NONE) { - LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, - exit_code); - } else { - LOGE("\"%s\" exited unexpectedly", name); - } - return false; - } - return true; -} - ssize_t sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { size_t copied = 0; diff --git a/app/src/util/process.h b/app/src/util/process.h index 7964be5c..14bc060e 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -107,14 +107,6 @@ sc_process_wait(sc_pid pid, bool close); void sc_process_close(sc_pid pid); -/** - * Convenience function to wait for a successful process execution - * - * Automatically log process errors with the provided process name. - */ -bool -sc_process_check_success(sc_pid pid, const char *name, bool close); - /** * Read from the pipe * diff --git a/app/src/util/process_intr.c b/app/src/util/process_intr.c index dcb81100..940fe89f 100644 --- a/app/src/util/process_intr.c +++ b/app/src/util/process_intr.c @@ -1,26 +1,5 @@ #include "process_intr.h" -bool -sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, - const char *name, bool close) { - if (!sc_intr_set_process(intr, pid)) { - // Already interrupted - return false; - } - - // Always pass close=false, interrupting would be racy otherwise - bool ret = sc_process_check_success(pid, name, false); - - sc_intr_set_process(intr, SC_PROCESS_NONE); - - if (close) { - // Close separately - sc_process_close(pid); - } - - return ret; -} - ssize_t sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, size_t len) { diff --git a/app/src/util/process_intr.h b/app/src/util/process_intr.h index 9406d889..530a9046 100644 --- a/app/src/util/process_intr.h +++ b/app/src/util/process_intr.h @@ -6,10 +6,6 @@ #include "intr.h" #include "process.h" -bool -sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid, - const char *name, bool close); - ssize_t sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, size_t len); From b90c89766b1e13afc82c2aba2ebd5d54c83c067f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 08:10:37 +0100 Subject: [PATCH 0180/1133] Set CLOEXEC flag on sockets If SOCK_CLOEXEC exists, then set the flag on socket creation. Otherwise, use fcntl() (or SetHandleInformation() on Windows) to set the flag afterwards. This avoids the sockets to be inherited in child processes. Refs #2783 --- app/meson.build | 3 +++ app/src/util/net.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/meson.build b/app/meson.build index 1561fdcd..1baf34fc 100644 --- a/app/meson.build +++ b/app/meson.build @@ -149,6 +149,9 @@ foreach f : check_functions endif endforeach +conf.set('HAVE_SOCK_CLOEXEC', host_machine.system() != 'windows' and + cc.has_header_symbol('sys/socket.h', 'SOCK_CLOEXEC')) + # the version, updated on release conf.set_quoted('SCRCPY_VERSION', meson.project_version()) diff --git a/app/src/util/net.c b/app/src/util/net.c index 8c51d12b..ec678d2e 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -1,6 +1,7 @@ #include "net.h" #include +#include #include #include @@ -10,17 +11,20 @@ # include typedef int socklen_t; typedef SOCKET sc_raw_socket; +# define SC_RAW_SOCKET_NONE INVALID_SOCKET #else # include # include # include # include # include +# include # define SOCKET_ERROR -1 typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr SOCKADDR; typedef struct in_addr IN_ADDR; typedef int sc_raw_socket; +# define SC_RAW_SOCKET_NONE -1 #endif bool @@ -79,6 +83,35 @@ unwrap(sc_socket socket) { #endif } +static inline bool +sc_raw_socket_close(sc_raw_socket raw_sock) { +#ifndef _WIN32 + return !close(raw_sock); +#else + return !closesocket(raw_sock); +#endif +} + +#ifndef HAVE_SOCK_CLOEXEC +// If SOCK_CLOEXEC does not exist, the flag must be set manually once the +// socket is created +static bool +set_cloexec_flag(sc_raw_socket raw_sock) { +#ifndef _WIN32 + if (fcntl(raw_sock, F_SETFD, FD_CLOEXEC) == -1) { + perror("fcntl F_SETFD"); + return false; + } +#else + if (!SetHandleInformation((HANDLE) raw_sock, HANDLE_FLAG_INHERIT, 0)) { + LOGE("SetHandleInformation socket failed"); + return false; + } +#endif + return true; +} +#endif + static void net_perror(const char *s) { #ifdef _WIN32 @@ -97,7 +130,16 @@ net_perror(const char *s) { sc_socket net_socket(void) { +#ifdef HAVE_SOCK_CLOEXEC + sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); +#else sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0); + if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) { + sc_raw_socket_close(raw_sock); + return SC_SOCKET_NONE; + } +#endif + sc_socket sock = wrap(raw_sock); if (sock == SC_SOCKET_NONE) { net_perror("socket"); @@ -156,8 +198,18 @@ net_accept(sc_socket server_socket) { SOCKADDR_IN csin; socklen_t sinsize = sizeof(csin); + +#ifdef HAVE_SOCK_CLOEXEC + sc_raw_socket raw_sock = + accept4(raw_server_socket, (SOCKADDR *) &csin, &sinsize, SOCK_CLOEXEC); +#else sc_raw_socket raw_sock = accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize); + if (raw_sock != SC_RAW_SOCKET_NONE && !set_cloexec_flag(raw_sock)) { + sc_raw_socket_close(raw_sock); + return SC_SOCKET_NONE; + } +#endif return wrap(raw_sock); } @@ -211,7 +263,6 @@ net_interrupt(sc_socket socket) { #endif } -#include bool net_close(sc_socket socket) { sc_raw_socket raw_sock = unwrap(socket); From 3e54773c4843b94f16df04af2c0edafef060f82e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 21:58:51 +0100 Subject: [PATCH 0181/1133] Remove intermediate static functions from adb.c They can easily be inlined into the public functions. --- app/src/adb.c | 100 +++++++++++++++++--------------------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 7383d347..92744164 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -196,46 +196,57 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { return adb_execute_p(serial, adb_cmd, len, NULL); } -static sc_pid -adb_exec_forward(const char *serial, uint16_t local_port, - const char *device_socket_name) { +bool +adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, + const char *device_socket_name) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"forward", local, remote}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + return process_check_success_intr(intr, pid, "adb forward"); } -static sc_pid -adb_exec_forward_remove(const char *serial, uint16_t local_port) { +bool +adb_forward_remove(struct sc_intr *intr, const char *serial, + uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); const char *const adb_cmd[] = {"forward", "--remove", local}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + return process_check_success_intr(intr, pid, "adb forward --remove"); } -static sc_pid -adb_exec_reverse(const char *serial, const char *device_socket_name, - uint16_t local_port) { +bool +adb_reverse(struct sc_intr *intr, const char *serial, + const char *device_socket_name, uint16_t local_port) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", remote, local}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + return process_check_success_intr(intr, pid, "adb reverse"); } -static sc_pid -adb_exec_reverse_remove(const char *serial, const char *device_socket_name) { +bool +adb_reverse_remove(struct sc_intr *intr, const char *serial, + const char *device_socket_name) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", "--remove", remote}; - return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + return process_check_success_intr(intr, pid, "adb reverse --remove"); } -static sc_pid -adb_exec_push(const char *serial, const char *local, const char *remote) { +bool +adb_push(struct sc_intr *intr, const char *serial, const char *local, + const char *remote) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) @@ -258,11 +269,11 @@ adb_exec_push(const char *serial, const char *local, const char *remote) { free((void *) local); #endif - return pid; + return process_check_success_intr(intr, pid, "adb push"); } -static sc_pid -adb_exec_install(const char *serial, const char *local) { +bool +adb_install(struct sc_intr *intr, const char *serial, const char *local) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) @@ -279,60 +290,15 @@ adb_exec_install(const char *serial, const char *local) { free((void *) local); #endif - return pid; -} - -static sc_pid -adb_exec_get_serialno(sc_pipe *pout) { - const char *const adb_cmd[] = {"get-serialno"}; - return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout); -} - -bool -adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, - const char *device_socket_name) { - sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name); - return process_check_success_intr(intr, pid, "adb forward"); -} - -bool -adb_forward_remove(struct sc_intr *intr, const char *serial, - uint16_t local_port) { - sc_pid pid = adb_exec_forward_remove(serial, local_port); - return process_check_success_intr(intr, pid, "adb forward --remove"); -} - -bool -adb_reverse(struct sc_intr *intr, const char *serial, - const char *device_socket_name, uint16_t local_port) { - sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port); - return process_check_success_intr(intr, pid, "adb reverse"); -} - -bool -adb_reverse_remove(struct sc_intr *intr, const char *serial, - const char *device_socket_name) { - sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name); - return process_check_success_intr(intr, pid, "adb reverse --remove"); -} - -bool -adb_push(struct sc_intr *intr, const char *serial, const char *local, - const char *remote) { - sc_pid pid = adb_exec_push(serial, local, remote); - return process_check_success_intr(intr, pid, "adb push"); -} - -bool -adb_install(struct sc_intr *intr, const char *serial, const char *local) { - sc_pid pid = adb_exec_install(serial, local); return process_check_success_intr(intr, pid, "adb install"); } char * adb_get_serialno(struct sc_intr *intr) { + const char *const adb_cmd[] = {"get-serialno"}; + sc_pipe pout; - sc_pid pid = adb_exec_get_serialno(&pout); + sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb get-serialno\""); return NULL; From b9b8b6aab828e0f1ca899171ecf5c7bfcaf9df46 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 21:37:40 +0100 Subject: [PATCH 0182/1133] Simplify Windows process inheritance configuration Merge if-blocks together. --- app/src/sys/win/process.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index cf82f014..1d356293 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -79,14 +79,19 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; if (handle_count) { si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + + unsigned i = 0; if (pin) { si.StartupInfo.hStdInput = stdin_read_handle; + handles[i++] = si.StartupInfo.hStdInput; } if (pout) { si.StartupInfo.hStdOutput = stdout_write_handle; + handles[i++] = si.StartupInfo.hStdOutput; } if (perr) { si.StartupInfo.hStdError = stderr_write_handle; + handles[i++] = si.StartupInfo.hStdError; } SIZE_T size; @@ -110,17 +115,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, goto error_close_stderr; } - // Explicitly pass the HANDLEs that must be inherited - unsigned i = 0; - if (pin) { - handles[i++] = stdin_read_handle; - } - if (pout) { - handles[i++] = stdout_write_handle; - } - if (perr) { - handles[i++] = stderr_write_handle; - } ok = UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handles, handle_count * sizeof(HANDLE), From 01ab503c229514196ae633966277b76639e438b2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 00:35:40 +0100 Subject: [PATCH 0183/1133] Remove obsolete precision in README Scrcpy is available in Debian stable packages, and several Ubuntu versions. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d80cb1f..f80f6261 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple]) ### Linux -On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): +On Debian and Ubuntu: ``` apt install scrcpy From f801d8b3128cf5aae3725c981f32abfd4b6c307e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Nov 2021 22:42:49 +0100 Subject: [PATCH 0184/1133] Expose flags for process execution Let the caller decide if stdout and stderr must be inherited on process creation, i.e. if stdout and stderr of the child process should be printed in the scrcpy console. This allows to get output and errors for specific adb commands depending on the context. PR #2827 --- app/src/adb.c | 2 +- app/src/sys/unix/process.c | 15 ++++++++++++++- app/src/sys/win/process.c | 25 +++++++++++++++++-------- app/src/util/process.c | 4 ++-- app/src/util/process.h | 16 ++++++++++++++-- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 92744164..729b5724 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -181,7 +181,7 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, sc_pid pid; enum sc_process_result r = - sc_process_execute_p(argv, &pid, NULL, pout, NULL); + sc_process_execute_p(argv, &pid, 0, NULL, pout, NULL); if (r != SC_PROCESS_SUCCESS) { show_adb_err_msg(r, argv); pid = SC_PROCESS_NONE; diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 5f4a9890..54a1bb80 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -11,8 +11,11 @@ #include "util/log.h" enum sc_process_result -sc_process_execute_p(const char *const argv[], sc_pid *pid, +sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, int *pin, int *pout, int *perr) { + bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT); + bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); + int in[2]; int out[2]; int err[2]; @@ -90,20 +93,30 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, } close(in[1]); } + // Do not close stdin in the child process, this makes adb fail on Linux + if (pout) { if (out[1] != STDOUT_FILENO) { dup2(out[1], STDOUT_FILENO); close(out[1]); } close(out[0]); + } else if (!inherit_stdout) { + // Close stdout in the child process + close(STDOUT_FILENO); } + if (perr) { if (err[1] != STDERR_FILENO) { dup2(err[1], STDERR_FILENO); close(err[1]); } close(err[0]); + } else if (!inherit_stderr) { + // Close stderr in the child process + close(STDERR_FILENO); } + close(internal[0]); enum sc_process_result err; if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 1d356293..bed98479 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -24,12 +24,17 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) { } enum sc_process_result -sc_process_execute_p(const char *const argv[], HANDLE *handle, +sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, HANDLE *pin, HANDLE *pout, HANDLE *perr) { - enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; + bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT); + bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); // Add 1 per non-NULL pointer - unsigned handle_count = !!pin + !!pout + !!perr; + unsigned handle_count = !!pin + + (pout || inherit_stdout) + + (perr || inherit_stderr); + + enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); @@ -85,12 +90,14 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, si.StartupInfo.hStdInput = stdin_read_handle; handles[i++] = si.StartupInfo.hStdInput; } - if (pout) { - si.StartupInfo.hStdOutput = stdout_write_handle; + if (pout || inherit_stdout) { + si.StartupInfo.hStdOutput = pout ? stdout_write_handle + : GetStdHandle(STD_OUTPUT_HANDLE); handles[i++] = si.StartupInfo.hStdOutput; } - if (perr) { - si.StartupInfo.hStdError = stderr_write_handle; + if (perr || inherit_stderr) { + si.StartupInfo.hStdError = perr ? stderr_write_handle + : GetStdHandle(STD_ERROR_HANDLE); handles[i++] = si.StartupInfo.hStdError; } @@ -140,7 +147,9 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, } BOOL bInheritHandles = handle_count > 0; - DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 0; + // DETACHED_PROCESS to disable stdin, stdout and stderr + DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT + : DETACHED_PROCESS; BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles, dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); free(wide); diff --git a/app/src/util/process.c b/app/src/util/process.c index 38931d9c..ad1af0a9 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -5,8 +5,8 @@ #include "log.h" enum sc_process_result -sc_process_execute(const char *const argv[], sc_pid *pid) { - return sc_process_execute_p(argv, pid, NULL, NULL, NULL); +sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags) { + return sc_process_execute_p(argv, pid, flags, NULL, NULL, NULL); } ssize_t diff --git a/app/src/util/process.h b/app/src/util/process.h index 14bc060e..17c09bc5 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -67,20 +67,32 @@ enum sc_process_result { SC_PROCESS_ERROR_MISSING_BINARY, }; +#define SC_PROCESS_NO_STDOUT (1 << 0) +#define SC_PROCESS_NO_STDERR (1 << 1) + /** * Execute the command and write the process id to `pid` + * + * The `flags` argument is a bitwise OR of the following values: + * - SC_PROCESS_NO_STDOUT + * - SC_PROCESS_NO_STDERR + * + * It indicates if stdout and stderr must be inherited from the scrcpy process + * (i.e. if the process must output to the scrcpy console). */ enum sc_process_result -sc_process_execute(const char *const argv[], sc_pid *pid); +sc_process_execute(const char *const argv[], sc_pid *pid, unsigned flags); /** * Execute the command and write the process id to `pid` * * If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr * (`perr`). + * + * The `flags` argument has the same semantics as in `sc_process_execute()`. */ enum sc_process_result -sc_process_execute_p(const char *const argv[], sc_pid *pid, +sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr); /** From e3d4aa8c5d42279e0095404d76f2b5bc2b7c52d7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 22:27:28 +0100 Subject: [PATCH 0185/1133] Use flags for adb commands Explicitly indicate, for each adb call, if stdout and stderr must be inherited. PR #2827 --- app/src/adb.c | 49 ++++++++++++++++++++++++++---------------- app/src/adb.h | 22 ++++++++++++------- app/src/adb_tunnel.c | 14 +++++++----- app/src/file_handler.c | 4 ++-- app/src/server.c | 7 +++--- 5 files changed, 60 insertions(+), 36 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 729b5724..13018f4d 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -173,16 +173,26 @@ adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) { static sc_pid adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, - sc_pipe *pout) { + unsigned flags, sc_pipe *pout) { const char **argv = adb_create_argv(serial, adb_cmd, len); if (!argv) { return SC_PROCESS_NONE; } + unsigned process_flags = 0; + if (flags & SC_ADB_NO_STDOUT) { + process_flags |= SC_PROCESS_NO_STDOUT; + } + if (flags & SC_ADB_NO_STDERR) { + process_flags |= SC_PROCESS_NO_STDERR; + } + sc_pid pid; enum sc_process_result r = - sc_process_execute_p(argv, &pid, 0, NULL, pout, NULL); + sc_process_execute_p(argv, &pid, process_flags, NULL, pout, NULL); if (r != SC_PROCESS_SUCCESS) { + // If the execution itself failed (not the command exit code), log the + // error in all cases show_adb_err_msg(r, argv); pid = SC_PROCESS_NONE; } @@ -192,61 +202,63 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, } sc_pid -adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { - return adb_execute_p(serial, adb_cmd, len, NULL); +adb_execute(const char *serial, const char *const adb_cmd[], size_t len, + unsigned flags) { + return adb_execute_p(serial, adb_cmd, len, flags, NULL); } bool adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, - const char *device_socket_name) { + const char *device_socket_name, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"forward", local, remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb forward"); } bool adb_forward_remove(struct sc_intr *intr, const char *serial, - uint16_t local_port) { + uint16_t local_port, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); const char *const adb_cmd[] = {"forward", "--remove", local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb forward --remove"); } bool adb_reverse(struct sc_intr *intr, const char *serial, - const char *device_socket_name, uint16_t local_port) { + const char *device_socket_name, uint16_t local_port, + unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", remote, local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb reverse"); } bool adb_reverse_remove(struct sc_intr *intr, const char *serial, - const char *device_socket_name) { + const char *device_socket_name, unsigned flags) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", "--remove", remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb reverse --remove"); } bool adb_push(struct sc_intr *intr, const char *serial, const char *local, - const char *remote) { + const char *remote, unsigned flags) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) @@ -262,7 +274,7 @@ adb_push(struct sc_intr *intr, const char *serial, const char *local, #endif const char *const adb_cmd[] = {"push", local, remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ free((void *) remote); @@ -273,7 +285,8 @@ adb_push(struct sc_intr *intr, const char *serial, const char *local, } bool -adb_install(struct sc_intr *intr, const char *serial, const char *local) { +adb_install(struct sc_intr *intr, const char *serial, const char *local, + unsigned flags) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) @@ -284,7 +297,7 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local) { #endif const char *const adb_cmd[] = {"install", "-r", local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ free((void *) local); @@ -294,11 +307,11 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local) { } char * -adb_get_serialno(struct sc_intr *intr) { +adb_get_serialno(struct sc_intr *intr, unsigned flags) { const char *const adb_cmd[] = {"get-serialno"}; sc_pipe pout; - sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), &pout); + sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb get-serialno\""); return NULL; diff --git a/app/src/adb.h b/app/src/adb.h index f58bc165..5033965b 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -8,31 +8,37 @@ #include "util/intr.h" +#define SC_ADB_NO_STDOUT (1 << 0) +#define SC_ADB_NO_STDERR (1 << 1) + sc_pid -adb_execute(const char *serial, const char *const adb_cmd[], size_t len); +adb_execute(const char *serial, const char *const adb_cmd[], size_t len, + unsigned flags); bool adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, - const char *device_socket_name); + const char *device_socket_name, unsigned flags); bool adb_forward_remove(struct sc_intr *intr, const char *serial, - uint16_t local_port); + uint16_t local_port, unsigned flags); bool adb_reverse(struct sc_intr *intr, const char *serial, - const char *device_socket_name, uint16_t local_port); + const char *device_socket_name, uint16_t local_port, + unsigned flags); bool adb_reverse_remove(struct sc_intr *intr, const char *serial, - const char *device_socket_name); + const char *device_socket_name, unsigned flags); bool adb_push(struct sc_intr *intr, const char *serial, const char *local, - const char *remote); + const char *remote, unsigned flags); bool -adb_install(struct sc_intr *intr, const char *serial, const char *local); +adb_install(struct sc_intr *intr, const char *serial, const char *local, + unsigned flags); /** * Execute `adb get-serialno` @@ -40,6 +46,6 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local); * Return the result, to be freed by the caller, or NULL on error. */ char * -adb_get_serialno(struct sc_intr *intr); +adb_get_serialno(struct sc_intr *intr, unsigned flags); #endif diff --git a/app/src/adb_tunnel.c b/app/src/adb_tunnel.c index fa86a8a5..00552597 100644 --- a/app/src/adb_tunnel.c +++ b/app/src/adb_tunnel.c @@ -20,7 +20,8 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, struct sc_port_range port_range) { uint16_t port = port_range.first; for (;;) { - if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port)) { + if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port, + SC_ADB_NO_STDOUT)) { // the command itself failed, it will fail on any port return false; } @@ -51,7 +52,8 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, } // failure, disable tunnel and try another port - if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME)) { + if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + SC_ADB_NO_STDOUT)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -81,7 +83,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, uint16_t port = port_range.first; for (;;) { - if (adb_forward(intr, serial, port, SC_SOCKET_NAME)) { + if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) { // success tunnel->local_port = port; tunnel->enabled = true; @@ -146,9 +148,11 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, bool ret; if (tunnel->forward) { - ret = adb_forward_remove(intr, serial, tunnel->local_port); + ret = adb_forward_remove(intr, serial, tunnel->local_port, + SC_ADB_NO_STDOUT); } else { - ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME); + ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + SC_ADB_NO_STDOUT); assert(tunnel->server_socket != SC_SOCKET_NONE); if (!net_close(tunnel->server_socket)) { diff --git a/app/src/file_handler.c b/app/src/file_handler.c index 12498ccf..addbb9a5 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -128,7 +128,7 @@ run_file_handler(void *data) { if (req.action == ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - bool ok = adb_install(intr, serial, req.file); + bool ok = adb_install(intr, serial, req.file, 0); if (ok) { LOGI("%s successfully installed", req.file); } else { @@ -136,7 +136,7 @@ run_file_handler(void *data) { } } else { LOGI("Pushing %s...", req.file); - bool ok = adb_push(intr, serial, req.file, push_target); + bool ok = adb_push(intr, serial, req.file, push_target, 0); if (ok) { LOGI("%s successfully pushed to %s", req.file, push_target); } else { diff --git a/app/src/server.c b/app/src/server.c index f0624a13..a62a093a 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -112,7 +112,7 @@ push_server(struct sc_intr *intr, const char *serial) { free(server_path); return false; } - bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH); + bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0); free(server_path); return ok; } @@ -234,7 +234,8 @@ execute_server(struct sc_server *server, // Port: 5005 // Then click on "Debug" #endif - pid = adb_execute(params->serial, cmd, count); + // Inherit both stdout and stderr (all server logs are printed to stdout) + pid = adb_execute(params->serial, cmd, count, 0); end: for (unsigned i = dyn_idx; i < count; ++i) { @@ -482,7 +483,7 @@ sc_server_fill_serial(struct sc_server *server) { // device/emulator" error) if (!server->params.serial) { // The serial is owned by sc_server_params, and will be freed on destroy - server->params.serial = adb_get_serialno(&server->intr); + server->params.serial = adb_get_serialno(&server->intr, 0); if (!server->params.serial) { LOGE("Could not get device serial"); return false; From e6e6f865a01879365f284fd3c8bfe7758ebf1bb5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:05:38 +0100 Subject: [PATCH 0186/1133] Add adb flag to disable execution error logs In addition to disable stdout and stderr of the child process, add a flag to disable the error log printed by scrcpy if the command failed. This will we useful for commands which are expected to fail in some cases (like "adb disconnect" if the device is not connected). PR #2827 --- app/src/adb.c | 40 ++++++++++++++++++++++++---------------- app/src/adb.h | 1 + 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 13018f4d..ce6aedbf 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -112,18 +112,25 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { } static bool -process_check_success_internal(sc_pid pid, const char *name, bool close) { +process_check_success_internal(sc_pid pid, const char *name, bool close, + unsigned flags) { + bool log_errors = !(flags & SC_ADB_NO_LOGERR); + if (pid == SC_PROCESS_NONE) { - LOGE("Could not execute \"%s\"", name); + if (log_errors) { + LOGE("Could not execute \"%s\"", name); + } return false; } sc_exit_code exit_code = sc_process_wait(pid, close); if (exit_code) { - if (exit_code != SC_EXIT_CODE_NONE) { - LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, - exit_code); - } else { - LOGE("\"%s\" exited unexpectedly", name); + if (log_errors) { + if (exit_code != SC_EXIT_CODE_NONE) { + LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, + exit_code); + } else { + LOGE("\"%s\" exited unexpectedly", name); + } } return false; } @@ -131,14 +138,15 @@ process_check_success_internal(sc_pid pid, const char *name, bool close) { } static bool -process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name) { +process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, + unsigned flags) { if (!sc_intr_set_process(intr, pid)) { // Already interrupted return false; } // Always pass close=false, interrupting would be racy otherwise - bool ret = process_check_success_internal(pid, name, false); + bool ret = process_check_success_internal(pid, name, false, flags); sc_intr_set_process(intr, SC_PROCESS_NONE); @@ -217,7 +225,7 @@ adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *const adb_cmd[] = {"forward", local, remote}; sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); - return process_check_success_intr(intr, pid, "adb forward"); + return process_check_success_intr(intr, pid, "adb forward", flags); } bool @@ -228,7 +236,7 @@ adb_forward_remove(struct sc_intr *intr, const char *serial, const char *const adb_cmd[] = {"forward", "--remove", local}; sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); - return process_check_success_intr(intr, pid, "adb forward --remove"); + return process_check_success_intr(intr, pid, "adb forward --remove", flags); } bool @@ -242,7 +250,7 @@ adb_reverse(struct sc_intr *intr, const char *serial, const char *const adb_cmd[] = {"reverse", remote, local}; sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); - return process_check_success_intr(intr, pid, "adb reverse"); + return process_check_success_intr(intr, pid, "adb reverse", flags); } bool @@ -253,7 +261,7 @@ adb_reverse_remove(struct sc_intr *intr, const char *serial, const char *const adb_cmd[] = {"reverse", "--remove", remote}; sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); - return process_check_success_intr(intr, pid, "adb reverse --remove"); + return process_check_success_intr(intr, pid, "adb reverse --remove", flags); } bool @@ -281,7 +289,7 @@ adb_push(struct sc_intr *intr, const char *serial, const char *local, free((void *) local); #endif - return process_check_success_intr(intr, pid, "adb push"); + return process_check_success_intr(intr, pid, "adb push", flags); } bool @@ -303,7 +311,7 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local, free((void *) local); #endif - return process_check_success_intr(intr, pid, "adb install"); + return process_check_success_intr(intr, pid, "adb install", flags); } char * @@ -321,7 +329,7 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) { ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); sc_pipe_close(pout); - bool ok = process_check_success_intr(intr, pid, "adb get-serialno"); + bool ok = process_check_success_intr(intr, pid, "adb get-serialno", flags); if (!ok) { return NULL; } diff --git a/app/src/adb.h b/app/src/adb.h index 5033965b..7391500d 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -10,6 +10,7 @@ #define SC_ADB_NO_STDOUT (1 << 0) #define SC_ADB_NO_STDERR (1 << 1) +#define SC_ADB_NO_LOGERR (1 << 2) sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len, From bfce22414fd3173b9671140d77d2e8a927d5ff94 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 22:39:46 +0100 Subject: [PATCH 0187/1133] Add adb connect and disconnect PR #2827 --- app/src/adb.c | 18 ++++++++++++++++++ app/src/adb.h | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index ce6aedbf..cb4f14da 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -314,6 +314,24 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local, return process_check_success_intr(intr, pid, "adb install", flags); } +bool +adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { + const char *const adb_cmd[] = {"connect", ip_port}; + + sc_pid pid = adb_execute(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags); + return process_check_success_intr(intr, pid, "adb connect", flags); +} + +bool +adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { + const char *const adb_cmd[] = {"disconnect", ip_port}; + size_t len = ip_port ? ARRAY_LEN(adb_cmd) + : ARRAY_LEN(adb_cmd) - 1; + + sc_pid pid = adb_execute(NULL, adb_cmd, len, flags); + return process_check_success_intr(intr, pid, "adb disconnect", flags); +} + char * adb_get_serialno(struct sc_intr *intr, unsigned flags) { const char *const adb_cmd[] = {"get-serialno"}; diff --git a/app/src/adb.h b/app/src/adb.h index 7391500d..d5d9bb37 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -41,6 +41,23 @@ bool adb_install(struct sc_intr *intr, const char *serial, const char *local, unsigned flags); +/** + * Execute `adb connect ` + * + * `ip_port` may not be NULL. + */ +bool +adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); + +/** + * Execute `adb disconnect []` + * + * If `ip_port` is NULL, execute `adb disconnect`. + * Otherwise, execute `adb disconnect `. + */ +bool +adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); + /** * Execute `adb get-serialno` * From 3bf6fd2894c283a976320b8f0862a63be0d78635 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 21:33:50 +0100 Subject: [PATCH 0188/1133] Workaround "adb connect" error detection "adb connect" always returns successfully (with exit code 0), even in case of failure. As a workaround, capture its output and check if it starts with "connected". PR #2827 --- app/src/adb.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index cb4f14da..819066c0 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -318,8 +318,37 @@ bool adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { const char *const adb_cmd[] = {"connect", ip_port}; - sc_pid pid = adb_execute(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags); - return process_check_success_intr(intr, pid, "adb connect", flags); + sc_pipe pout; + sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGE("Could not execute \"adb connect\""); + return false; + } + + // "adb connect" always returns successfully (with exit code 0), even in + // case of failure. As a workaround, check if its output starts with + // "connected". + char buf[128]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "adb connect", flags); + if (!ok) { + return false; + } + + if (r == -1) { + return false; + } + + ok = !strncmp("connected", buf, sizeof("connected") - 1); + if (!ok && !(flags & SC_ADB_NO_STDERR)) { + // "adb connect" also prints errors to stdout. Since we capture it, + // re-print the error to stderr. + sc_str_truncate(buf, r, "\r\n"); + fprintf(stderr, "%s\n", buf); + } + return ok; } bool From b52f87a892ec97beb2afb1e0a711632038ccfe4a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Nov 2021 09:32:21 +0100 Subject: [PATCH 0189/1133] Add util function to locate a column in a string This will help to parse the result of "adb shell ip route" to find the device IP address. PR #2827 --- app/src/util/str.c | 26 ++++++++++++++++++++++++++ app/src/util/str.h | 20 ++++++++++++++++++++ app/tests/test_str.c | 21 +++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/app/src/util/str.c b/app/src/util/str.c index ab1c8783..1564ffe2 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -304,3 +304,29 @@ sc_str_truncate(char *data, size_t len, const char *endchars) { data[idx] = '\0'; return idx; } + +ssize_t +sc_str_index_of_column(const char *s, unsigned col, const char *seps) { + size_t colidx = 0; + + size_t idx = 0; + while (s[idx] != '\0' && colidx != col) { + size_t r = strcspn(&s[idx], seps); + idx += r; + + if (s[idx] == '\0') { + // Not found + return -1; + } + + size_t consecutive_seps = strspn(&s[idx], seps); + assert(consecutive_seps); // At least one + idx += consecutive_seps; + + if (s[idx] != '\0') { + ++colidx; + } + } + + return col == colidx ? (ssize_t) idx : -1; +} diff --git a/app/src/util/str.h b/app/src/util/str.h index 521dfff5..2c7d7829 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -114,4 +114,24 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); size_t sc_str_truncate(char *data, size_t len, const char *endchars); +/** + * Find the start of a column in a string + * + * A string may represent several columns, separated by some "spaces" + * (separators). This function aims to find the start of the column number + * `col`. + * + * For example, to find the 4th column (column number 3): + * + * // here + * // v + * const char *s = "abc def ghi jk"; + * ssize_t index = sc_str_index_of_column(s, 3, " "); + * assert(index == 16); // points to "jk" + * + * Return -1 if no such column exists. + */ +ssize_t +sc_str_index_of_column(const char *s, unsigned col, const char *seps); + #endif diff --git a/app/tests/test_str.c b/app/tests/test_str.c index c66bb2f4..bd976b3c 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -364,6 +364,26 @@ static void test_truncate(void) { assert(!strcmp("hello", s4)); } +static void test_index_of_column(void) { + assert(sc_str_index_of_column("a bc d", 0, " ") == 0); + assert(sc_str_index_of_column("a bc d", 1, " ") == 2); + assert(sc_str_index_of_column("a bc d", 2, " ") == 6); + assert(sc_str_index_of_column("a bc d", 3, " ") == -1); + + assert(sc_str_index_of_column("a ", 0, " ") == 0); + assert(sc_str_index_of_column("a ", 1, " ") == -1); + + assert(sc_str_index_of_column("", 0, " ") == 0); + assert(sc_str_index_of_column("", 1, " ") == -1); + + assert(sc_str_index_of_column("a \t \t bc \t d\t", 0, " \t") == 0); + assert(sc_str_index_of_column("a \t \t bc \t d\t", 1, " \t") == 8); + assert(sc_str_index_of_column("a \t \t bc \t d\t", 2, " \t") == 15); + assert(sc_str_index_of_column("a \t \t bc \t d\t", 3, " \t") == -1); + + assert(sc_str_index_of_column(" a bc d", 1, " ") == 2); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -384,5 +404,6 @@ int main(int argc, char *argv[]) { test_strlist_contains(); test_wrap_lines(); test_truncate(); + test_index_of_column(); return 0; } From b7e631791cc0c9af407c9f625047754f6adf369c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 28 Nov 2021 22:02:40 +0100 Subject: [PATCH 0190/1133] Add util function to remove trailing '\r' Depending on the platform and adb versions, the lines output by adb could end with "\r\r\n". This util function helps to remove all trailing '\r'. PR #2827 --- app/src/util/str.c | 11 +++++++++++ app/src/util/str.h | 11 +++++++++++ app/tests/test_str.c | 15 +++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/app/src/util/str.c b/app/src/util/str.c index 1564ffe2..70e3f1de 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -330,3 +330,14 @@ sc_str_index_of_column(const char *s, unsigned col, const char *seps) { return col == colidx ? (ssize_t) idx : -1; } + +size_t +sc_str_remove_trailing_cr(char *s, size_t len) { + while (len) { + if (s[len - 1] != '\r') { + break; + } + s[--len] = '\0'; + } + return len; +} diff --git a/app/src/util/str.h b/app/src/util/str.h index 2c7d7829..b81764ef 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -134,4 +134,15 @@ sc_str_truncate(char *data, size_t len, const char *endchars); ssize_t sc_str_index_of_column(const char *s, unsigned col, const char *seps); +/** + * Remove all `\r` at the end of the line + * + * The line length is provided by `len` (this avoids a call to `strlen()` when + * the caller already knows the length). + * + * Return the new length. + */ +size_t +sc_str_remove_trailing_cr(char *s, size_t len); + #endif diff --git a/app/tests/test_str.c b/app/tests/test_str.c index bd976b3c..cc3039e7 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -384,6 +384,20 @@ static void test_index_of_column(void) { assert(sc_str_index_of_column(" a bc d", 1, " ") == 2); } +static void test_remove_trailing_cr() { + char s[] = "abc\r"; + sc_str_remove_trailing_cr(s, sizeof(s) - 1); + assert(!strcmp(s, "abc")); + + char s2[] = "def\r\r\r\r"; + sc_str_remove_trailing_cr(s2, sizeof(s2) - 1); + assert(!strcmp(s2, "def")); + + char s3[] = "adb\rdef\r"; + sc_str_remove_trailing_cr(s3, sizeof(s3) - 1); + assert(!strcmp(s3, "adb\rdef")); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -405,5 +419,6 @@ int main(int argc, char *argv[]) { test_wrap_lines(); test_truncate(); test_index_of_column(); + test_remove_trailing_cr(); return 0; } From f609b406c9226144d9a7fdd7cdd12919ceaa2c73 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:11:59 +0100 Subject: [PATCH 0191/1133] Add function to find the device IP address Parse the result of "adb shell ip route" to find the device IP address. PR #2827 --- app/meson.build | 9 +++- app/src/adb.c | 38 +++++++++++++++++ app/src/adb.h | 9 ++++ app/src/adb_parser.c | 65 +++++++++++++++++++++++++++++ app/src/adb_parser.h | 14 +++++++ app/tests/test_adb_parser.c | 83 +++++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 app/src/adb_parser.c create mode 100644 app/src/adb_parser.h create mode 100644 app/tests/test_adb_parser.c diff --git a/app/meson.build b/app/meson.build index 1baf34fc..720d9c8c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,7 @@ src = [ 'src/main.c', 'src/adb.c', + 'src/adb_parser.c', 'src/adb_tunnel.c', 'src/cli.c', 'src/clock.c', @@ -204,8 +205,14 @@ install_data('../data/icon.png', # do not build tests in release (assertions would not be executed at all) if get_option('buildtype') == 'debug' tests = [ + ['test_adb_parser', [ + 'tests/test_adb_parser.c', + 'src/adb_parser.c', + 'src/util/str.c', + 'src/util/strbuf.c', + ]], ['test_buffer_util', [ - 'tests/test_buffer_util.c' + 'tests/test_buffer_util.c', ]], ['test_cbuf', [ 'tests/test_cbuf.c', diff --git a/app/src/adb.c b/app/src/adb.c index 819066c0..69bf551a 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -5,6 +5,7 @@ #include #include +#include "adb_parser.h" #include "util/file.h" #include "util/log.h" #include "util/process_intr.h" @@ -389,3 +390,40 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) { return strdup(buf); } + +char * +adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { + const char *const cmd[] = {"shell", "ip", "route"}; + + sc_pipe pout; + sc_pid pid = adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGD("Could not execute \"ip route\""); + return NULL; + } + + // "adb shell ip route" output should contain only a few lines + char buf[1024]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "ip route", flags); + if (!ok) { + return NULL; + } + + if (r == -1) { + return false; + } + + assert((size_t) r <= sizeof(buf)); + if (r == sizeof(buf) && buf[sizeof(buf) - 1] != '\0') { + // The implementation assumes that the output of "ip route" fits in the + // buffer in a single pass + LOGW("Result of \"ip route\" does not fit in 1Kb. " + "Please report an issue.\n"); + return NULL; + } + + return sc_adb_parse_device_ip_from_output(buf, r); +} diff --git a/app/src/adb.h b/app/src/adb.h index d5d9bb37..ba0c2bde 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -66,4 +66,13 @@ adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); char * adb_get_serialno(struct sc_intr *intr, unsigned flags); +/** + * Attempt to retrieve the device IP + * + * Return the IP as a string of the form "xxx.xxx.xxx.xxx", to be freed by the + * caller, or NULL on error. + */ +char * +adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); + #endif diff --git a/app/src/adb_parser.c b/app/src/adb_parser.c new file mode 100644 index 00000000..5ac21ede --- /dev/null +++ b/app/src/adb_parser.c @@ -0,0 +1,65 @@ +#include "adb_parser.h" + +#include +#include + +#include "util/log.h" +#include "util/str.h" + +static char * +sc_adb_parse_device_ip_from_line(char *line, size_t len) { + // One line from "ip route" looks lile: + // "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x" + + // Get the location of the device name (index of "wlan0" in the example) + ssize_t idx_dev_name = sc_str_index_of_column(line, 2, " "); + if (idx_dev_name == -1) { + return NULL; + } + + // Get the location of the ip address (column 8, but column 6 if we start + // from column 2). Must be computed before truncating individual columns. + ssize_t idx_ip = sc_str_index_of_column(&line[idx_dev_name], 6, " "); + if (idx_ip == -1) { + return NULL; + } + // idx_ip is searched from &line[idx_dev_name] + idx_ip += idx_dev_name; + + char *dev_name = &line[idx_dev_name]; + sc_str_truncate(dev_name, len - idx_dev_name + 1, " \t"); + + char *ip = &line[idx_ip]; + sc_str_truncate(ip, len - idx_ip + 1, " \t"); + + // Only consider lines where the device name starts with "wlan" + if (strncmp(dev_name, "wlan", sizeof("wlan") - 1)) { + LOGD("Device ip lookup: ignoring %s (%s)", ip, dev_name); + return NULL; + } + + return strdup(ip); +} + +char * +sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len) { + size_t idx_line = 0; + while (idx_line < buf_len && buf[idx_line] != '\0') { + char *line = &buf[idx_line]; + size_t len = sc_str_truncate(line, buf_len - idx_line, "\n"); + + // The same, but without any trailing '\r' + size_t line_len = sc_str_remove_trailing_cr(line, len); + + char *ip = sc_adb_parse_device_ip_from_line(line, line_len); + if (ip) { + // Found + return ip; + } + + // The next line starts after the '\n' (replaced by `\0`) + idx_line += len + 1; + } + + return NULL; +} diff --git a/app/src/adb_parser.h b/app/src/adb_parser.h new file mode 100644 index 00000000..79f26631 --- /dev/null +++ b/app/src/adb_parser.h @@ -0,0 +1,14 @@ +#ifndef SC_ADB_PARSER_H +#define SC_ADB_PARSER_H + +#include "common.h" + +#include "stddef.h" + +/** + * Parse the ip from the output of `adb shell ip route` + */ +char * +sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len); + +#endif diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c new file mode 100644 index 00000000..fbc65649 --- /dev/null +++ b/app/tests/test_adb_parser.c @@ -0,0 +1,83 @@ +#include "common.h" + +#include + +#include "adb_parser.h" + +static void test_get_ip_single_line() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34\r\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_single_line_without_eol() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_single_line_with_trailing_space() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.12.34 \n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.12.34")); +} + +static void test_get_ip_multiline_first_ok() { + char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.1.2\r\n" + "10.0.0.0/24 dev rmnet proto kernel scope link src " + "10.0.0.2\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.1.2")); +} + +static void test_get_ip_multiline_second_ok() { + char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src " + "10.0.0.3\r\n" + "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.1.3\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(ip); + assert(!strcmp(ip, "192.168.1.3")); +} + +static void test_get_ip_no_wlan() { + char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " + "192.168.12.34\r\r\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(!ip); +} + +static void test_get_ip_truncated() { + char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " + "\n"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + assert(!ip); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_get_ip_single_line(); + test_get_ip_single_line_without_eol(); + test_get_ip_single_line_with_trailing_space(); + test_get_ip_multiline_first_ok(); + test_get_ip_multiline_second_ok(); + test_get_ip_no_wlan(); + test_get_ip_truncated(); +} From 8543d842ea4d73d6912c38f9702f7a18651b3d49 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:13:39 +0100 Subject: [PATCH 0192/1133] Add function to switch device to TCP/IP mode Expose a function to execute "adb tcpip ". PR #2827 --- app/src/adb.c | 11 +++++++++++ app/src/adb.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 69bf551a..747e5ac9 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -315,6 +315,17 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local, return process_check_success_intr(intr, pid, "adb install", flags); } +bool +adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, + unsigned flags) { + char port_string[5 + 1]; + sprintf(port_string, "%" PRIu16, port); + const char *const adb_cmd[] = {"tcpip", port_string}; + + sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + return process_check_success_intr(intr, pid, "adb tcpip", flags); +} + bool adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { const char *const adb_cmd[] = {"connect", ip_port}; diff --git a/app/src/adb.h b/app/src/adb.h index ba0c2bde..f56c98c4 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -41,6 +41,13 @@ bool adb_install(struct sc_intr *intr, const char *serial, const char *local, unsigned flags); +/** + * Execute `adb tcpip ` + */ +bool +adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, + unsigned flags); + /** * Execute `adb connect ` * From 800ba33ff42be775a279e02deef4b655ec3e991f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:18:36 +0100 Subject: [PATCH 0193/1133] Add function to read an adb property This will allow to read the property "service.adb.tcp.port" to know if the TCP/IP mode is enabled on the device, and which listening port is used. PR #2827 --- app/src/adb.c | 31 +++++++++++++++++++++++++++++++ app/src/adb.h | 7 +++++++ 2 files changed, 38 insertions(+) diff --git a/app/src/adb.c b/app/src/adb.c index 747e5ac9..598b331f 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -373,6 +373,37 @@ adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { return process_check_success_intr(intr, pid, "adb disconnect", flags); } +char * +adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, + unsigned flags) { + const char *const adb_cmd[] = {"shell", "getprop", prop}; + + sc_pipe pout; + sc_pid pid = + adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGE("Could not execute \"adb getprop\""); + return NULL; + } + + char buf[128]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "adb getprop", flags); + if (!ok) { + return NULL; + } + + if (r == -1) { + return NULL; + } + + sc_str_truncate(buf, r, " \r\n"); + + return strdup(buf); +} + char * adb_get_serialno(struct sc_intr *intr, unsigned flags) { const char *const adb_cmd[] = {"get-serialno"}; diff --git a/app/src/adb.h b/app/src/adb.h index f56c98c4..8d7f3ea1 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -65,6 +65,13 @@ adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); bool adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); +/** + * Execute `adb getprop ` + */ +char * +adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, + unsigned flags); + /** * Execute `adb get-serialno` * From 3b310f8317e4f62ee5aa76786aa75e3d610d34e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Nov 2021 22:57:36 +0100 Subject: [PATCH 0194/1133] Extract interruptible sleep for server This improves readability, and makes the function reusable. PR #2827 --- app/src/server.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index a62a093a..6211b08b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -136,6 +136,20 @@ log_level_to_server_string(enum sc_log_level level) { } } +static bool +sc_server_sleep(struct sc_server *server, sc_tick deadline) { + sc_mutex_lock(&server->mutex); + bool timed_out = false; + while (!server->stopped && !timed_out) { + timed_out = !sc_cond_timedwait(&server->cond_stopped, + &server->mutex, deadline); + } + bool stopped = server->stopped; + sc_mutex_unlock(&server->mutex); + + return !stopped; +} + static sc_pid execute_server(struct sc_server *server, const struct sc_server_params *params) { @@ -286,17 +300,9 @@ connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay, } if (attempts) { - sc_mutex_lock(&server->mutex); sc_tick deadline = sc_tick_now() + delay; - bool timed_out = false; - while (!server->stopped && !timed_out) { - timed_out = !sc_cond_timedwait(&server->cond_stopped, - &server->mutex, deadline); - } - bool stopped = server->stopped; - sc_mutex_unlock(&server->mutex); - - if (stopped) { + bool ok = sc_server_sleep(server, deadline); + if (!ok) { LOGI("Connection attempt stopped"); break; } From 19858e6aeb99b57de28f042053b8c3d4372e56ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:22:49 +0100 Subject: [PATCH 0195/1133] Add --tcpip feature Expose an option to automatically configure and reconnect the device over TCP/IP, to simplify wireless connection without using adb explicitly. There are two variants: - If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). - If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting. PR #2827 --- app/scrcpy.1 | 8 ++ app/src/adb.h | 2 + app/src/cli.c | 27 ++++++ app/src/options.c | 2 + app/src/options.h | 2 + app/src/scrcpy.c | 2 + app/src/server.c | 203 +++++++++++++++++++++++++++++++++++++++++++++- app/src/server.h | 2 + 8 files changed, 244 insertions(+), 4 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 122af757..715000b6 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -199,6 +199,14 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr Default is "lalt,lsuper" (left-Alt or left-Super). +.TP +.BI "\-\-tcpip[=ip[:port]] +Configure and reconnect the device over TCP/IP. + +If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). + +If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting. + .TP .B \-S, \-\-turn\-screen\-off Turn the device screen off immediately. diff --git a/app/src/adb.h b/app/src/adb.h index 8d7f3ea1..4d1278cf 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -12,6 +12,8 @@ #define SC_ADB_NO_STDERR (1 << 1) #define SC_ADB_NO_LOGERR (1 << 2) +#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR) + sc_pid adb_execute(const char *serial, const char *const adb_cmd[], size_t len, unsigned flags); diff --git a/app/src/cli.c b/app/src/cli.c index 791d3c43..8cfaf57b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -50,6 +50,7 @@ #define OPT_TUNNEL_HOST 1030 #define OPT_TUNNEL_PORT 1031 #define OPT_NO_CLIPBOARD_AUTOSYNC 1032 +#define OPT_TCPIP 1033 struct sc_option { char shortopt; @@ -404,6 +405,20 @@ static const struct sc_option options[] = { .text = "Keep the device on while scrcpy is running, when the device " "is plugged in.", }, + { + .longopt_id = OPT_TCPIP, + .longopt = "tcpip", + .argdesc = "ip[:port]", + .optional_arg = true, + .text = "Configure and reconnect the device over TCP/IP.\n" + "If a destination address is provided, then scrcpy connects to " + "this address before starting. The device must listen on the " + "given TCP port (default is 5555).\n" + "If no destination address is provided, then scrcpy attempts " + "to find the IP address of the current device (typically " + "connected over USB), enables TCP/IP mode, then connects to " + "this address before starting.", + }, { .longopt_id = OPT_WINDOW_BORDERLESS, .longopt = "window-borderless", @@ -1378,6 +1393,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_CLIPBOARD_AUTOSYNC: opts->clipboard_autosync = false; break; + case OPT_TCPIP: + opts->tcpip = true; + opts->tcpip_dst = optarg; + break; #ifdef HAVE_V4L2 case OPT_V4L2_SINK: opts->v4l2_device = optarg; @@ -1400,6 +1419,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + // If a TCP/IP address is provided, then tcpip must be enabled + assert(opts->tcpip || !opts->tcpip_dst); + + if (opts->serial && opts->tcpip_dst) { + LOGE("Incompatible options: -s/--serial and --tcpip with an argument"); + return false; + } + #ifdef HAVE_V4L2 if (!opts->display && !opts->record_filename && !opts->v4l2_device) { LOGE("-N/--no-display requires either screen recording (-r/--record)" diff --git a/app/src/options.c b/app/src/options.c index a99b09da..a14bda9a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -54,4 +54,6 @@ const struct scrcpy_options scrcpy_options_default = { .legacy_paste = false, .power_off_on_close = false, .clipboard_autosync = true, + .tcpip = false, + .tcpip_dst = NULL, }; diff --git a/app/src/options.h b/app/src/options.h index d10b2e8a..f183bd73 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -109,6 +109,8 @@ struct scrcpy_options { bool legacy_paste; bool power_off_on_close; bool clipboard_autosync; + bool tcpip; + const char *tcpip_dst; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f0142b46..99317ffc 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -365,6 +365,8 @@ scrcpy(struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, + .tcpip = options->tcpip, + .tcpip_dst = options->tcpip_dst, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index 6211b08b..06cb7b72 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -69,6 +69,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->crop); free((char *) params->codec_options); free((char *) params->encoder_name); + free((char *) params->tcpip_dst); } static bool @@ -92,6 +93,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(crop); COPY(codec_options); COPY(encoder_name); + COPY(tcpip_dst); #undef COPY return true; @@ -494,22 +496,215 @@ sc_server_fill_serial(struct sc_server *server) { LOGE("Could not get device serial"); return false; } + + LOGD("Device serial: %s", server->params.serial); + } + + return true; +} + +static bool +is_tcpip_mode_enabled(struct sc_server *server) { + struct sc_intr *intr = &server->intr; + const char *serial = server->params.serial; + + char *current_port = + adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); + if (!current_port) { + return false; + } + + // Is the device is listening on TCP on port 5555? + bool enabled = !strcmp("5555", current_port); + free(current_port); + return enabled; +} + +static bool +wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts, + sc_tick delay) { + if (is_tcpip_mode_enabled(server)) { + LOGI("TCP/IP mode enabled"); + return true; + } + + // Only print this log if TCP/IP is not enabled + LOGI("Waiting for TCP/IP mode enabled..."); + + do { + sc_tick deadline = sc_tick_now() + delay; + if (!sc_server_sleep(server, deadline)) { + LOGI("TCP/IP mode waiting interrupted"); + return false; + } + + if (is_tcpip_mode_enabled(server)) { + LOGI("TCP/IP mode enabled"); + return true; + } + } while (--attempts); + return false; +} + +char * +append_port_5555(const char *ip) { + size_t len = strlen(ip); + + // sizeof counts the final '\0' + char *ip_port = malloc(len + sizeof(":5555")); + if (!ip_port) { + LOG_OOM(); + return NULL; + } + + memcpy(ip_port, ip, len); + memcpy(ip_port + len, ":5555", sizeof(":5555")); + + return ip_port; +} + +static bool +sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { + const char *serial = server->params.serial; + assert(serial); + + struct sc_intr *intr = &server->intr; + + char *ip = adb_get_device_ip(intr, serial, 0); + if (!ip) { + LOGE("Device IP not found"); + return false; + } + + char *ip_port = append_port_5555(ip); + free(ip); + if (!ip_port) { + return false; + } + + bool tcp_mode = is_tcpip_mode_enabled(server); + + if (!tcp_mode) { + bool ok = adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); + if (!ok) { + LOGE("Could not restart adbd in TCP/IP mode"); + goto error; + } + + unsigned attempts = 40; + sc_tick delay = SC_TICK_FROM_MS(250); + ok = wait_tcpip_mode_enabled(server, attempts, delay); + if (!ok) { + goto error; + } + } + + *out_ip_port = ip_port; + + return true; + +error: + free(ip_port); + return false; +} + +static bool +sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { + struct sc_intr *intr = &server->intr; + + // Error expected if not connected, do not report any error + adb_disconnect(intr, ip_port, SC_ADB_SILENT); + + bool ok = adb_connect(intr, ip_port, 0); + if (!ok) { + LOGE("Could not connect to %s", ip_port); + return false; } + // Override the serial, owned by the sc_server_params + free((void *) server->params.serial); + server->params.serial = strdup(ip_port); + if (!server->params.serial) { + LOG_OOM(); + return false; + } + + LOGI("Connected to %s", ip_port); return true; } + +static bool +sc_server_configure_tcpip(struct sc_server *server) { + char *ip_port; + + const struct sc_server_params *params = &server->params; + + // If tcpip parameter is given, then it must connect to this address. + // Therefore, the device is unknown, so serial is meaningless at this point. + assert(!params->serial || !params->tcpip_dst); + + if (params->tcpip_dst) { + // Append ":5555" if no port is present + bool contains_port = strchr(params->tcpip_dst, ':'); + ip_port = contains_port ? strdup(params->tcpip_dst) + : append_port_5555(params->tcpip_dst); + if (!ip_port) { + LOG_OOM(); + return false; + } + } else { + // The device IP address must be retrieved from the current + // connected device + if (!sc_server_fill_serial(server)) { + return false; + } + + // The serial is either the real serial when connected via USB, or + // the IP:PORT when connected over TCP/IP. Only the latter contains + // a colon. + bool is_already_tcpip = strchr(params->serial, ':'); + if (is_already_tcpip) { + // Nothing to do + LOGI("Device already connected via TCP/IP: %s", params->serial); + return true; + } + + bool ok = sc_server_switch_to_tcpip(server, &ip_port); + if (!ok) { + return false; + } + } + + // On success, this call changes params->serial + bool ok = sc_server_connect_to_tcpip(server, ip_port); + free(ip_port); + return ok; +} + static int run_server(void *data) { struct sc_server *server = data; - if (!sc_server_fill_serial(server)) { - goto error_connection_failed; + const struct sc_server_params *params = &server->params; + + if (params->serial) { + LOGD("Device serial: %s", params->serial); } - const struct sc_server_params *params = &server->params; + if (params->tcpip) { + // params->serial may be changed after this call + bool ok = sc_server_configure_tcpip(server); + if (!ok) { + goto error_connection_failed; + } + } - LOGD("Device serial: %s", params->serial); + // It is ok to call this function even if the device serial has been + // changed by switching over TCP/IP + if (!sc_server_fill_serial(server)) { + goto error_connection_failed; + } bool ok = push_server(&server->intr, params->serial); if (!ok) { diff --git a/app/src/server.h b/app/src/server.h index 5b25ff46..8ea20dc7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -42,6 +42,8 @@ struct sc_server_params { bool force_adb_forward; bool power_off_on_close; bool clipboard_autosync; + bool tcpip; + const char *tcpip_dst; }; struct sc_server { From 1a6caeb18c06ddd3a1116a0e82d65be15b2f48b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Nov 2021 22:42:34 +0100 Subject: [PATCH 0196/1133] Document --tcpip in README PR #2827 --- README.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fa4aff68..ef9fcc98 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [Read in another language](#translations) This application provides display and control of Android devices connected via -USB (or [over TCP/IP](#wireless)). It does not require any _root_ access. +USB (or [over TCP/IP](#tcpip-wireless)). It does not require any _root_ access. It works on _GNU/Linux_, _Windows_ and _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) @@ -356,10 +356,38 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink ### Connection -#### Wireless +#### TCP/IP (wireless) _Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a -device over TCP/IP: +device over TCP/IP. + +##### Automatic + +An option `--tcpip` allows to configure the connection automatically. There are +two variants. + +If the device (accessible at 192.168.1.1 in this example) already listens on a +port (typically 5555) for incoming adb connections, then run: + +```bash +scrcpy --tcpip=192.168.1.1 # default port is 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +If the device TCP/IP mode is disabled (or if you don't know the IP address), +connect the device over USB, then run: + +```bash +scrcpy --tcpip # without arguments +``` + +It will automatically find the device IP address, enable TCP/IP mode, then +connect to the device before starting. + +##### Manual + +Alternatively, it is possible to enable the TCP/IP connection manually using +`adb`: 1. Connect the device to the same Wi-Fi as your computer. 2. Get your device IP address, in Settings → About phone → Status, or by From 82a053015d01d3a1e1b6f60fbe6c0cd6910dc344 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 21:19:39 +0100 Subject: [PATCH 0197/1133] Improve HID keyboard documentation Explain how to configure the keyboard layout. --- README.md | 9 +++++++++ app/scrcpy.1 | 6 ++++++ app/src/cli.c | 9 ++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ef9fcc98..3e167b06 100644 --- a/README.md +++ b/README.md @@ -803,6 +803,15 @@ 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]. +This settings page can be started directly: + +```bash +adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS +``` + +However, the option is only available when the HID keyboard is enabled (or when +a physical keyboard is connected). + [Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 715000b6..f7508d5e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -90,6 +90,12 @@ This provides a better experience for IME users, and allows to generate non-ASCI It may only work over USB, and is currently only supported on Linux. +The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: + + adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS + +However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected). + .TP .B \-\-legacy\-paste Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). diff --git a/app/src/cli.c b/app/src/cli.c index 8cfaf57b..410ce25a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -165,7 +165,14 @@ static const struct sc_option options[] = { "generate non-ASCII characters, contrary to the default " "injection method.\n" "It may only work over USB, and is currently only supported " - "on Linux.", + "on Linux.\n" + "The keyboard layout must be configured (once and for all) on " + "the device, via Settings -> System -> Languages and input -> " + "Physical keyboard. This settings page can be started " + "directly: `adb shell am start -a " + "android.settings.HARD_KEYBOARD_SETTINGS`.\n" + "However, the option is only available when the HID keyboard " + "is enabled (or a physical keyboard is connected).", }, { .shortopt = 'h', From c96505200a21d6816740fa1d0e272180280306e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 21:57:06 +0100 Subject: [PATCH 0198/1133] Fix code style in keyboard_inject --- app/src/keyboard_inject.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 4112fd4c..83924ab7 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -9,8 +9,7 @@ #include "util/log.h" /** Downcast key processor to sc_keyboard_inject */ -#define DOWNCAST(KP) \ - container_of(KP, struct sc_keyboard_inject, key_processor) +#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) #define MAP(FROM, TO) case FROM: *to = TO; return true #define FAIL default: return false From 0c0f62e4abe5bd58d9a2a90585b252c574009eec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 21:58:56 +0100 Subject: [PATCH 0199/1133] Use static maps to convert input events This improves readability (hopefully). PR #2831 --- app/meson.build | 1 + app/src/keyboard_inject.c | 163 +++++++++++++++++++++++--------------- app/src/mouse_inject.c | 35 +++++--- app/src/util/intmap.c | 13 +++ app/src/util/intmap.h | 24 ++++++ 5 files changed, 160 insertions(+), 76 deletions(-) create mode 100644 app/src/util/intmap.c create mode 100644 app/src/util/intmap.h diff --git a/app/meson.build b/app/meson.build index 720d9c8c..7d9c2b5c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -28,6 +28,7 @@ src = [ 'src/video_buffer.c', 'src/util/acksync.c', 'src/util/file.c', + 'src/util/intmap.c', 'src/util/intr.c', 'src/util/log.c', 'src/util/net.c', diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 83924ab7..bb3bb953 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -6,60 +6,115 @@ #include "android/input.h" #include "control_msg.h" #include "controller.h" +#include "util/intmap.h" #include "util/log.h" /** Downcast key processor to sc_keyboard_inject */ #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) -#define MAP(FROM, TO) case FROM: *to = TO; return true -#define FAIL default: return false static bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { - switch (from) { - MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); - MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); - FAIL; + static const struct sc_intmap_entry actions[] = { + {SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN}, + {SDL_KEYUP, AKEY_EVENT_ACTION_UP}, + }; + + const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); + if (entry) { + *to = entry->value; + return true; } + + return false; } static bool convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, bool prefer_text) { - switch (from) { - MAP(SDLK_RETURN, AKEYCODE_ENTER); - MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); - MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); - MAP(SDLK_BACKSPACE, AKEYCODE_DEL); - MAP(SDLK_TAB, AKEYCODE_TAB); - MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); - MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); - MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); - MAP(SDLK_END, AKEYCODE_MOVE_END); - MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); - MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); - MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); - MAP(SDLK_UP, AKEYCODE_DPAD_UP); - MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); - MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); - MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); - MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); + // Navigation keys and ENTER. + // Used in all modes. + static const struct sc_intmap_entry special_keys[] = { + {SDLK_RETURN, AKEYCODE_ENTER}, + {SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER}, + {SDLK_ESCAPE, AKEYCODE_ESCAPE}, + {SDLK_BACKSPACE, AKEYCODE_DEL}, + {SDLK_TAB, AKEYCODE_TAB}, + {SDLK_PAGEUP, AKEYCODE_PAGE_UP}, + {SDLK_DELETE, AKEYCODE_FORWARD_DEL}, + {SDLK_HOME, AKEYCODE_MOVE_HOME}, + {SDLK_END, AKEYCODE_MOVE_END}, + {SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN}, + {SDLK_RIGHT, AKEYCODE_DPAD_RIGHT}, + {SDLK_LEFT, AKEYCODE_DPAD_LEFT}, + {SDLK_DOWN, AKEYCODE_DPAD_DOWN}, + {SDLK_UP, AKEYCODE_DPAD_UP}, + {SDLK_LCTRL, AKEYCODE_CTRL_LEFT}, + {SDLK_RCTRL, AKEYCODE_CTRL_RIGHT}, + {SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT}, + {SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT}, + }; + + // Numpad navigation keys. + // Used in all modes, when NumLock and Shift are disabled. + static const struct sc_intmap_entry kp_nav_keys[] = { + {SDLK_KP_0, AKEYCODE_INSERT}, + {SDLK_KP_1, AKEYCODE_MOVE_END}, + {SDLK_KP_2, AKEYCODE_DPAD_DOWN}, + {SDLK_KP_3, AKEYCODE_PAGE_DOWN}, + {SDLK_KP_4, AKEYCODE_DPAD_LEFT}, + {SDLK_KP_6, AKEYCODE_DPAD_RIGHT}, + {SDLK_KP_7, AKEYCODE_MOVE_HOME}, + {SDLK_KP_8, AKEYCODE_DPAD_UP}, + {SDLK_KP_9, AKEYCODE_PAGE_UP}, + {SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL}, + }; + + // Letters and space. + // Used in non-text mode. + static const struct sc_intmap_entry alphaspace_keys[] = { + {SDLK_a, AKEYCODE_A}, + {SDLK_b, AKEYCODE_B}, + {SDLK_c, AKEYCODE_C}, + {SDLK_d, AKEYCODE_D}, + {SDLK_e, AKEYCODE_E}, + {SDLK_f, AKEYCODE_F}, + {SDLK_g, AKEYCODE_G}, + {SDLK_h, AKEYCODE_H}, + {SDLK_i, AKEYCODE_I}, + {SDLK_j, AKEYCODE_J}, + {SDLK_k, AKEYCODE_K}, + {SDLK_l, AKEYCODE_L}, + {SDLK_m, AKEYCODE_M}, + {SDLK_n, AKEYCODE_N}, + {SDLK_o, AKEYCODE_O}, + {SDLK_p, AKEYCODE_P}, + {SDLK_q, AKEYCODE_Q}, + {SDLK_r, AKEYCODE_R}, + {SDLK_s, AKEYCODE_S}, + {SDLK_t, AKEYCODE_T}, + {SDLK_u, AKEYCODE_U}, + {SDLK_v, AKEYCODE_V}, + {SDLK_w, AKEYCODE_W}, + {SDLK_x, AKEYCODE_X}, + {SDLK_y, AKEYCODE_Y}, + {SDLK_z, AKEYCODE_Z}, + {SDLK_SPACE, AKEYCODE_SPACE}, + }; + + const struct sc_intmap_entry *entry = + SC_INTMAP_FIND_ENTRY(special_keys, from); + if (entry) { + *to = entry->value; + return true; } if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { // Handle Numpad events when Num Lock is disabled // If SHIFT is pressed, a text event will be sent instead - switch(from) { - MAP(SDLK_KP_0, AKEYCODE_INSERT); - MAP(SDLK_KP_1, AKEYCODE_MOVE_END); - MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); - MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN); - MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT); - MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT); - MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME); - MAP(SDLK_KP_8, AKEYCODE_DPAD_UP); - MAP(SDLK_KP_9, AKEYCODE_PAGE_UP); - MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL); + entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from); + if (entry) { + *to = entry->value; + return true; } } @@ -71,37 +126,15 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { return false; } + // if ALT and META are not pressed, also handle letters and space - switch (from) { - MAP(SDLK_a, AKEYCODE_A); - MAP(SDLK_b, AKEYCODE_B); - MAP(SDLK_c, AKEYCODE_C); - MAP(SDLK_d, AKEYCODE_D); - MAP(SDLK_e, AKEYCODE_E); - MAP(SDLK_f, AKEYCODE_F); - MAP(SDLK_g, AKEYCODE_G); - MAP(SDLK_h, AKEYCODE_H); - MAP(SDLK_i, AKEYCODE_I); - MAP(SDLK_j, AKEYCODE_J); - MAP(SDLK_k, AKEYCODE_K); - MAP(SDLK_l, AKEYCODE_L); - MAP(SDLK_m, AKEYCODE_M); - MAP(SDLK_n, AKEYCODE_N); - MAP(SDLK_o, AKEYCODE_O); - MAP(SDLK_p, AKEYCODE_P); - MAP(SDLK_q, AKEYCODE_Q); - MAP(SDLK_r, AKEYCODE_R); - MAP(SDLK_s, AKEYCODE_S); - MAP(SDLK_t, AKEYCODE_T); - MAP(SDLK_u, AKEYCODE_U); - MAP(SDLK_v, AKEYCODE_V); - MAP(SDLK_w, AKEYCODE_W); - MAP(SDLK_x, AKEYCODE_X); - MAP(SDLK_y, AKEYCODE_Y); - MAP(SDLK_z, AKEYCODE_Z); - MAP(SDLK_SPACE, AKEYCODE_SPACE); - FAIL; + entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from); + if (entry) { + *to = entry->value; + return true; } + + return false; } static enum android_metastate diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 1d5fe230..8f7e363d 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -6,6 +6,7 @@ #include "android/input.h" #include "control_msg.h" #include "controller.h" +#include "util/intmap.h" #include "util/log.h" /** Downcast mouse processor to sc_mouse_inject */ @@ -32,25 +33,37 @@ convert_mouse_buttons(uint32_t state) { return buttons; } -#define MAP(FROM, TO) case FROM: *to = TO; return true -#define FAIL default: return false static bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { - switch (from) { - MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); - MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); - FAIL; + static const struct sc_intmap_entry actions[] = { + {SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN}, + {SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP}, + }; + + const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); + if (entry) { + *to = entry->value; + return true; } + + return false; } static bool convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { - switch (from) { - MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE); - MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN); - MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP); - FAIL; + static const struct sc_intmap_entry actions[] = { + {SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE}, + {SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN}, + {SDL_FINGERUP, AMOTION_EVENT_ACTION_UP}, + }; + + const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); + if (entry) { + *to = entry->value; + return true; } + + return false; } static bool diff --git a/app/src/util/intmap.c b/app/src/util/intmap.c new file mode 100644 index 00000000..fa11acef --- /dev/null +++ b/app/src/util/intmap.c @@ -0,0 +1,13 @@ +#include "intmap.h" + +const struct sc_intmap_entry * +sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len, + int32_t key) { + for (size_t i = 0; i < len; ++i) { + const struct sc_intmap_entry *entry = &entries[i]; + if (entry->key == key) { + return entry; + } + } + return NULL; +} diff --git a/app/src/util/intmap.h b/app/src/util/intmap.h new file mode 100644 index 00000000..2898c461 --- /dev/null +++ b/app/src/util/intmap.h @@ -0,0 +1,24 @@ +#ifndef SC_ARRAYMAP_H +#define SC_ARRAYMAP_H + +#include "common.h" + +#include + +struct sc_intmap_entry { + int32_t key; + int32_t value; +}; + +const struct sc_intmap_entry * +sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len, + int32_t key); + +/** + * MAP is expected to be a static array of sc_intmap_entry, so that + * ARRAY_LEN(MAP) can be computed statically. + */ +#define SC_INTMAP_FIND_ENTRY(MAP, KEY) \ + sc_intmap_find_entry(MAP, ARRAY_LEN(MAP), KEY) + +#endif From 5e918ac0c39cf7004c7f6843f2d28d4a06fd216e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 22:05:28 +0100 Subject: [PATCH 0200/1133] Use enum for key injection mode There was only two key injection modes: - the default one - the mode with --prefer-text enabled To prepare the addition of another mode (--raw-key-events), use an enum instead of a bool. PR #2831 --- app/src/cli.c | 2 +- app/src/keyboard_inject.c | 16 ++++++++-------- app/src/keyboard_inject.h | 2 +- app/src/options.c | 2 +- app/src/options.h | 13 ++++++++++++- app/tests/test_cli.c | 2 +- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 410ce25a..67a7a89f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1350,7 +1350,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->push_target = optarg; break; case OPT_PREFER_TEXT: - opts->prefer_text = true; + opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT; break; case OPT_ROTATION: if (!parse_rotation(optarg, &opts->rotation)) { diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index bb3bb953..e6212193 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -30,7 +30,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { static bool convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, - bool prefer_text) { + enum sc_key_inject_mode key_inject_mode) { // Navigation keys and ENTER. // Used in all modes. static const struct sc_intmap_entry special_keys[] = { @@ -118,7 +118,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, } } - if (prefer_text && !(mod & KMOD_CTRL)) { + if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) { // do not forward alpha and space key events (unless Ctrl is pressed) return false; } @@ -199,7 +199,7 @@ convert_meta_state(SDL_Keymod mod) { static bool convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, - bool prefer_text, uint32_t repeat) { + enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { @@ -208,7 +208,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, uint16_t mod = from->keysym.mod; if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, - prefer_text)) { + key_inject_mode)) { return false; } @@ -239,7 +239,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } struct control_msg msg; - if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) { + if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { if (!controller_push_msg(ki->controller, &msg)) { LOGW("Could not request 'inject keycode'"); } @@ -251,11 +251,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp, const SDL_TextInputEvent *event) { struct sc_keyboard_inject *ki = DOWNCAST(kp); - if (!ki->prefer_text) { + if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) { char c = event->text[0]; if (isalpha(c) || c == ' ') { assert(event->text[1] == '\0'); - // letters and space are handled as raw key event + // Letters and space are handled as raw key events return; } } @@ -278,7 +278,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki, struct controller *controller, const struct scrcpy_options *options) { ki->controller = controller; - ki->prefer_text = options->prefer_text; + ki->key_inject_mode = options->key_inject_mode; ki->forward_key_repeat = options->forward_key_repeat; ki->repeat = 0; diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h index f4ebe40e..edd5b1ba 100644 --- a/app/src/keyboard_inject.h +++ b/app/src/keyboard_inject.h @@ -18,7 +18,7 @@ struct sc_keyboard_inject { // number of repetitions. This variable keeps track of the count. unsigned repeat; - bool prefer_text; + enum sc_key_inject_mode key_inject_mode; bool forward_key_repeat; }; diff --git a/app/src/options.c b/app/src/options.c index a14bda9a..4860fa07 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -43,7 +43,7 @@ const struct scrcpy_options scrcpy_options_default = { .control = true, .display = true, .turn_screen_off = false, - .prefer_text = false, + .key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .window_borderless = false, .mipmaps = true, .stay_awake = false, diff --git a/app/src/options.h b/app/src/options.h index f183bd73..84a80fbe 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -38,6 +38,17 @@ enum sc_keyboard_input_mode { SC_KEYBOARD_INPUT_MODE_HID, }; +enum sc_key_inject_mode { + // Inject special keys, letters and space as key events. + // Inject numbers and punctuation as text events. + // This is the default mode. + SC_KEY_INJECT_MODE_MIXED, + + // Inject special keys as key events. + // Inject letters and space, numbers and punctuation as text events. + SC_KEY_INJECT_MODE_TEXT, +}; + #define SC_MAX_SHORTCUT_MODS 8 enum sc_shortcut_mod { @@ -98,7 +109,7 @@ struct scrcpy_options { bool control; bool display; bool turn_screen_off; - bool prefer_text; + enum sc_key_inject_mode key_inject_mode; bool window_borderless; bool mipmaps; bool stay_awake; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 05bacbf8..5bc1cc07 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -89,7 +89,7 @@ static void test_options(void) { assert(!strcmp(opts->serial, "0123456789abcdef")); assert(opts->show_touches); assert(opts->turn_screen_off); - assert(opts->prefer_text); + assert(opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT); assert(!strcmp(opts->window_title, "my device")); assert(opts->window_x == 100); assert(opts->window_y == -1); From bd56d81f728d29a0c710276f64e6c754edde217a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Nov 2021 22:15:44 +0100 Subject: [PATCH 0201/1133] Add --raw-key-events This option allows to inject all input keys as key events, and ignore text events. Fixes #2816 PR #2831 --- README.md | 8 ++++- app/scrcpy.1 | 4 +++ app/src/cli.c | 17 +++++++++++ app/src/keyboard_inject.c | 62 +++++++++++++++++++++++++++++++++++++++ app/src/options.h | 3 ++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e167b06..550f1f7f 100644 --- a/README.md +++ b/README.md @@ -833,7 +833,13 @@ 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 +On the contrary, you could force to always inject raw key events: + +```bash +scrcpy --raw-key-events +``` + +These options have 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 diff --git a/app/scrcpy.1 b/app/scrcpy.1 index f7508d5e..b99c781f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -165,6 +165,10 @@ Set the target directory for pushing files to the device by drag & drop. It is p Default is "/sdcard/Download/". +.TP +.B \-\-raw\-key\-events +Inject key events for all input keys, and ignore text events. + .TP .BI "\-r, \-\-record " file Record screen to diff --git a/app/src/cli.c b/app/src/cli.c index 67a7a89f..f0b2e89c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -51,6 +51,7 @@ #define OPT_TUNNEL_PORT 1031 #define OPT_NO_CLIPBOARD_AUTOSYNC 1032 #define OPT_TCPIP 1033 +#define OPT_RAW_KEY_EVENTS 1034 struct sc_option { char shortopt; @@ -282,6 +283,11 @@ static const struct sc_option options[] = { "drag & drop. It is passed as is to \"adb push\".\n" "Default is \"/sdcard/Download/\".", }, + { + .longopt_id = OPT_RAW_KEY_EVENTS, + .longopt = "raw-key-events", + .text = "Inject key events for all input keys, and ignore text events." + }, { .shortopt = 'r', .longopt = "record", @@ -1350,8 +1356,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->push_target = optarg; break; case OPT_PREFER_TEXT: + if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) { + LOGE("--prefer-text is incompatible with --raw-key-events"); + return false; + } opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT; break; + case OPT_RAW_KEY_EVENTS: + if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) { + LOGE("--prefer-text is incompatible with --raw-key-events"); + return false; + } + opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW; + break; case OPT_ROTATION: if (!parse_rotation(optarg, &opts->rotation)) { return false; diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index e6212193..5143eafc 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -101,6 +101,55 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, {SDLK_SPACE, AKEYCODE_SPACE}, }; + // Numbers and punctuation keys. + // Used in raw mode only. + static const struct sc_intmap_entry numbers_punct_keys[] = { + {SDLK_HASH, AKEYCODE_POUND}, + {SDLK_PERCENT, AKEYCODE_PERIOD}, + {SDLK_QUOTE, AKEYCODE_APOSTROPHE}, + {SDLK_ASTERISK, AKEYCODE_STAR}, + {SDLK_PLUS, AKEYCODE_PLUS}, + {SDLK_COMMA, AKEYCODE_COMMA}, + {SDLK_MINUS, AKEYCODE_MINUS}, + {SDLK_PERIOD, AKEYCODE_PERIOD}, + {SDLK_SLASH, AKEYCODE_SLASH}, + {SDLK_0, AKEYCODE_0}, + {SDLK_1, AKEYCODE_1}, + {SDLK_2, AKEYCODE_2}, + {SDLK_3, AKEYCODE_3}, + {SDLK_4, AKEYCODE_4}, + {SDLK_5, AKEYCODE_5}, + {SDLK_6, AKEYCODE_6}, + {SDLK_7, AKEYCODE_7}, + {SDLK_8, AKEYCODE_8}, + {SDLK_9, AKEYCODE_9}, + {SDLK_SEMICOLON, AKEYCODE_SEMICOLON}, + {SDLK_EQUALS, AKEYCODE_EQUALS}, + {SDLK_AT, AKEYCODE_AT}, + {SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET}, + {SDLK_BACKSLASH, AKEYCODE_BACKSLASH}, + {SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET}, + {SDLK_BACKQUOTE, AKEYCODE_GRAVE}, + {SDLK_KP_1, AKEYCODE_NUMPAD_1}, + {SDLK_KP_2, AKEYCODE_NUMPAD_2}, + {SDLK_KP_3, AKEYCODE_NUMPAD_3}, + {SDLK_KP_4, AKEYCODE_NUMPAD_4}, + {SDLK_KP_5, AKEYCODE_NUMPAD_5}, + {SDLK_KP_6, AKEYCODE_NUMPAD_6}, + {SDLK_KP_7, AKEYCODE_NUMPAD_7}, + {SDLK_KP_8, AKEYCODE_NUMPAD_8}, + {SDLK_KP_9, AKEYCODE_NUMPAD_9}, + {SDLK_KP_0, AKEYCODE_NUMPAD_0}, + {SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE}, + {SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY}, + {SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT}, + {SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD}, + {SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT}, + {SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS}, + {SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN}, + {SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN}, + }; + const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(special_keys, from); if (entry) { @@ -134,6 +183,14 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, return true; } + if (key_inject_mode == SC_KEY_INJECT_MODE_RAW) { + entry = SC_INTMAP_FIND_ENTRY(numbers_punct_keys, from); + if (entry) { + *to = entry->value; + return true; + } + } + return false; } @@ -251,6 +308,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp, const SDL_TextInputEvent *event) { struct sc_keyboard_inject *ki = DOWNCAST(kp); + if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { + // Never inject text events + return; + } + if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) { char c = event->text[0]; if (isalpha(c) || c == ' ') { diff --git a/app/src/options.h b/app/src/options.h index 84a80fbe..39703210 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -47,6 +47,9 @@ enum sc_key_inject_mode { // Inject special keys as key events. // Inject letters and space, numbers and punctuation as text events. SC_KEY_INJECT_MODE_TEXT, + + // Inject everything as key events. + SC_KEY_INJECT_MODE_RAW, }; #define SC_MAX_SHORTCUT_MODS 8 From bf97a46b0c93fa1ef71dbeff86f42f5ceccdb2ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 21:49:36 +0100 Subject: [PATCH 0202/1133] Upgrade gradle build tools to 7.0.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6ed7e4c6..c7d31ef7 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From cbe73b0bc3595ec191b6907edcace3fbb49b2506 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 09:05:53 +0100 Subject: [PATCH 0203/1133] Fix set_clipboard message log If paste is disabled on set_clipboard, then the PASTE key is not injected, but COPY is unrelated. PR #2834 --- app/src/control_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 90cde0cf..4cc2f9d7 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -197,7 +197,7 @@ control_msg_log(const struct control_msg *msg) { case CONTROL_MSG_TYPE_SET_CLIPBOARD: LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"", msg->set_clipboard.sequence, - msg->set_clipboard.paste ? "paste" : "copy", + msg->set_clipboard.paste ? "paste" : "nopaste", msg->set_clipboard.text); break; case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: From dc19ae334dcc55f64a9e29ec9d5e754c6da69657 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 09:18:23 +0100 Subject: [PATCH 0204/1133] Move acknowledgment handling Handle all actions related to SET_CLIPBOARD from the dedicated method. PR #2834 --- .../java/com/genymobile/scrcpy/Controller.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 8b24b300..b52d413e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -120,12 +120,7 @@ public class Controller { } break; case ControlMessage.TYPE_SET_CLIPBOARD: - long sequence = msg.getSequence(); - setClipboard(msg.getText(), msg.getPaste()); - if (sequence != ControlMessage.SEQUENCE_INVALID) { - // Acknowledgement requested - sender.pushAckClipboard(sequence); - } + setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); break; case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: if (device.supportsInputEvents()) { @@ -281,7 +276,7 @@ public class Controller { return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); } - private boolean setClipboard(String text, boolean paste) { + private boolean setClipboard(String text, boolean paste, long sequence) { boolean ok = device.setClipboardText(text); if (ok) { Ln.i("Device clipboard set"); @@ -292,6 +287,11 @@ public class Controller { device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE); } + if (sequence != ControlMessage.SEQUENCE_INVALID) { + // Acknowledgement requested + sender.pushAckClipboard(sequence); + } + return ok; } } From bfcb9d06c351e4976cdc3b69a24bf35b8f1bb1c2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 21:46:29 +0100 Subject: [PATCH 0205/1133] Expose sync mode for injecting events Expose the inject input event mode so that it is possible to wait for the events to be "finished". This will be necessary to read the clipboard content only after the COPY or CUT key event is handled. PR #2834 --- .../com/genymobile/scrcpy/Controller.java | 16 +++++----- .../java/com/genymobile/scrcpy/Device.java | 31 +++++++++++-------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index b52d413e..f853c85a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -55,7 +55,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device if (!Device.isScreenOn()) { - device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); + device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); // dirty hack // After POWER is injected, the device is powered on asynchronously. @@ -144,7 +144,7 @@ public class Controller { if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { schedulePowerModeOff(); } - return device.injectKeyEvent(action, keycode, repeat, metaState); + return device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC); } private boolean injectChar(char c) { @@ -155,7 +155,7 @@ public class Controller { return false; } for (KeyEvent event : events) { - if (!device.injectEvent(event)) { + if (!device.injectEvent(event, Device.INJECT_MODE_ASYNC)) { return false; } } @@ -219,7 +219,7 @@ public class Controller { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); - return device.injectEvent(event); + return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } private boolean injectScroll(Position position, int hScroll, int vScroll) { @@ -242,7 +242,7 @@ public class Controller { MotionEvent event = MotionEvent .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); - return device.injectEvent(event); + return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } /** @@ -260,7 +260,7 @@ public class Controller { private boolean pressBackOrTurnScreenOn(int action) { if (Device.isScreenOn()) { - return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0); + return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC); } // Screen is off @@ -273,7 +273,7 @@ public class Controller { if (keepPowerModeOff) { schedulePowerModeOff(); } - return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); + return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); } private boolean setClipboard(String text, boolean paste, long sequence) { @@ -284,7 +284,7 @@ public class Controller { // On Android >= 7, also press the PASTE key if requested if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { - device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE); + device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC); } if (sequence != ControlMessage.SEQUENCE_INVALID) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 35f4efd4..ba833a06 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -24,6 +24,10 @@ public final class Device { public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; + public static final int INJECT_MODE_ASYNC = InputManager.INJECT_INPUT_EVENT_MODE_ASYNC; + public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT; + public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; + public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; @@ -164,7 +168,7 @@ public final class Device { return supportsInputEvents; } - public static boolean injectEvent(InputEvent inputEvent, int displayId) { + public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) { if (!supportsInputEvents(displayId)) { throw new AssertionError("Could not inject input event if !supportsInputEvents()"); } @@ -173,30 +177,31 @@ public final class Device { return false; } - return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode); } - public boolean injectEvent(InputEvent event) { - return injectEvent(event, displayId); + public boolean injectEvent(InputEvent event, int injectMode) { + return injectEvent(event, displayId, injectMode); } - public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) { + public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) { long now = SystemClock.uptimeMillis(); KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); - return injectEvent(event, displayId); + return injectEvent(event, displayId, injectMode); } - public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { - return injectKeyEvent(action, keyCode, repeat, metaState, displayId); + public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) { + return injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode); } - public static boolean pressReleaseKeycode(int keyCode, int displayId) { - return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId); + public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) { + return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode) + && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode); } - public boolean pressReleaseKeycode(int keyCode) { - return pressReleaseKeycode(keyCode, displayId); + public boolean pressReleaseKeycode(int keyCode, int injectMode) { + return pressReleaseKeycode(keyCode, displayId, injectMode); } public static boolean isScreenOn() { @@ -272,7 +277,7 @@ public final class Device { if (!isScreenOn()) { return true; } - return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId); + return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC); } /** From e2b3968c66ff1310d7644aef92dcb543604e1b6b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 09:30:57 +0100 Subject: [PATCH 0206/1133] Always synchronize clipboard on explicit COPY/CUT If --no-clipboard-autosync is enabled, the automatic clipboard synchronization performed whenever the device clipboard changes is disabled. But on explicit COPY and CUT scrcpy shortcuts (MOD+c and MOD+x), the clipboard should still be synchronized, so that it remains possible to copy-paste from the device to the computer. This is consistent with the behavior of MOD+v, which pastes the computer clipboard to the device. Refs #2228 Refs #2817 PR #2834 --- app/src/control_msg.c | 17 ++++++--- app/src/control_msg.h | 9 +++++ app/src/input_manager.c | 35 +++++++++++-------- app/tests/test_control_msg_serialize.c | 6 +++- .../com/genymobile/scrcpy/ControlMessage.java | 16 +++++++++ .../scrcpy/ControlMessageReader.java | 13 ++++++- .../com/genymobile/scrcpy/Controller.java | 28 ++++++++++++--- .../java/com/genymobile/scrcpy/Server.java | 2 +- .../scrcpy/ControlMessageReaderTest.java | 2 ++ 9 files changed, 102 insertions(+), 26 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 4cc2f9d7..7fd77631 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -55,6 +55,12 @@ static const char *const screen_power_mode_labels[] = { "suspend", }; +static const char *const copy_key_labels[] = { + "none", + "copy", + "cut", +}; + static void write_position(uint8_t *buf, const struct sc_position *position) { buffer_write32be(&buf[0], position->point.x); @@ -117,6 +123,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: buf[1] = msg->inject_keycode.action; return 2; + case CONTROL_MSG_TYPE_GET_CLIPBOARD: + buf[1] = msg->get_clipboard.copy_key; + return 2; case CONTROL_MSG_TYPE_SET_CLIPBOARD: { buffer_write64be(&buf[1], msg->set_clipboard.sequence); buf[9] = !!msg->set_clipboard.paste; @@ -131,7 +140,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_COLLAPSE_PANELS: - case CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_ROTATE_DEVICE: // no additional data return 1; @@ -194,6 +202,10 @@ control_msg_log(const struct control_msg *msg) { LOG_CMSG("back-or-screen-on %s", KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); break; + case CONTROL_MSG_TYPE_GET_CLIPBOARD: + LOG_CMSG("get clipboard copy_key=%s", + copy_key_labels[msg->get_clipboard.copy_key]); + break; case CONTROL_MSG_TYPE_SET_CLIPBOARD: LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"", msg->set_clipboard.sequence, @@ -213,9 +225,6 @@ control_msg_log(const struct control_msg *msg) { case CONTROL_MSG_TYPE_COLLAPSE_PANELS: LOG_CMSG("collapse panels"); break; - case CONTROL_MSG_TYPE_GET_CLIPBOARD: - LOG_CMSG("get clipboard"); - break; case CONTROL_MSG_TYPE_ROTATE_DEVICE: LOG_CMSG("rotate device"); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 7352defe..6f1824bb 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -41,6 +41,12 @@ enum screen_power_mode { SCREEN_POWER_MODE_NORMAL = 2, }; +enum get_clipboard_copy_key { + GET_CLIPBOARD_COPY_KEY_NONE, + GET_CLIPBOARD_COPY_KEY_COPY, + GET_CLIPBOARD_COPY_KEY_CUT, +}; + struct control_msg { enum control_msg_type type; union { @@ -69,6 +75,9 @@ struct control_msg { enum android_keyevent_action action; // action for the BACK key // screen may only be turned on on ACTION_DOWN } back_or_screen_on; + struct { + enum get_clipboard_copy_key copy_key; + } get_clipboard; struct { uint64_t sequence; char *text; // owned, to be freed by free() diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 31d6540f..9a553842 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -148,16 +148,6 @@ action_menu(struct controller *controller, int actions) { send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); } -static inline void -action_copy(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_COPY, actions, "COPY"); -} - -static inline void -action_cut(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_CUT, actions, "CUT"); -} - // turn the screen on if it was off, press BACK otherwise // If the screen is off, it is turned on only on ACTION_DOWN static void @@ -211,6 +201,21 @@ collapse_panels(struct controller *controller) { } } +static bool +get_device_clipboard(struct controller *controller, + enum get_clipboard_copy_key copy_key) { + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; + msg.get_clipboard.copy_key = copy_key; + + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'get device clipboard'"); + return false; + } + + return true; +} + static bool set_device_clipboard(struct controller *controller, bool paste, uint64_t sequence) { @@ -450,13 +455,15 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_c: - if (control && !shift && !repeat) { - action_copy(controller, action); + if (control && !shift && !repeat && down) { + get_device_clipboard(controller, + GET_CLIPBOARD_COPY_KEY_COPY); } return; case SDLK_x: - if (control && !shift && !repeat) { - action_cut(controller, action); + if (control && !shift && !repeat && down) { + get_device_clipboard(controller, + GET_CLIPBOARD_COPY_KEY_CUT); } return; case SDLK_v: diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 5cd7056b..6fed2438 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -210,14 +210,18 @@ static void test_serialize_collapse_panels(void) { static void test_serialize_get_clipboard(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_GET_CLIPBOARD, + .get_clipboard = { + .copy_key = GET_CLIPBOARD_COPY_KEY_COPY, + }, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; size_t size = control_msg_serialize(&msg, buf); - assert(size == 1); + assert(size == 2); const unsigned char expected[] = { CONTROL_MSG_TYPE_GET_CLIPBOARD, + GET_CLIPBOARD_COPY_KEY_COPY, }; assert(!memcmp(buf, expected, sizeof(expected))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 2cd80191..63ba0fa3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -20,6 +20,10 @@ public final class ControlMessage { public static final long SEQUENCE_INVALID = 0; + public static final int COPY_KEY_NONE = 0; + public static final int COPY_KEY_COPY = 1; + public static final int COPY_KEY_CUT = 2; + private int type; private String text; private int metaState; // KeyEvent.META_* @@ -31,6 +35,7 @@ public final class ControlMessage { private Position position; private int hScroll; private int vScroll; + private int copyKey; private boolean paste; private int repeat; private long sequence; @@ -82,6 +87,13 @@ public final class ControlMessage { return msg; } + public static ControlMessage createGetClipboard(int copyKey) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_GET_CLIPBOARD; + msg.copyKey = copyKey; + return msg; + } + public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; @@ -151,6 +163,10 @@ public final class ControlMessage { return vScroll; } + public int getCopyKey() { + return copyKey; + } + public boolean getPaste() { return paste; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 80931e94..f09ed26f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -13,6 +13,7 @@ public class ControlMessageReader { static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; + static final int GET_CLIPBOARD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k @@ -70,6 +71,9 @@ public class ControlMessageReader { case ControlMessage.TYPE_BACK_OR_SCREEN_ON: msg = parseBackOrScreenOnEvent(); break; + case ControlMessage.TYPE_GET_CLIPBOARD: + msg = parseGetClipboard(); + break; case ControlMessage.TYPE_SET_CLIPBOARD: msg = parseSetClipboard(); break; @@ -79,7 +83,6 @@ public class ControlMessageReader { case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_COLLAPSE_PANELS: - case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_ROTATE_DEVICE: msg = ControlMessage.createEmpty(type); break; @@ -162,6 +165,14 @@ public class ControlMessageReader { return ControlMessage.createBackOrScreenOn(action); } + private ControlMessage parseGetClipboard() { + if (buffer.remaining() < GET_CLIPBOARD_LENGTH) { + return null; + } + int copyKey = toUnsigned(buffer.get()); + return ControlMessage.createGetClipboard(copyKey); + } + private ControlMessage parseSetClipboard() { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { return null; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index f853c85a..9246004a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -21,6 +21,7 @@ public class Controller { private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; + private final boolean clipboardAutosync; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -31,9 +32,10 @@ public class Controller { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection) { + public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) { this.device = device; this.connection = connection; + this.clipboardAutosync = clipboardAutosync; initPointers(); sender = new DeviceMessageSender(connection); } @@ -114,10 +116,7 @@ public class Controller { Device.collapsePanels(); break; case ControlMessage.TYPE_GET_CLIPBOARD: - String clipboardText = Device.getClipboardText(); - if (clipboardText != null) { - sender.pushClipboardText(clipboardText); - } + getClipboard(msg.getCopyKey()); break; case ControlMessage.TYPE_SET_CLIPBOARD: setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); @@ -276,6 +275,25 @@ public class Controller { return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); } + private void getClipboard(int copyKey) { + // On Android >= 7, press the COPY or CUT key if requested + if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { + int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT; + // Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one + device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH); + } + + // If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in + // particular when COPY or CUT are injected, so it should not be synchronized twice. On Android < 7, do not synchronize at all rather than + // copying an old clipboard content. + if (!clipboardAutosync) { + String clipboardText = Device.getClipboardText(); + if (clipboardText != null) { + sender.pushClipboardText(clipboardText); + } + } + } + private boolean setClipboard(String text, boolean paste, long sequence) { boolean ok = device.setClipboardText(text); if (ok) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 0f0abab0..fc31dada 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -74,7 +74,7 @@ public final class Server { Thread controllerThread = null; Thread deviceMessageSenderThread = null; if (options.getControl()) { - final Controller controller = new Controller(device, connection); + final Controller controller = new Controller(device, connection, options.getClipboardAutosync()); // asynchronous controllerThread = startController(controller); diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 3b0b6c0d..5e79d4f0 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -219,6 +219,7 @@ public class ControlMessageReaderTest { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD); + dos.writeByte(ControlMessage.COPY_KEY_COPY); byte[] packet = bos.toByteArray(); @@ -226,6 +227,7 @@ public class ControlMessageReaderTest { ControlMessage event = reader.next(); Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType()); + Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey()); } @Test From b25b674c450db3817b529a89079908d75245a300 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 22:03:20 +0100 Subject: [PATCH 0207/1133] Clarify TCP/IP mode in README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 550f1f7f..4ece2290 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,8 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink #### TCP/IP (wireless) _Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a -device over TCP/IP. +device over TCP/IP. The device must be connected on the same network as the +computer. ##### Automatic @@ -374,8 +375,8 @@ scrcpy --tcpip=192.168.1.1 # default port is 5555 scrcpy --tcpip=192.168.1.1:5555 ``` -If the device TCP/IP mode is disabled (or if you don't know the IP address), -connect the device over USB, then run: +If adb TCP/IP mode is disabled on the device (or if you don't know the IP +address), connect the device over USB, then run: ```bash scrcpy --tcpip # without arguments From 003e7381064f2c0e8ce1095d1a65522fab4e118b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 22:15:28 +0100 Subject: [PATCH 0208/1133] Bump version to 1.21 --- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 7a814212..0b4ed174 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.20', + version: '1.21', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 52781a29..1f939a1a 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 12000 - versionName "1.20" + versionCode 12100 + versionName "1.21" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 61b42103..0f86c29f 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.20 +SCRCPY_VERSION_NAME=1.21 PLATFORM_VERSION=31 PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} From cb8713eb1fcb8baff914f04952c55411891f86a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 Nov 2021 22:24:06 +0100 Subject: [PATCH 0209/1133] Update links to v1.21 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index 5f473ce2..c9473f27 100644 --- a/BUILD.md +++ b/BUILD.md @@ -270,10 +270,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.20`][direct-scrcpy-server] - _(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_ + - [`scrcpy-server-v1.21`][direct-scrcpy-server] + _(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index af381057..d6908bd6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.20) +# scrcpy (v1.21) scrcpy @@ -101,10 +101,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.20.zip`][direct-win64] - _(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_ + - [`scrcpy-win64-v1.21.zip`][direct-win64] + _(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index e12b4469..2a59a6f1 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 -PREBUILT_SERVER_SHA256=b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21 +PREBUILT_SERVER_SHA256=dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 86c91e183d1fae21f43172c7d67140cefdc3cc1c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 30 Nov 2021 09:41:47 +0100 Subject: [PATCH 0210/1133] Log CreateProcessW() error code on Windows Refs #2838 --- app/src/sys/win/process.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index bed98479..70da9a9a 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -154,7 +154,9 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); free(wide); if (!ok) { - if (GetLastError() == ERROR_FILE_NOT_FOUND) { + int err = GetLastError(); + LOGE("CreateProcessW() error %d", err); + if (err == ERROR_FILE_NOT_FOUND) { ret = SC_PROCESS_ERROR_MISSING_BINARY; } goto error_free_attribute_list; From 64a04b8d4a263df1a6eaf5f9794b9e7974d3b591 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 30 Nov 2021 12:22:12 +0100 Subject: [PATCH 0211/1133] Fix process execution on Windows 7 According to this bug report on Firefox: > CreateProcess fails with ERROR_NO_SYSTEM_RESOURCES on Windows 7. It > looks like the reason why is because PROC_THREAD_ATTRIBUTE_HANDLE_LIST > doesn't like console handles. To avoid the problem, do not pass console handles to PROC_THREAD_ATTRIBUTE_HANDLE_LIST. Refs #2783 Refs f801d8b3128cf5aae3725c981f32abfd4b6c307e Fixes #2838 PR #2840 --- app/src/sys/win/process.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 70da9a9a..6e9da09c 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -30,9 +30,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); // Add 1 per non-NULL pointer - unsigned handle_count = !!pin - + (pout || inherit_stdout) - + (perr || inherit_stderr); + unsigned handle_count = !!pin || !!pout || !!perr; enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; @@ -81,23 +79,29 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, si.StartupInfo.cb = sizeof(si); HANDLE handles[3]; + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + if (inherit_stdout) { + si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + if (inherit_stderr) { + si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + } + LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; if (handle_count) { - si.StartupInfo.dwFlags = STARTF_USESTDHANDLES; - unsigned i = 0; if (pin) { si.StartupInfo.hStdInput = stdin_read_handle; handles[i++] = si.StartupInfo.hStdInput; } - if (pout || inherit_stdout) { - si.StartupInfo.hStdOutput = pout ? stdout_write_handle - : GetStdHandle(STD_OUTPUT_HANDLE); + if (pout) { + assert(!inherit_stdout); + si.StartupInfo.hStdOutput = stdout_write_handle; handles[i++] = si.StartupInfo.hStdOutput; } - if (perr || inherit_stderr) { - si.StartupInfo.hStdError = perr ? stderr_write_handle - : GetStdHandle(STD_ERROR_HANDLE); + if (perr) { + assert(!inherit_stderr); + si.StartupInfo.hStdError = stderr_write_handle; handles[i++] = si.StartupInfo.hStdError; } @@ -146,10 +150,15 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, goto error_free_attribute_list; } - BOOL bInheritHandles = handle_count > 0; - // DETACHED_PROCESS to disable stdin, stdout and stderr - DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT - : DETACHED_PROCESS; + BOOL bInheritHandles = handle_count > 0 || inherit_stdout || inherit_stderr; + DWORD dwCreationFlags = 0; + if (handle_count > 0) { + dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; + } + if (!inherit_stdout && !inherit_stderr) { + // DETACHED_PROCESS to disable stdin, stdout and stderr + dwCreationFlags |= DETACHED_PROCESS; + } BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles, dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); free(wide); From 5704ec6967c03510dd7d206e5f59c1e4fd6e10a0 Mon Sep 17 00:00:00 2001 From: Archisman Panigrahi Date: Thu, 2 Dec 2021 02:55:45 +0530 Subject: [PATCH 0212/1133] AUR to official Arch Repository PR #2844 Signed-off-by: Romain Vimont --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d6908bd6..201141a8 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,12 @@ On Debian and Ubuntu: apt install scrcpy ``` +On Arch Linux: + +``` +pacman -S scrcpy +``` + A [Snap] package is available: [`scrcpy`][snap-link]. [snap-link]: https://snapstats.org/snaps/scrcpy @@ -82,10 +88,6 @@ For Fedora, a [COPR] package is available: [`scrcpy`][copr-link]. [COPR]: https://fedoraproject.org/wiki/Category:Copr [copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ -For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. From ab00210b37b56f32da1d4e48188e4584c50e638b Mon Sep 17 00:00:00 2001 From: yangfl Date: Thu, 2 Dec 2021 20:49:35 +0100 Subject: [PATCH 0213/1133] Fix script to build without gradle The PLATFORM variable is assigned either from $ANDROID_PLATFORM or gets a default value (currently $PLATFORM_VERSION). The check to use either dx (SDK < 31) or d8 (SDK >= 31) must be based on the actual $PLATFORM, not the default $PLATFORM_VERSION. Refs 52138fd9213216a21fbdcda4d430394d7c8f0979 Refs PR #2850 Signed-off-by: Romain Vimont --- server/build_without_gradle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 0f86c29f..c3c6630c 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -57,7 +57,7 @@ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ echo "Dexing..." cd "$CLASSES_DIR" -if [[ $PLATFORM_VERSION -lt 31 ]] +if [[ $PLATFORM -lt 31 ]] then # use dx "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ From a208400133a2a7e7392649b593f46eed87be4d91 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Dec 2021 20:54:23 +0100 Subject: [PATCH 0214/1133] Remove useless intermediate variable Now that PLATFORM is correctly used in the if-condition, PLATFORM_VERSION is useless. PR #2850 --- server/build_without_gradle.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index c3c6630c..ab5e19e4 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,7 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=1.21 -PLATFORM_VERSION=31 -PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} +PLATFORM=${ANDROID_PLATFORM:-31} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" From 65fbec96436151e8ebce27c0d46e98b86e78c1ce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Dec 2021 21:43:09 +0100 Subject: [PATCH 0215/1133] Mention SCRCPY_ICON_PATH env var in manpage --- app/scrcpy.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b99c781f..38c92d39 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -419,6 +419,10 @@ Specify the path to adb. .B SCRCPY_SERVER_PATH Specify the path to server binary. +.TP +.B SCRCPY_ICON_PATH +Specify the path to the program icon. + .SH AUTHORS .B scrcpy From 94702a4309c6b25cf87a18c729fda029f8eb6da1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Dec 2021 09:10:40 +0100 Subject: [PATCH 0216/1133] Fix memset() size in tests The memset() size was 1 byte too long. It was harmless because the last 'a' was overwritten by '\0`. --- app/tests/test_control_msg_serialize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 6fed2438..c7f464c8 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -54,7 +54,7 @@ static void test_serialize_inject_text_long(void) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; - memset(text, 'a', sizeof(text)); + memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; msg.inject_text.text = text; From daa06abd34981a09aaecd85307b5719ca34f6034 Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sat, 4 Dec 2021 10:54:21 +0800 Subject: [PATCH 0217/1133] Fix comment in control message serialization Refs 245999aec4a4a1454212b38a988aa96fa701bb04 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/control_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 7fd77631..d2957467 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -69,7 +69,7 @@ write_position(uint8_t *buf, const struct sc_position *position) { buffer_write16be(&buf[10], position->screen_size.height); } -// write length (2 bytes) + string (non nul-terminated) +// write length (4 bytes) + string (non null-terminated) static size_t write_string(const char *utf8, size_t max_len, unsigned char *buf) { size_t len = sc_str_utf8_truncation_index(utf8, max_len); From d80bc25ebacc0fe1ba40ab836e03e6590156aecc Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sat, 4 Dec 2021 11:11:30 +0800 Subject: [PATCH 0218/1133] Fix overflow in memcpy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In function ‘memcpy’, inlined from ‘control_msg_serialize.constprop’ at ../app/src/control_msg.c:77:5, inlined from ‘run_controller’ at ../app/src/controller.c:69:12: /usr/include/x86_64-linux-gnu/bits/string_fortified.h:34:10: warning: ‘__builtin___memcpy_chk’ writing 262138 bytes into a region of size 262130 overflows the destination [-Wstringop-overflow=] return __builtin___memcpy_chk (__dest, __src, __len, __bos0 (__dest)); Refs 901d8371655582b432d5d92430177a59df8058b9 PR #2859 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/control_msg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 6f1824bb..7f3235d7 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -14,8 +14,8 @@ #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 -// type: 1 byte; paste flag: 1 byte; length: 4 bytes -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) +// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes +#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14) #define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) From ae90ef22db27ca0709ca57a3c08236ccb4cb4673 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Dec 2021 09:08:21 +0100 Subject: [PATCH 0219/1133] Add a unit test for clipboard text length This would have catched the possible memcpy() overflow fixed by the previous commit. Refs #2859 --- app/tests/test_control_msg_serialize.c | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index c7f464c8..42b72b59 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -250,6 +250,40 @@ static void test_serialize_set_clipboard(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_set_clipboard_long(void) { + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, + .set_clipboard = { + .sequence = UINT64_C(0x0102030405060708), + .paste = true, + .text = NULL, + }, + }; + + char text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1]; + memset(text, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); + text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0'; + msg.set_clipboard.text = text; + + unsigned char buf[CONTROL_MSG_MAX_SIZE]; + size_t size = control_msg_serialize(&msg, buf); + assert(size == CONTROL_MSG_MAX_SIZE); + + unsigned char expected[CONTROL_MSG_MAX_SIZE] = { + CONTROL_MSG_TYPE_SET_CLIPBOARD, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence + 1, // paste + // text length + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24, + (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff, + (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff, + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff, + }; + memset(expected + 14, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); + + assert(!memcmp(buf, expected, sizeof(expected))); +} + static void test_serialize_set_screen_power_mode(void) { struct control_msg msg = { .type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, @@ -299,6 +333,7 @@ int main(int argc, char *argv[]) { test_serialize_collapse_panels(); test_serialize_get_clipboard(); test_serialize_set_clipboard(); + test_serialize_set_clipboard_long(); test_serialize_set_screen_power_mode(); test_serialize_rotate_device(); return 0; From 36c8778d2d83fe74a2de9873db0eabfed77f528c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Dec 2021 09:25:36 +0100 Subject: [PATCH 0220/1133] Add missing comma Thank you clang: ../app/src/control_msg.c:45:5: warning: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Wstring-concatenation] "hover-exit", ^ --- app/src/control_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index d2957467..6ccdc054 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -41,7 +41,7 @@ static const char *const android_motionevent_action_labels[] = { "pointer-up", "hover-move", "scroll", - "hover-enter" + "hover-enter", "hover-exit", "btn-press", "btn-release", From 90cf956f57338e66c983960d767ad6cdf7e7c7a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Dec 2021 09:27:59 +0100 Subject: [PATCH 0221/1133] Remove spurious ';' --- app/src/cli.c | 2 +- app/tests/test_cli.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index f0b2e89c..2ecf96f1 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -678,7 +678,7 @@ sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) { } return true; -}; +} static void sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) { diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 5bc1cc07..a29d5fdd 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -169,4 +169,4 @@ int main(int argc, char *argv[]) { test_options2(); test_parse_shortcut_mods(); return 0; -}; +} From dca2c5f94fd0f35ef1c6583cc59bb9ba1b6bdbd4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Dec 2021 23:46:01 +0100 Subject: [PATCH 0222/1133] Require SDL >= 2.0.5 Icon loading uses SDL_CreateRGBSurfaceWithFormatFrom(), available since SDL 2.0.5 (in 2016). Refs #2862 --- app/meson.build | 2 +- app/src/compat.h | 9 --------- app/src/scrcpy.c | 2 -- app/src/screen.c | 12 +----------- 4 files changed, 2 insertions(+), 23 deletions(-) diff --git a/app/meson.build b/app/meson.build index 7d9c2b5c..1907812d 100644 --- a/app/meson.build +++ b/app/meson.build @@ -87,7 +87,7 @@ if not get_option('crossbuild_windows') dependency('libavformat'), dependency('libavcodec'), dependency('libavutil'), - dependency('sdl2'), + dependency('sdl2', version: '>= 2.0.5'), ] if v4l2_support diff --git a/app/src/compat.h b/app/src/compat.h index 3ab56049..311d617d 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -35,15 +35,6 @@ # define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL #endif -#if SDL_VERSION_ATLEAST(2, 0, 5) -// -# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH -// -# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS -// -# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP -#endif - #if SDL_VERSION_ATLEAST(2, 0, 6) // # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 99317ffc..9c2bd03b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -94,12 +94,10 @@ sdl_set_hints(const char *render_driver) { LOGW("Could not enable linear filtering"); } -#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH // Handle a click to gain focus as any other click if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) { LOGW("Could not enable mouse focus clickthrough"); } -#endif #ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS // Disable synthetic mouse events from touch events diff --git a/app/src/screen.c b/app/src/screen.c index 34a2d5d9..c72fdf44 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -64,12 +64,7 @@ set_window_size(struct screen *screen, struct sc_size new_size) { static bool get_preferred_display_bounds(struct sc_size *bounds) { SDL_Rect rect; -#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS -# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) -#else -# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r)) -#endif - if (GET_DISPLAY_BOUNDS(0, &rect)) { + if (SDL_GetDisplayUsableBounds(0, &rect)) { LOGW("Could not get display usable bounds: %s", SDL_GetError()); return false; } @@ -394,12 +389,7 @@ screen_init(struct screen *screen, const struct screen_params *params) { | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { -#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; -#else - LOGW("The 'always on top' flag is not available " - "(compile with SDL >= 2.0.5 to enable it)"); -#endif } if (params->window_borderless) { window_flags |= SDL_WINDOW_BORDERLESS; From 099c54658057f283d0c4c91b160a77efdbeee4f2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Dec 2021 23:55:29 +0100 Subject: [PATCH 0223/1133] Require libavformat >= 57.33 In ffmpeg/doc/APIchanges: > 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h > Add AVStream.codecpar, deprecate AVStream.codec. Refs 5d9e96dc4eaa41b185c41e8a6df6191762764b1c Refs #2862 --- app/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/meson.build b/app/meson.build index 1907812d..bac8d25e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -84,7 +84,7 @@ if not get_option('crossbuild_windows') # native build dependencies = [ - dependency('libavformat'), + dependency('libavformat', version: '>= 57.33'), dependency('libavcodec'), dependency('libavutil'), dependency('sdl2', version: '>= 2.0.5'), From 80fe12a95fd240e3698966842eef4c06e84fb152 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Dec 2021 00:02:22 +0100 Subject: [PATCH 0224/1133] Require libavcodec >= 57.37 In ffmpeg/doc/APIchanges: > 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h > Add a new audio/video encoding and decoding API with decoupled input > and output -- avcodec_send_packet(), avcodec_receive_frame(), > avcodec_send_frame() and avcodec_receive_packet(). Refs de9b79ec2dd73e5442c6bf0161669bcd8ca7d5be Refs #2862 --- app/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/meson.build b/app/meson.build index bac8d25e..3916b098 100644 --- a/app/meson.build +++ b/app/meson.build @@ -85,7 +85,7 @@ if not get_option('crossbuild_windows') # native build dependencies = [ dependency('libavformat', version: '>= 57.33'), - dependency('libavcodec'), + dependency('libavcodec', version: '>= 57.37'), dependency('libavutil'), dependency('sdl2', version: '>= 2.0.5'), ] From cabcbc2b151255b49a442a1c43e7db44432eb58e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Dec 2021 21:50:24 +0100 Subject: [PATCH 0225/1133] Do not create control socket if no control If --no-control is enabled, then it is not necessary to create a second communication socket between the client and the server. This also facilitates the use of the server alone (without the client) to receive only the raw video stream. --- app/src/server.c | 33 ++++++++------ .../genymobile/scrcpy/DesktopConnection.java | 45 ++++++++++++------- .../java/com/genymobile/scrcpy/Server.java | 5 ++- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 06cb7b72..8453f959 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -388,6 +388,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { assert(tunnel->enabled); const char *serial = server->params.serial; + bool control = server->params.control; sc_socket video_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE; @@ -397,9 +398,12 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { goto fail; } - control_socket = net_accept_intr(&server->intr, tunnel->server_socket); - if (control_socket == SC_SOCKET_NONE) { - goto fail; + if (control) { + control_socket = + net_accept_intr(&server->intr, tunnel->server_socket); + if (control_socket == SC_SOCKET_NONE) { + goto fail; + } } } else { uint32_t tunnel_host = server->params.tunnel_host; @@ -420,15 +424,18 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { goto fail; } - // we know that the device is listening, we don't need several attempts - control_socket = net_socket(); - if (control_socket == SC_SOCKET_NONE) { - goto fail; - } - bool ok = net_connect_intr(&server->intr, control_socket, tunnel_host, - tunnel_port); - if (!ok) { - goto fail; + if (control) { + // we know that the device is listening, we don't need several + // attempts + control_socket = net_socket(); + if (control_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, control_socket, + tunnel_host, tunnel_port); + if (!ok) { + goto fail; + } } } @@ -442,7 +449,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { } assert(video_socket != SC_SOCKET_NONE); - assert(control_socket != SC_SOCKET_NONE); + assert(!control || control_socket != SC_SOCKET_NONE); server->video_socket = video_socket; server->control_socket = control_socket; diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 0ec43040..40cb088c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -30,8 +30,13 @@ public final class DesktopConnection implements Closeable { private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; this.controlSocket = controlSocket; - controlInputStream = controlSocket.getInputStream(); - controlOutputStream = controlSocket.getOutputStream(); + if (controlSocket != null) { + controlInputStream = controlSocket.getInputStream(); + controlOutputStream = controlSocket.getOutputStream(); + } else { + controlInputStream = null; + controlOutputStream = null; + } videoFd = videoSocket.getFileDescriptor(); } @@ -41,31 +46,35 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { + public static DesktopConnection open(Device device, boolean tunnelForward, boolean control) throws IOException { LocalSocket videoSocket; - LocalSocket controlSocket; + LocalSocket controlSocket = null; if (tunnelForward) { LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME); try { videoSocket = localServerSocket.accept(); // send one byte so the client may read() to detect a connection error videoSocket.getOutputStream().write(0); - try { - controlSocket = localServerSocket.accept(); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; + if (control) { + try { + controlSocket = localServerSocket.accept(); + } catch (IOException | RuntimeException e) { + videoSocket.close(); + throw e; + } } } finally { localServerSocket.close(); } } else { videoSocket = connect(SOCKET_NAME); - try { - controlSocket = connect(SOCKET_NAME); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; + if (control) { + try { + controlSocket = connect(SOCKET_NAME); + } catch (IOException | RuntimeException e) { + videoSocket.close(); + throw e; + } } } @@ -79,9 +88,11 @@ public final class DesktopConnection implements Closeable { videoSocket.shutdownInput(); videoSocket.shutdownOutput(); videoSocket.close(); - controlSocket.shutdownInput(); - controlSocket.shutdownOutput(); - controlSocket.close(); + if (controlSocket != null) { + controlSocket.shutdownInput(); + controlSocket.shutdownOutput(); + controlSocket.close(); + } } private void send(String deviceName, int width, int height) throws IOException { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index fc31dada..4f9575ae 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -66,14 +66,15 @@ public final class Server { Thread initThread = startInitThread(options); boolean tunnelForward = options.isTunnelForward(); + boolean control = options.getControl(); - try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { + try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName()); Thread controllerThread = null; Thread deviceMessageSenderThread = null; - if (options.getControl()) { + if (control) { final Controller controller = new Controller(device, connection, options.getClipboardAutosync()); // asynchronous From ddb9396743072f97628fab168ef7fcd45a597b03 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Dec 2021 23:20:17 +0100 Subject: [PATCH 0226/1133] Interrupt and close sockets on server stop The sockets were never interrupted or closed by the client since recent changes to run the server from a dedicated thread (see commit 04267085441d6fcd05eff7df0118708f7622e237). As a side effect, the server could never terminate properly (it was waiting on socket blocking calls), so it was always killed by the client after the WATCHDOG_DELAY. Interrupt the sockets on stop to give the servera chance to terminate property, then close them. --- app/src/server.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/server.c b/app/src/server.c index 8453f959..e89a6f10 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -763,6 +763,17 @@ run_server(void *data) { } sc_mutex_unlock(&server->mutex); + // Interrupt sockets to wake up socket blocking calls on the server + assert(server->video_socket != SC_SOCKET_NONE); + net_interrupt(server->video_socket); + net_close(server->video_socket); + + if (server->control_socket != SC_SOCKET_NONE) { + // There is no control_socket if --no-control is set + net_interrupt(server->control_socket); + net_close(server->control_socket); + } + // Give some delay for the server to terminate properly #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; From 682a6911735cb8f6dccd9653ce30b72f267235c6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Dec 2021 23:33:59 +0100 Subject: [PATCH 0227/1133] Use timers with microsecond precision SDL only provides millisecond precision. Use system timers to get a better precision. --- app/src/util/tick.c | 59 +++++++++++++++++++++++++++++++++++++-------- app/src/util/tick.h | 2 ++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/app/src/util/tick.c b/app/src/util/tick.c index b85ce971..cc0bab5e 100644 --- a/app/src/util/tick.c +++ b/app/src/util/tick.c @@ -1,16 +1,55 @@ #include "tick.h" -#include +#include +#include +#ifdef _WIN32 +# include +#endif sc_tick sc_tick_now(void) { - // SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed - // in microseconds to store PTS without precision loss. - // - // As an alternative, SDL_GetPerformanceCounter() and - // SDL_GetPerformanceFrequency() could be used, but: - // - the conversions (avoiding overflow) are expansive, since the - // frequency is not known at compile time; - // - in practice, we don't need more precision for now. - return (sc_tick) SDL_GetTicks() * 1000; +#ifndef _WIN32 + // Maximum sc_tick precision (microsecond) + struct timespec ts; + int ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret) { + abort(); + } + + return SC_TICK_FROM_SEC(ts.tv_sec) + SC_TICK_FROM_NS(ts.tv_nsec); +#else + LARGE_INTEGER c; + + // On systems that run Windows XP or later, the function will always + // succeed and will thus never return zero. + // + // + + BOOL ok = QueryPerformanceCounter(&c); + assert(ok); + (void) ok; + + LONGLONG counter = c.QuadPart; + + static LONGLONG frequency; + if (!frequency) { + // Initialize on first call + LARGE_INTEGER f; + ok = QueryPerformanceFrequency(&f); + assert(ok); + frequency = f.QuadPart; + assert(frequency); + } + + if (frequency % SC_TICK_FREQ == 0) { + // Expected case (typically frequency = 10000000, i.e. 100ns precision) + sc_tick div = frequency / SC_TICK_FREQ; + return SC_TICK_FROM_US(counter / div); + } + + // Split the division to avoid overflow + sc_tick secs = SC_TICK_FROM_SEC(counter / frequency); + sc_tick subsec = SC_TICK_FREQ * (counter % frequency) / frequency; + return secs + subsec; +#endif } diff --git a/app/src/util/tick.h b/app/src/util/tick.h index 47d02529..2d941f23 100644 --- a/app/src/util/tick.h +++ b/app/src/util/tick.h @@ -10,9 +10,11 @@ typedef int64_t sc_tick; #define SC_TICK_FREQ 1000000 // microsecond // To be adapted if SC_TICK_FREQ changes +#define SC_TICK_TO_NS(tick) ((tick) * 1000) #define SC_TICK_TO_US(tick) (tick) #define SC_TICK_TO_MS(tick) ((tick) / 1000) #define SC_TICK_TO_SEC(tick) ((tick) / 1000000) +#define SC_TICK_FROM_NS(ns) ((ns) / 1000) #define SC_TICK_FROM_US(us) (us) #define SC_TICK_FROM_MS(ms) ((ms) * 1000) #define SC_TICK_FROM_SEC(sec) ((sec) * 1000000) From 09c55b0f93f1108d3829e9e4b22670b4ae685280 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Dec 2021 13:59:20 +0100 Subject: [PATCH 0228/1133] Set "low delay" decoder flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don't really know the concrete benefits, but scrcpy definitely wants low delay decoding. Suggested-by: François Cartegnie --- app/src/decoder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/decoder.c b/app/src/decoder.c index 7107e01d..a20986dd 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -46,6 +46,8 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { return false; } + decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; + if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { LOGE("Could not open codec"); avcodec_free_context(&decoder->codec_ctx); From 3ada5c51bc25cf4175d73ad69e4750ae7a81fe7e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 21:32:11 +0100 Subject: [PATCH 0229/1133] Rename scrcpy threads Prefix the name of threads by "scrcpy-". This improves readability in the output of `top -H` for example. Limit the thread names to 16 bytes, because it is limited on some platforms. --- app/src/aoa_hid.c | 2 +- app/src/controller.c | 2 +- app/src/file_handler.c | 2 +- app/src/fps_counter.c | 2 +- app/src/receiver.c | 4 ++-- app/src/recorder.c | 4 ++-- app/src/server.c | 3 ++- app/src/stream.c | 3 ++- app/src/util/process.c | 2 +- app/src/util/thread.c | 4 ++++ app/src/v4l2_sink.c | 2 +- app/src/video_buffer.c | 2 +- 12 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index a3fc3bd4..abf74cf9 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -373,7 +373,7 @@ bool sc_aoa_start(struct sc_aoa *aoa) { LOGD("Starting AOA thread"); - bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa); + bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa); if (!ok) { LOGC("Could not start AOA thread"); return false; diff --git a/app/src/controller.c b/app/src/controller.c index 6cf3d20e..10eceaf2 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -110,7 +110,7 @@ controller_start(struct controller *controller) { LOGD("Starting controller thread"); bool ok = sc_thread_create(&controller->thread, run_controller, - "controller", controller); + "scrcpy-ctl", controller); if (!ok) { LOGC("Could not start controller thread"); return false; diff --git a/app/src/file_handler.c b/app/src/file_handler.c index addbb9a5..95d230ae 100644 --- a/app/src/file_handler.c +++ b/app/src/file_handler.c @@ -154,7 +154,7 @@ file_handler_start(struct file_handler *file_handler) { LOGD("Starting file_handler thread"); bool ok = sc_thread_create(&file_handler->thread, run_file_handler, - "file_handler", file_handler); + "scrcpy-file", file_handler); if (!ok) { LOGC("Could not start file_handler thread"); return false; diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index c92d4140..25ee00eb 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -108,7 +108,7 @@ fps_counter_start(struct fps_counter *counter) { // same thread, no need to lock if (!counter->thread_started) { bool ok = sc_thread_create(&counter->thread, run_fps_counter, - "fps counter", counter); + "scrcpy-fps", counter); if (!ok) { LOGE("Could not start FPS counter thread"); return false; diff --git a/app/src/receiver.c b/app/src/receiver.c index eeb206f1..1e25536e 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -111,8 +111,8 @@ bool receiver_start(struct receiver *receiver) { LOGD("Starting receiver thread"); - bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver", - receiver); + bool ok = sc_thread_create(&receiver->thread, run_receiver, + "scrcpy-receiver", receiver); if (!ok) { LOGC("Could not start receiver thread"); return false; diff --git a/app/src/recorder.c b/app/src/recorder.c index 4364b9a5..74cfce07 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -287,8 +287,8 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { } LOGD("Starting recorder thread"); - ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", - recorder); + ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", + recorder); if (!ok) { LOGC("Could not start recorder thread"); goto error_avio_close; diff --git a/app/src/server.c b/app/src/server.c index e89a6f10..3b7a0fcc 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -804,7 +804,8 @@ error_connection_failed: bool sc_server_start(struct sc_server *server) { - bool ok = sc_thread_create(&server->thread, run_server, "server", server); + bool ok = + sc_thread_create(&server->thread, run_server, "scrcpy-server", server); if (!ok) { LOGE("Could not create server thread"); return false; diff --git a/app/src/stream.c b/app/src/stream.c index 3ac0f5e1..f8d73a27 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -284,7 +284,8 @@ bool stream_start(struct stream *stream) { LOGD("Starting stream thread"); - bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream); + bool ok = + sc_thread_create(&stream->thread, run_stream, "scrcpy-stream", stream); if (!ok) { LOGC("Could not start stream thread"); return false; diff --git a/app/src/util/process.c b/app/src/util/process.c index ad1af0a9..9c4dcd9f 100644 --- a/app/src/util/process.c +++ b/app/src/util/process.c @@ -64,7 +64,7 @@ sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid, observer->listener_userdata = listener_userdata; observer->terminated = false; - ok = sc_thread_create(&observer->thread, run_observer, "process_observer", + ok = sc_thread_create(&observer->thread, run_observer, "scrcpy-proc", observer); if (!ok) { sc_cond_destroy(&observer->cond_terminated); diff --git a/app/src/util/thread.c b/app/src/util/thread.c index 23eddf1d..c6e6b81e 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -8,6 +8,10 @@ bool sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, void *userdata) { + // The thread name length is limited on some systems. Never use a name + // longer than 16 bytes (including the final '\0') + assert(strlen(name) <= 15); + SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata); if (!sdl_thread) { LOG_OOM(); diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index dce11ce1..7675fd92 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -272,7 +272,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { vs->stopped = false; LOGD("Starting v4l2 thread"); - ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); + ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs); if (!ok) { LOGC("Could not start v4l2 thread"); goto error_av_packet_free; diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 12e66cf1..11f76479 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -170,7 +170,7 @@ bool sc_video_buffer_start(struct sc_video_buffer *vb) { if (vb->buffering_time) { bool ok = - sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb); + sc_thread_create(&vb->b.thread, run_buffering, "scrcpy-vbuf", vb); if (!ok) { LOGE("Could not start buffering thread"); return false; From b5d4ec61fcb82c88b7e25ad78651e070194c33f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 21:43:54 +0100 Subject: [PATCH 0230/1133] Move newline generation in help If we removed the shortcuts intro, we would not need the additional '\n' of the section title, so it should be printed along with the shortcuts intro. --- app/src/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 2ecf96f1..2281c255 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -776,7 +776,7 @@ print_shortcuts_intro(unsigned cols) { return; } - printf("%s\n", intro); + printf("\n%s\n", intro); free(intro); } @@ -831,7 +831,7 @@ scrcpy_print_usage(const char *arg0) { } // Print shortcuts section - printf("\nShortcuts:\n\n"); + printf("\nShortcuts:\n"); print_shortcuts_intro(cols); for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { print_shortcut(&shortcuts[i], cols); From f0361fc8b374c1827cedcabf3ec011ac78d1f72c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 21:45:39 +0100 Subject: [PATCH 0231/1133] Add environment variables in help Print the list of environment variables used by scrcpy in --help. --- app/src/cli.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 2281c255..f1f50049 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -71,6 +71,11 @@ struct sc_shortcut { const char *text; }; +struct sc_envvar { + const char *name; + const char *text; +}; + struct sc_getopt_adapter { char *optstring; struct option *longopts; @@ -585,6 +590,21 @@ static const struct sc_shortcut shortcuts[] = { }, }; +static const struct sc_envvar envvars[] = { + { + .name = "ADB", + .text = "Path to adb executable", + }, + { + .name = "SCRCPY_ICON_PATH", + .text = "Path to the program icon", + }, + { + .name = "SCRCPY_SERVER_PATH", + .text = "Path to the server binary", + } +}; + static char * sc_getopt_adapter_create_optstring(void) { struct sc_strbuf buf; @@ -804,6 +824,23 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { free(text); } +static void +print_envvar(const struct sc_envvar *envvar, unsigned cols) { + assert(cols > 8); // sc_str_wrap_lines() requires indent < columns + assert(envvar->name); + assert(envvar->text); + + printf("\n %s\n", envvar->name); + char *text = sc_str_wrap_lines(envvar->text, cols, 8); + if (!text) { + printf("\n"); + return; + } + + printf("%s\n", text); + free(text); +} + void scrcpy_print_usage(const char *arg0) { #define SC_TERM_COLS_DEFAULT 80 @@ -836,6 +873,12 @@ scrcpy_print_usage(const char *arg0) { for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { print_shortcut(&shortcuts[i], cols); } + + // Print environment variables section + printf("\nEnvironment variables:\n"); + for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) { + print_envvar(&envvars[i], cols); + } } static bool From 878ffffc36393bc544f4a8cb5a3345d6aab52a02 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 21:47:05 +0100 Subject: [PATCH 0232/1133] Update environment variables section in manpage Use the same content as the section printed with --help. --- app/scrcpy.1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 38c92d39..74d3957f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -413,15 +413,15 @@ Push file to device (see \fB\-\-push\-target\fR) .TP .B ADB -Specify the path to adb. +Path to adb. .TP -.B SCRCPY_SERVER_PATH -Specify the path to server binary. +.B SCRCPY_ICON_PATH +Path to the program icon. .TP -.B SCRCPY_ICON_PATH -Specify the path to the program icon. +.B SCRCPY_SERVER_PATH +Path to the server binary. .SH AUTHORS From 2cb4e04209e316ff849d4b48cbac1f38dbfc0035 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 23:36:10 +0100 Subject: [PATCH 0233/1133] Update copyright date to 2021 in manpage December, it's time to update to 2021! --- app/scrcpy.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 74d3957f..7a6c5134 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -446,7 +446,7 @@ Copyright \(co 2018 Genymobile Genymobile .UE -Copyright \(co 2018\-2020 +Copyright \(co 2018\-2021 .MT rom@rom1v.com Romain Vimont .ME From cfcbc2ac218612b2e9d2322ceb98ec99d7cd9381 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 22:58:31 +0100 Subject: [PATCH 0234/1133] Add icon to scrcpy.exe The icon will be associated to scrcpy.exe in the Windows explorer. The .ico was created using imagemagick: convert icon.png icon.ico It is included as a binary for simplicity. Refs #2815 --- app/meson.build | 2 ++ app/scrcpy-windows.rc | 1 + cross_win32.txt | 1 + cross_win64.txt | 1 + data/icon.ico | Bin 0 -> 8840 bytes 5 files changed, 5 insertions(+) create mode 100644 app/scrcpy-windows.rc create mode 100644 data/icon.ico diff --git a/app/meson.build b/app/meson.build index 3916b098..4c0e909d 100644 --- a/app/meson.build +++ b/app/meson.build @@ -49,9 +49,11 @@ conf.set('_XOPEN_SOURCE', '700') conf.set('_GNU_SOURCE', true) if host_machine.system() == 'windows' + windows = import('windows') src += [ 'src/sys/win/file.c', 'src/sys/win/process.c', + windows.compile_resources('scrcpy-windows.rc'), ] conf.set('_WIN32_WINNT', '0x0600') conf.set('WINVER', '0x0600') diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc new file mode 100644 index 00000000..0dadc84c --- /dev/null +++ b/app/scrcpy-windows.rc @@ -0,0 +1 @@ +0 ICON "../data/icon.ico" diff --git a/cross_win32.txt b/cross_win32.txt index 4db17be7..41ec3078 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -7,6 +7,7 @@ cpp = 'i686-w64-mingw32-g++' ar = 'i686-w64-mingw32-ar' strip = 'i686-w64-mingw32-strip' pkgconfig = 'i686-w64-mingw32-pkg-config' +windres = 'i686-w64-mingw32-windres' [host_machine] system = 'windows' diff --git a/cross_win64.txt b/cross_win64.txt index d03f0272..2565330b 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -7,6 +7,7 @@ cpp = 'x86_64-w64-mingw32-g++' ar = 'x86_64-w64-mingw32-ar' strip = 'x86_64-w64-mingw32-strip' pkgconfig = 'x86_64-w64-mingw32-pkg-config' +windres = 'x86_64-w64-mingw32-windres' [host_machine] system = 'windows' diff --git a/data/icon.ico b/data/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3238778fdb225a8df62c1334afb89d86f66d4a1 GIT binary patch literal 8840 zcmc(F^;=Y3wD1|aLu%+D1f)BqTaZraE@kMBp*vMXx>HJ|rKCeZB&EB%bMCy)_aA)s zhkJ5AYsXppti5^z00?pZcYpv0;6@Jsq=<5ihMFQS76lffh^wq5r}ba$e>Vmi;$`es zW(xrHBFb{oIzIo7mVA>L^|NVDPew*t*(%%aJN)l6zUKJz^X*F6^NSKfdMp{b!u1=w zZR-TP%)1qY4XV7_-&vB8uO%1=B01qUl3C-2*swE*NKIl?LCkF#^PP5E_`O#YmlRFM zgLS;G@^ii(?~e`-kFGGw@KofKkLu5VUJ1B6rv~W431l$-7mv}N!#RhGLO9cYRItW` z55Aqu)Nzc;ML>M7fCwsVLg>FMx<1OBs*P090~ZA z3upC&^Lf50NT;D)&x%<*>ACI0=qj9~>)A1oTP7=(FX)qUM{v+u5`5oV?_}g0*bUrV zSw@dm2mKt|4&xT(xKj(fHtK$b+X`lWDbx#C=An&(=FkG4SCPdnv&Vzj( zh0>0?D{z}69$gp-W)#5&UvwWJy;oq)3UybuCZoSotIS zG!!Y$Tp|u}`f#6k-JMS_L3xfD96j-vu56WKuT+qvgmAC`8CC0P5gTPdlVa7xDOZ=prcU$NkvC#{@;L7mr%<}k`EP4RKTfbauSsgk(`JUuoqd} zoYFaVnNu8u_)J#Uqln3Uu#}`NPPcs=>J6&MxASL+y!Otk7WsL&6X|Sz7=Xl3W31rD z3iakuCYct9s=b=w`~j_qf^gPgFqgOq2R>qPw(~i8^Kk6!1Idr{*TL2R_0yRy+>`*- z1CKwikSkHhtRuR9VBm<8w>-4_>1fW^NGQEa@9E)wn;UOYD13~=UHCb8Tt?fYE$%(} zY$J#pZSnFfzcS}>)JLFotV^Zs=hgFtwKLbj9!vwCXMlyt37DU@oXBfZcerQWA(X{@1s#3HW6R9JK3I&gQ>>Gy`aQBeWkuLZ=j|U4XNbjSGEXcE zm{nMqk%_)HAUcL_P^R_~x)n(f2|egtP~@C{;)#xFT$+h7khPy5pHu+75>I-qpYKYI zL_}cz!kRAHj}9l~^Dmk6nBB!m7&i3n!V{0`&M;7Rk~{*>6~qGnO`W(q9|&Zf&3B`I zRR|-v$p1?gl@tTO@+n2Ry&UoQ^m)C4i7i9>$5i0Oaz=;@3h7@zKh=Ed<-srt?#Oqt&rjbyupnYS`-4L z-L}@wh^1e+^aLWOccvE(3S?=QuDGAq6uBeo|3KNkWCyxv-L@Jgvq5jlAbo8uboz0jV9Y{;X-gIvlPoL^%!|d9v;d)l| za0~~1L6hPsr}t&jKYJMXA=i=tuEI8dIPzdymW7wBzjt8q*J*yQ{8hJ^riyL&UcdGW@K-a}7oNH81n^G_KGA6CUof z;jAUvA>L|SfTPfAo%RJur=-JXiXc`o8IJI2chIaCnk&%|mpQ!ssVkr|_#X?zK_>_< zaduAi11AC1rD!t-hj#~?q%+t79+FJeH748c70l>HEq1aiFRWK_f3P^GF)is|2isKI zYD!gG9gE6TiKqT54KH1wLz~U@#5T)QGjovATuCG~77>UWa{nA$en^0!avBwo{Og5W zj5IL{27TDLnd71k33cRHJ|&t*g6xQc#yLf-FYf9=V47v__Oj^Z2k5I#n%UHenX^IY zE5xMkf5jI{1iv(<1+bftsJ(@BE}uVXN{zC7YQ^R=Ul9+yy}MbiD5on3DZ}^Vu;|U` zNj^Zr7zUx((8>_fB1vP$>+1d4SY_GP-K=NDB1et*5vy7N+tk4uDyNFw`{w}uh!7A< z3r*klb9q^7p58wGTGEB1R^tPJQld5FCJKTd&gutGPm`P5i5f%+-vqpt=Hq5_{Abg~ zUwnG9-%tHBM=o#Tgi;qt@Cb4WO2EPCPNqd%_I@WU%Ee`L0-1_#^l@MR@Rx9vYAVeW zVYMCa8|q!rE=WyFx^pIND$S&gYktVV006KUZ=8#wSg(dn6Oz@t7bX`?NSTcS7MG*! z{p!r@T;7m%StB#?3_lxw&!OnS?gZ!*<1;M0#Ebmx5ii>Hr){l4TdeesPyLsD#QiI8 z^U|l;xfGp(sReO`zL+J|@LpS0U4|ysku6CQO(2bsd#$&X}lqo-%bbc1BUvOh&aecVxcguLjp+08Gf*vC3W zA`XqWnD?~Wjt}Ko_K`LAJ9(;0S(o+hjcJDdE|=YNo$3HU*KG>9oajHg$k~;4@3r!- zUsY1$jZE=5Uwd}Thb!U>)a7Qc(Jq~#3fe{suRajC?aykYk(xYmqfkk0~Fy z_}<7XmX?MUEIGU zj`J(vNaLtLt-*99WySmFPFIeY=+&rRITU({!g6_+?@BAAevR{-!h^Y_6$$YVbImb?tOd_(< zcv!ZVS{)G)fHfV4A%>LYtjHLS{a%%<3Y5BJU=nFy@_d;=YyRQPlyJf-z zWE4^D!}Xxr)8^8)Uq&JEnsPGZ`KIg~g5P%%O;ycQ3z|3eAj{<4i3@%+0qbnWY}uE7 zAYW6l?DwqQAnOGB?+Sz)fXpbmA&v}b=xM!E@KMG;o5g#7BObn#ZX+(UuEU7F@_8gW zO#hQzwobL(mcF$G5(GzMi=T`QNzDAJ2LYc%1Bw*BM}Md~)H9b+p%V@E#10RfbN8kV zy7>4vnjfWhV4%GOb z+a=I*me&i{s4?X4y!JllvVkEAE~eT2A~yFKeAgJEwTGK4_6ZJ$5#+n5Cwd4$ApyV= z@E@|Kv}Uti=*xG`s(B;g_75YE4VNmqbmJW{vviqwO6|I92;(Vcd0J8`;0RqsHo6zR z=|K!!Fq!6rWmF%Exa^fnSx$GV?E7YEGVxTr9R8d5vGPCd&Jj^RJf-V|f?!Ig^jn)> z$4Q2KEWq|&Y)BpM5*Evr$fin7oN%eCm5z$eYwFKm%8het2vs=_R{2WpQUpE?3l59) z=j0Ywz+dD*QH)vr%VE7@hEpfedY8}29Jnzt)zCjROc74a-^S?ZZwbj5(x<9dly@Cp zoLHx{DCxqBSpdCspO+y@>I5Bg_xK5*#1Ym8b|X3`md|E)pt4uEL^X_u&tQQlXOJHE z)SWaPXD-&Tw(!7QTs^l=#eIz&s5bVn7-@UCBtm-0^7shn`p0jjIctzmX^ZuGyffNzWJR$08_bKnTixYIRSxX z`(2)sjkR7_OYkf6W;lPQi^gFN7IE}WaYUO2ws9BdBE_4o)1poqANFU(9PrDfaipSZ zIv)KVelN`hs6;LGRge<{fZxpz^P8%X*{2KX@FY#tp}l~Mk6$?(Hnur0PY`Ccy+HQ` zXriSZVObp-2`c9H<7VlX=1;6r4~V+G`Y?Wqc&#RqBqp9d9s{BnX9q-}52; z@%-krN}q!bNSinBS%pQ*k@-iJ8<&1uVIY+;cX&`^dC~Q|vuUgKLbx?$N8)U(&Hv9- zhw$5`eq7_Ck1N`wGEzovm9rBR-N*iZLbbYf=Q@DLiG6R^*vz?8n7gr--52&4`JXau zQC*g&Hy@zVCaeBHrm@s=;0{@dkqw9K=f9zOO3*+^~)d)Mf?LE@uQqv7&W;456 z7GX*-aUa45A&e{QyB!ElUPQA^r#nvGVO5xxyKC4aI_pTnlfLw|Vk~1uGJnW-=KsN7 z{mfTrmpswl0%r8B`J~zPqfr>7%1+aN9hU7yR>^9-GAppmVP$_~*)$%nxj50A%Ekkr z%LED&&&N`RirW(xMq^5k?i(U7KdXE+e4YD5%~`%|0{i}AsRM11guMTuCu}!$-JXD< zm`GQB2M6X`}lFPMl^@m|#(;?OTadpKiy8lGUuneBC*Fw-);9fku~T_d@71DVI&xvp@Rj z>ZFSN-`j3U`3&a$ITavw#=c zPtAsJy<<{jIs5new`D(f#F^oaUMQkx)&pU;VYkxS*PYyz7czzB26*PTjm~ZOamB`z z0K!UrzuabUl{=+XB5?m?(L;UfxY6!(pUOGSsBo4mMpHy!&{u6vmUDO;$RB%<*%Af1 z4S0mmI0{?85s5W6YFs~EJr^z&mJ!<{1*lV724JAwtv+w5s}(wfl;#`d>_tFVv)BxKn3buT@Q=(UD0evQs;5tIc)k(A|SE75~vJ53ats|6|9yc<0Vh6|2M-Xn}Ez+?M{c0>Q_oJ!! zb?3^Ysj+RVPFA;%#n{vbnr!h8cXj2SHDfsfp8iA;DAmMO!(W zuny{CGlhPZca2$Js_lh&pVP`00xS-j^k_3OZXS0JN}Nw3&V=&VW>oh~H{Z2*&3Cqu zI2(u#pMfSs4e&)wDSnB3Sm$2(++4A7GWLj5VKMgQ;CIV~u&C>I)`7CLCqGM+n(=n0 z;YNPhftzs&{AxoXKd1$ZuP~|9L-IxLy08RrzjN~RSC4CZ2+X-vayE7MMd;_NY|7hy zF-hL`{K*($*ns+D&3XNG!tH}Q*P4vi@Z>qGiX#ZwKWl|&@DGR}TFL&P99hFp$Lb~1 zXS`{%oLip6v8;a+jWm*H{HB@W*xT-xYeJ|On8Fh7Xz(*{b*Q~q&3Fz6)N0M!=5xe+ zSVo%_M&Bs8zd4y=FtWdQBMcMASGF!7NV1zN1QRDCeKBjC`RMp*Q)!Nop!CkWc2R#cGvWIt4IErmbArbiiBc{X5)oODmDCBI zT@BanF0f0^nJ~fYRCXeOi6c|Q-*2uqwAyVZ9s<8Uj+=;YxDw1_XFM&oTIma>@GW`d z)4U!#tk-843^|7f`)@Meo}V%E=o%~(c2A1Kv3*YUSNgNGT3NCG01$%}Q6avSKi~W_ z>lHCY8&$Ls(YLyO0{&_St8RDrv=Xkx?%revv22eOl(v5**B=vZxvIAKr;)O%``!kg z6Sk4vEC1SmOE}2qnQZVNu7Q{di@=H)D4;?iLUn1Ma=BUDt@LqN{}51e0y#dIi#oaz zH)qROV8+7y$^DGh5qy1jvv{F$w*T$o3s0;@3mzgs0x#n)=Ek?QRTz{!0V=d9q)sJP9iE4x_AN#i_JzN^gm z>u#T7?eRWF8_+TG58fkG{XV?p@MLnul9kPAR!8dMVmzo;RZj9Q9<9Qw*bS+i4o6&@ zeYQF+Lw8ai_fK&;OV^67*|ZyTJk>i$p;ayD{Oawfnjy(J*~gUGK`XcDOT5f27Q@LsYHo7cqQGNkxjmvhy%OM z_XDIROZvpam4J}ve$OUB6;C>phs>_m%~(fQ+?rCgotRoC_9iDpk6L@)gqYtB0&NY` zT}lEczGjC~-MxLkSA~w=WF5Zz4pW*oH0<|jQ(sq+6Fv^1Oc08oKw9;q$4am#Qdp;0 zVpoX*M!u7znO!PIun|sox2+Lvsck%xhjU(wQDYSjWqx zyh(*qJZ1R_NL_AMzX^>f;TVQ*`&sChNnIB=OcYV2$~a->y^0_yN3|K2Uu4M;p}7m; z?^=miFdri@h0rkHj~7~OI@PipqWkZ%rs)PXK^n7E3OPn%hhLK4*x;hp+1gbx9+m;g zlrmHI>W+G8WQ0z}X6g!UBAmc^WW?Vf!tWqo*@#6ckoi|caQn}&FC1eYh;YJ?&lVW@ zXob{8MqqKv`jtC2d0$h0NmC_CgbASc1QGAaHd$S4N3#LZQRHh>fi@*j z{-LiRa^ z>HwR}#PPIWQ9aQh4gDG-oRVU=92?&v9d|Ab(!cmHhplJDUd!TQ@C#SYlDL*GZqfIf zW`(4B%nacRb0eZVlJ9z3O8W`-@F0T3(zVn0x$M%XB2K($>#Ba;u++(9S`LUOZ`5Ov zyJFcpxBe_)*Y(mmgF{(csJE>xTB}+TlJe2{T|o4}Z!-qS=QrYQ9~K(%GuurR8$jVb zV;S#5eAF3bEB2J?Mu0s9bfZrv|2zq8Ax z+1O{G^>DLbDCu(dLt+c8)bER_D1i})@XgpHX{BkAYFrqBB@H6M@;V*~Mu1<>3X};X z61W?>sTsZiVhctM{n;_j(k$zQt@^w;Fkt}_k`6~}kY1y2qNVJZu#}V(s~H+RnV(ym zLmw5Z=Bi&7f-*BRqY|mbt|^sMM`=mrqX+CW1pQaLLNZHAO4KsjKd3kY+S=Op<=Uk| z=1=)_y-x}X3eOKyQ=eXLtXBD98FRA;AkENcW=V)YLOP39T(XruP+bl$JIbeNJ^RQ_?1XfNCd%KpY?6; z@9&rAfBIgaqe$n4JcfTuDRC$-%?-L=Q>5|TJi4QO3t~xcr$w|!oxp;L-z>W8V@3Q* zY4^Wz#zjH;Kzfu!FsoFoRTF%Xx$<~!l_W063unMvrDxy8vEE|O2m~n7o>%=>9+tZU zAFcy`baXr`R1gw;ALNc-UtC;-{`W6BC2v~B+kdXt*a2BhmYsTNFxsxf>FYY1k+jvP zo0Sz2Vc|K77&*tF0?B@`)BE?21rUrQuP+hCxzz_m_4V~D+j!{^`fRnG<-nVz7`q(b z?ct%1%=(qo?jnn%f?(* z*=ox`g%ll^#av6|7$|}ZCV1Z^f;v0l`B5~Y*4`ql)_ERVot9UL6`zx}fn|kqh1GNj zMpVzQ)6-MCBTn@4(U~eE$&=c5fZ`sNFFL8&9obx6S{eof4HB*;Yw`OzG{2LI;f z2Hvo=1^Fx%cz;Po8+5yt`_ClNiCe>Qbp%kFvatXCuUcmpVVc~3JfELJpbgn&Wm^FO}Uy3NxB?c;m18!>v!T!C+ZUl&A zEBx?ut!FH^cLfPiwR>^`XM|ZGX0KY`Qd4sRYQxry`!A%?sr6WgKxo@C{%J3TLD3%d zD+vM&27_<;VO3sV!qS~tNaF%0k7cG1v5J>?v=Z!YW1Y_4BGCfEJHavyuq_rKZTH~d z0SCr`Xl-E2LXi;k57GJs7R>j80}fwCAmhZ-Pm}+yiyi?&Mn;C?cwF6tN*kt0T6xns zqF$k(xNHs*DyMA`0?3G!pwl<+A=-X5kuRIEgjz>A4FJ8Cb?PB%L0sg?Hec^iVMvG| zu?ReN$p)f(BQVyoNCXR)^Cm!>CNNQ`v2Kxr4~*TRwv9*~c8efZ68V3F z3|jLJ9C9MpDGFwaJN`eAu-D<-_cq5so{ zS6UCM+h}xjuRPmEESbk=0RTht`78-O$&K^(iz!pX5O@6JT2B;QMpt*-3~(rr%e&kC zQyA0T-Tg6B@PlYCxG=wf(-y4o+(%~iSO^W+poHfAh<_?tviVMg3J{o>li|Gue=5rl#PBX>qAO4x+1} z9yTP$g@&f{9(f9_7Lufe3$gT;R?<~P0yni0G{BI|HD45OeYMf}Jy<0S`*R1!*p2JR zAb<%1t`ldj*oj(Ba4@icQil@QU89CVfJ~sUcMTJLD-su?C2NK!g#~l=4*CDte1QU| ZrP6;PhFmM2h1g31D9fwKRm+%%{tsGWu_6Ef literal 0 HcmV?d00001 From 29570ee819c7d08e9c17b3e66b8d8ad3ea735417 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Dec 2021 23:04:40 +0100 Subject: [PATCH 0235/1133] Add metadata to scrcpy.exe for Windows Refs --- app/scrcpy-windows.rc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 0dadc84c..8e3a585e 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -1 +1,20 @@ 0 ICON "../data/icon.ico" +1 VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "FileDescription", "Display and control your Android device" + VALUE "InternalName", "scrcpy" + VALUE "LegalCopyright", "Romain Vimont, Genymobile" + VALUE "OriginalFilename", "scrcpy.exe" + VALUE "ProductName", "scrcpy" + VALUE "ProductVersion", "1.21" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END From 892cfe943ee5cf2001c74a1d39ed8496ba5272db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Dec 2021 19:50:17 +0100 Subject: [PATCH 0236/1133] Add script to bump version The version must now be bumped at 4 different places. Add a script to bump automatically: ./bump_version 1.23.4 --- bump_version | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 bump_version diff --git a/bump_version b/bump_version new file mode 100755 index 00000000..a0963666 --- /dev/null +++ b/bump_version @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# +# This script bump scrcpy version by editing all the necessary files. +# +# Usage: +# +# ./bump_version 1.23.4 +# +# Then check the diff manually to confirm that everything is ok. + +set -e + +if [[ $# != 1 ]] +then + echo "Syntax: $0 " >&2 + exit 1 +fi + +VERSION="$1" + +a=( ${VERSION//./ } ) +MAJOR="${a[0]:-0}" +MINOR="${a[1]:-0}" +PATCH="${a[2]:-0}" + +# If VERSION is 1.23.4, then VERSION_CODE is 12304 +VERSION_CODE="$(( $MAJOR * 10000 + $MINOR * 100 + "$PATCH" ))" + +echo "$VERSION: major=$MAJOR minor=$MINOR patch=$PATCH [versionCode=$VERSION_CODE]" +sed -i "s/^\(\s*version: \)'[^']*'/\1'$VERSION'/" meson.build +sed -i "s/^\(\s*versionCode \).*/\1$VERSION_CODE/;s/^\(\s*versionName \).*/\1\"$VERSION\"/" server/build.gradle +sed -i "s/^\(SCRCPY_VERSION_NAME=\).*/\1$VERSION/" server/build_without_gradle.sh +sed -i "s/^\(\s*VALUE \"ProductVersion\", \)\"[^\"]*\"/\1\"$VERSION\"/" app/scrcpy-windows.rc +echo done From 0685c491cd8f680b40e733adbbc367b48ec1801e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Dec 2021 19:50:58 +0100 Subject: [PATCH 0237/1133] Improve crossbuild configuration Use meson native features to detect crossbuild, and remove the user-provided option crossbuild_windows. --- app/meson.build | 4 +++- meson_options.txt | 1 - release.mk | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/meson.build b/app/meson.build index 4c0e909d..38393f0c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -82,7 +82,9 @@ endif cc = meson.get_compiler('c') -if not get_option('crossbuild_windows') +crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows' + +if not crossbuild_windows # native build dependencies = [ diff --git a/meson_options.txt b/meson_options.txt index 66ad5b25..d64e357f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,5 @@ option('compile_app', type: 'boolean', value: true, description: 'Build the client') option('compile_server', type: 'boolean', value: true, description: 'Build the server') -option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') diff --git a/release.mk b/release.mk index 05ddbe46..cb6c9b2e 100644 --- a/release.mk +++ b/release.mk @@ -70,7 +70,6 @@ build-win32: prepare-deps-win32 meson "$(WIN32_BUILD_DIR)" \ --cross-file cross_win32.txt \ --buildtype release --strip -Db_lto=true \ - -Dcrossbuild_windows=true \ -Dcompile_server=false \ -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" @@ -83,7 +82,6 @@ build-win64: prepare-deps-win64 meson "$(WIN64_BUILD_DIR)" \ --cross-file cross_win64.txt \ --buildtype release --strip -Db_lto=true \ - -Dcrossbuild_windows=true \ -Dcompile_server=false \ -Dportable=true ) ninja -C "$(WIN64_BUILD_DIR)" From d0496719080ccf9ee00fccf6cb0765e31765529f Mon Sep 17 00:00:00 2001 From: Chih-Hsuan Yen Date: Sun, 5 Dec 2021 12:58:02 +0800 Subject: [PATCH 0238/1133] Fix adb server hang Since commit 04267085441d6fcd05eff7df0118708f7622e237, the server is run in a dedicated thread. For SDL, many signals, including SIGINT and SIGTERM, are masked for new threads. As a result, if the adb server is not already running, adb commands invoked by scrcpy will start an adb server that ignores those signals and cannot be terminated at system shutdown. Fixes #2873 PR #2870 Signed-off-by: Romain Vimont --- app/src/sys/unix/process.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index 54a1bb80..cb82f3a9 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -119,6 +119,13 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, close(internal[0]); enum sc_process_result err; + + // Somehow SDL masks many signals - undo them for other processes + // https://github.com/libsdl-org/SDL/blob/release-2.0.18/src/thread/pthread/SDL_systhread.c#L167 + sigset_t mask; + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { execvp(argv[0], (char *const *) argv); perror("exec"); From feb250a9738ce80e27d8e82e87d08c4038670993 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Dec 2021 18:27:45 +0100 Subject: [PATCH 0239/1133] Fix typos reported by codespell --- app/src/server.c | 2 +- app/src/util/str.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 3b7a0fcc..ab5439ad 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -232,7 +232,7 @@ execute_server(struct sc_server *server, ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); } if (!params->clipboard_autosync) { - // By defaut, clipboard_autosync is true + // By default, clipboard_autosync is true ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); } diff --git a/app/src/util/str.c b/app/src/util/str.c index 70e3f1de..2d67f816 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -240,7 +240,7 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) { APPEND_INDENT(); - // The last separator encountered, it must be inserted only conditionnaly, + // The last separator encountered, it must be inserted only conditionally, // depending on the next token char pending = 0; From 25a41359358a2032fdcc09482974d5fd15a06511 Mon Sep 17 00:00:00 2001 From: Thomas Van Machelen Date: Tue, 14 Dec 2021 11:26:46 +0100 Subject: [PATCH 0240/1133] Mention react-native menu shortcut in README PR #2879 Signed-off-by: Romain Vimont --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 201141a8..c4b25f25 100644 --- a/README.md +++ b/README.md @@ -938,7 +938,7 @@ _[Super] is typically the Windows or Cmd key._ | Click on `HOME` | MOD+h \| _Middle-click_ | Click on `BACK` | MOD+b \| _Right-click²_ | Click on `APP_SWITCH` | MOD+s \| _4th-click³_ - | Click on `MENU` (unlock screen) | MOD+m + | Click on `MENU` (unlock screen)⁴ | MOD+m | Click on `VOLUME_UP` | MOD+ _(up)_ | Click on `VOLUME_DOWN` | MOD+ _(down)_ | Click on `POWER` | MOD+p @@ -949,9 +949,9 @@ _[Super] is typically the Windows or Cmd key._ | Expand notification panel | MOD+n \| _5th-click³_ | Expand settings panel | MOD+n+n \| _Double-5th-click³_ | Collapse panels | MOD+Shift+n - | Copy to clipboard⁴ | MOD+c - | Cut to clipboard⁴ | MOD+x - | Synchronize clipboards and paste⁴ | MOD+v + | Copy to clipboard⁵ | MOD+c + | Cut to clipboard⁵ | MOD+x + | Synchronize clipboards and paste⁵ | MOD+v | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom | Ctrl+_click-and-move_ @@ -961,7 +961,8 @@ _[Super] is typically the Windows or Cmd key._ _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ _³4th and 5th mouse buttons, if your mouse has them._ -_⁴Only on Android >= 7._ +_⁴For react-native apps in development, `MENU` triggers development menu._ +_⁵Only on Android >= 7._ Shortcuts with repeated keys are executted by releasing and pressing the key a second time. For example, to execute "Expand settings panel": From ad11c5babb0ff89647dd606392ed7907a20277c7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Dec 2021 17:12:46 +0100 Subject: [PATCH 0241/1133] Set DPI awareness for Windows Add a windows manifest to set the DPI awareness by default: Refs #40 Fixes #2865 --- app/scrcpy-windows.manifest | 9 +++++++++ app/scrcpy-windows.rc | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 app/scrcpy-windows.manifest diff --git a/app/scrcpy-windows.manifest b/app/scrcpy-windows.manifest new file mode 100644 index 00000000..f2708ecb --- /dev/null +++ b/app/scrcpy-windows.manifest @@ -0,0 +1,9 @@ + + + + + true + PerMonitorV2 + + + diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 8e3a585e..7525d092 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -1,5 +1,8 @@ +#include + 0 ICON "../data/icon.ico" -1 VERSIONINFO +1 RT_MANIFEST "scrcpy-windows.manifest" +2 VERSIONINFO BEGIN BLOCK "StringFileInfo" BEGIN From 720c3064dfd54c67a58df7aa8e94f12512607f37 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Dec 2021 17:35:08 +0100 Subject: [PATCH 0242/1133] Upgrade SDL (2.0.18) for Windows Include the latest version of SDL in Windows releases. --- cross_win32.txt | 2 +- cross_win64.txt | 2 +- prebuilt-deps/Makefile | 6 +++--- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index 41ec3078..b0e43622 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -18,4 +18,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' -prebuilt_sdl2 = 'SDL2-2.0.16/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 2565330b..6625c0cf 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -18,4 +18,4 @@ endian = 'little' [properties] prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' -prebuilt_sdl2 = 'SDL2-2.0.16/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index dced047c..fa986978 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -30,9 +30,9 @@ prepare-ffmpeg-dev-win64: ffmpeg-4.3.1-win64-dev prepare-sdl2: - @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.16-mingw.tar.gz \ - 2bfe48628aa9635c12eac7d421907e291525de1d0b04b3bca4a5bd6e6c881a6f \ - SDL2-2.0.16 + @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \ + bbad7c6947f6ca3e05292f065852ed8b62f319fc5533047e7708769c4dbae394 \ + SDL2-2.0.18 prepare-adb: @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ diff --git a/release.mk b/release.mk index cb6c9b2e..6414f079 100644 --- a/release.mk +++ b/release.mk @@ -101,7 +101,7 @@ dist-win32: build-server build-win32 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.16/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.18/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -118,7 +118,7 @@ dist-win64: build-server build-win64 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.16/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.18/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From 2f091beeaadf991245bcdc1ce9eb5722def7f622 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Dec 2021 19:21:19 +0100 Subject: [PATCH 0243/1133] Simplify sc_size assignment Assign the whole struct instead of each field separately. --- app/src/screen.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index c72fdf44..e35641f7 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -101,8 +101,7 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) { struct sc_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; - window_size.height = current_size.height; + window_size = current_size; } else { window_size.width = MIN(current_size.width, display_size.width); window_size.height = MIN(current_size.height, display_size.height); From 6261bb0b5a02a9ec0cea81ff3feff047bb94ebbb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Dec 2021 19:17:15 +0100 Subject: [PATCH 0244/1133] Ignore display bounds on resize-to-fit The "resize to fit" feature (MOD+w or double-click on black borders) computed the "optimal size" using the same function computing the initial window size on start. However, on "resize to fit", only the black borders must be removed (the content size must be preserved), so the display bounds must not be considered. --- app/src/screen.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index e35641f7..0cb09a4b 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -90,7 +90,8 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) { // - 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) { +get_optimal_size(struct sc_size current_size, struct sc_size content_size, + bool within_display_bounds) { if (content_size.width == 0 || content_size.height == 0) { // avoid division by 0 return current_size; @@ -99,8 +100,9 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) { struct sc_size window_size; struct sc_size display_size; - if (!get_preferred_display_bounds(&display_size)) { - // could not get display bounds, do not constraint the size + if (!within_display_bounds || + !get_preferred_display_bounds(&display_size)) { + // do not constraint the size window_size = current_size; } else { window_size.width = MIN(current_size.width, display_size.width); @@ -134,7 +136,7 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, uint16_t req_height) { struct sc_size window_size; if (!req_width && !req_height) { - window_size = get_optimal_size(content_size, content_size); + window_size = get_optimal_size(content_size, content_size, true); } else { if (req_width) { window_size.width = req_width; @@ -559,7 +561,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size, .height = (uint32_t) window_size.height * new_content_size.height / old_content_size.height, }; - target_size = get_optimal_size(target_size, new_content_size); + target_size = get_optimal_size(target_size, new_content_size, true); set_window_size(screen, target_size); } @@ -694,7 +696,7 @@ screen_resize_to_fit(struct screen *screen) { struct sc_size window_size = get_window_size(screen); struct sc_size optimal_size = - get_optimal_size(window_size, screen->content_size); + get_optimal_size(window_size, screen->content_size, false); // Center the window related to the device screen assert(optimal_size.width <= window_size.width); From 826ddf1a6ee7784e1f72ca6202ebf2cd51b335ef Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Dec 2021 12:16:20 +0100 Subject: [PATCH 0245/1133] Document HID keyboard events --- app/src/hid_keyboard.c | 76 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 20fe8d51..cf8eab61 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -126,6 +126,80 @@ static const unsigned char keyboard_report_desc[] = { 0xC0 }; +/** + * A keyboard HID event is 8 bytes long: + * + * - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys) + * - byte 1: reserved (always 0) + * - bytes 2 to 7: pressed keys (6 at most) + * + * 7 6 5 4 3 2 1 0 + * +---------------+ + * byte 0: |. . . . . . . .| modifiers + * +---------------+ + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | `- left Ctrl + * | | | | | | `--- left Shift + * | | | | | `----- left Alt + * | | | | `------- left Gui + * | | | `--------- right Ctrl + * | | `----------- right Shift + * | `------------- right Alt + * `--------------- right Gui + * + * +---------------+ + * byte 1: |0 0 0 0 0 0 0 0| reserved + * +---------------+ + * + * +---------------+ + * bytes 2 to 7: |. . . . . . . .| scancode of 1st key pressed + * +---------------+ + * |. . . . . . . .| scancode of 2nd key pressed + * +---------------+ + * |. . . . . . . .| scancode of 3rd key pressed + * +---------------+ + * |. . . . . . . .| scancode of 4th key pressed + * +---------------+ + * |. . . . . . . .| scancode of 5th key pressed + * +---------------+ + * |. . . . . . . .| scancode of 6th key pressed + * +---------------+ + * + * If there are less than 6 keys pressed, the last items are set to 0. + * For example, if A and W are pressed: + * + * +---------------+ + * bytes 2 to 7: |0 0 0 0 0 1 0 0| A is pressed (scancode = 4) + * +---------------+ + * |0 0 0 1 1 0 1 0| W is pressed (scancode = 26) + * +---------------+ + * |0 0 0 0 0 0 0 0| ^ + * +---------------+ | only 2 keys are pressed, the + * |0 0 0 0 0 0 0 0| | remaining items are set to 0 + * +---------------+ | + * |0 0 0 0 0 0 0 0| | + * +---------------+ | + * |0 0 0 0 0 0 0 0| v + * +---------------+ + * + * Pressing more than 6 keys is not supported. If this happens (typically, + * never in practice), report a "phantom state": + * + * +---------------+ + * bytes 2 to 7: |0 0 0 0 0 0 0 1| ^ + * +---------------+ | + * |0 0 0 0 0 0 0 1| | more than 6 keys pressed: + * +---------------+ | the list is filled with a special + * |0 0 0 0 0 0 0 1| | rollover error code (0x01) + * +---------------+ | + * |0 0 0 0 0 0 0 1| | + * +---------------+ | + * |0 0 0 0 0 0 0 1| | + * +---------------+ | + * |0 0 0 0 0 0 0 1| v + * +---------------+ + */ + static unsigned char sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { unsigned char modifiers = HID_MODIFIER_NONE; @@ -217,7 +291,7 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, // 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: + // Phantom state: // - Modifiers // - Reserved // - ErrorRollOver * HID_MAX_KEYS From 1fbc590b26aa0f9b0144c4bbf99a0bf52fe31855 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Dec 2021 10:49:22 +0100 Subject: [PATCH 0246/1133] Fix memory leaks in tests Tests were failing when run with ASAN enabled. --- app/tests/test_adb_parser.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index fbc65649..cb3abb0e 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -11,6 +11,7 @@ static void test_get_ip_single_line() { char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); assert(ip); assert(!strcmp(ip, "192.168.12.34")); + free(ip); } static void test_get_ip_single_line_without_eol() { @@ -20,6 +21,7 @@ static void test_get_ip_single_line_without_eol() { char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); assert(ip); assert(!strcmp(ip, "192.168.12.34")); + free(ip); } static void test_get_ip_single_line_with_trailing_space() { @@ -29,6 +31,7 @@ static void test_get_ip_single_line_with_trailing_space() { char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); assert(ip); assert(!strcmp(ip, "192.168.12.34")); + free(ip); } static void test_get_ip_multiline_first_ok() { @@ -40,6 +43,7 @@ static void test_get_ip_multiline_first_ok() { char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); assert(ip); assert(!strcmp(ip, "192.168.1.2")); + free(ip); } static void test_get_ip_multiline_second_ok() { @@ -51,6 +55,7 @@ static void test_get_ip_multiline_second_ok() { char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); assert(ip); assert(!strcmp(ip, "192.168.1.3")); + free(ip); } static void test_get_ip_no_wlan() { From 6b9f39773396c17124fd7411c0b38a4b7d96fa28 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 17:20:36 +0100 Subject: [PATCH 0247/1133] Happy new year 2022! --- LICENSE | 2 +- README.id.md | 2 +- README.it.md | 2 +- README.jp.md | 2 +- README.ko.md | 2 +- README.md | 2 +- README.pt-br.md | 2 +- README.sp.md | 2 +- README.tr.md | 2 +- README.zh-Hans.md | 2 +- README.zh-Hant.md | 2 +- app/scrcpy.1 | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/LICENSE b/LICENSE index b320f699..bea74a6b 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ identification within third-party archives. Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.id.md b/README.id.md index b4b16735..9657b95a 100644 --- a/README.id.md +++ b/README.id.md @@ -672,7 +672,7 @@ Baca [halaman pengembang]. ## Lisensi Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.it.md b/README.it.md index 6b5d6884..52e68ba3 100644 --- a/README.it.md +++ b/README.it.md @@ -790,7 +790,7 @@ Leggi la [pagina per sviluppatori]. ## Licenza (in inglese) Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.jp.md b/README.jp.md index a97ef765..583582fd 100644 --- a/README.jp.md +++ b/README.jp.md @@ -776,7 +776,7 @@ _⁴Android 7以上のみ._ ## ライセンス Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.ko.md b/README.ko.md index 31e38c6f..a77cde48 100644 --- a/README.ko.md +++ b/README.ko.md @@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상 ## 라이선스 Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index d6908bd6..8a177406 100644 --- a/README.md +++ b/README.md @@ -1017,7 +1017,7 @@ Read the [developers page]. ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.pt-br.md b/README.pt-br.md index cdfeafeb..cc7e5f0b 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page]. ## Licença Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.sp.md b/README.sp.md index 6f76a7be..05c14533 100644 --- a/README.sp.md +++ b/README.sp.md @@ -720,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md). ## Licencia Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.tr.md b/README.tr.md index 15c56b27..9501a889 100644 --- a/README.tr.md +++ b/README.tr.md @@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md). ## Lisans Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.zh-Hans.md b/README.zh-Hans.md index b96d6d5a..61bb2842 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -842,7 +842,7 @@ ADB=/path/to/adb scrcpy ## 许可协议 Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.zh-Hant.md b/README.zh-Hant.md index c0e30254..87c0a8dd 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -679,7 +679,7 @@ _³只支援 Android 7+。_ ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2021 Romain Vimont + Copyright (C) 2018-2022 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7a6c5134..971c8a19 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -446,7 +446,7 @@ Copyright \(co 2018 Genymobile Genymobile .UE -Copyright \(co 2018\-2021 +Copyright \(co 2018\-2022 .MT rom@rom1v.com Romain Vimont .ME From 37124e14527192799e15af664186d1504bbc68ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 23:27:34 +0100 Subject: [PATCH 0248/1133] Avoid unused function warning If HAVE_SOCK_CLOEXEC is not defined, then sc_raw_socket_close() is never used. Add an #ifndef block to remove the warning. --- app/src/util/net.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/util/net.c b/app/src/util/net.c index ec678d2e..565db2e9 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -83,6 +83,7 @@ unwrap(sc_socket socket) { #endif } +#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning static inline bool sc_raw_socket_close(sc_raw_socket raw_sock) { #ifndef _WIN32 @@ -91,6 +92,7 @@ sc_raw_socket_close(sc_raw_socket raw_sock) { return !closesocket(raw_sock); #endif } +#endif #ifndef HAVE_SOCK_CLOEXEC // If SOCK_CLOEXEC does not exist, the flag must be set manually once the From ba28d817fbb38725cd5bcc5fe3c3c4d45ddfeace Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 17:28:27 +0100 Subject: [PATCH 0249/1133] Fail on unsupported HID option If the feature is not supported on the platform, fail during command line parsing instead of using a fallback. --- app/src/cli.c | 6 ++++++ app/src/scrcpy.c | 9 +++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index f1f50049..b420996c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1300,7 +1300,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], args->help = true; break; case 'K': +#ifdef HAVE_AOA_HID opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; +#else + LOGE("HID over AOA (-K/--hid-keyboard) is not supported on " + "this platform. It is only available on Linux."); + return false; +#endif break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9c2bd03b..3a271ee5 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -534,8 +534,8 @@ scrcpy(struct scrcpy_options *options) { struct sc_mouse_processor *mp = NULL; if (options->control) { - if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { #ifdef HAVE_AOA_HID + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { bool aoa_hid_ok = false; bool ok = sc_aoa_init(&s->aoa, serial, acksync); @@ -566,13 +566,10 @@ aoa_hid_end: "(-K/--hid-keyboard 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)"); - options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; + assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID); #endif - } // keyboard_input_mode may have been reset if HID mode failed if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) { From 26ee7ce56638788a82f69a0dc4cf93f41575be71 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 17:34:49 +0100 Subject: [PATCH 0250/1133] Expose V4L2 option on all platforms This allows to report a meaningful error message if an unsupported feature is used on an incompatible platform. This is consistent with the behavior of -K/--hid-keyboard. --- app/src/cli.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index b420996c..5b727fff 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -240,11 +240,8 @@ static const struct sc_option options[] = { { .shortopt = 'N', .longopt = "no-display", - .text = "Do not display device (only when screen recording " -#ifdef HAVE_V4L2 - "or V4L2 sink " -#endif - "is enabled).", + .text = "Do not display device (only when screen recording or V4L2 " + "sink is enabled).", }, { .longopt_id = OPT_NO_KEY_REPEAT, @@ -381,14 +378,14 @@ static const struct sc_option options[] = { "Default is 0 (not forced): the local port used for " "establishing the tunnel will be used.", }, -#ifdef HAVE_V4L2 { .longopt_id = OPT_V4L2_SINK, .longopt = "v4l2-sink", .argdesc = "/dev/videoN", .text = "Output to v4l2loopback device.\n" "It requires to lock the video orientation (see " - "--lock-video-orientation).", + "--lock-video-orientation).\n" + "This feature is only available on Linux.", }, { .longopt_id = OPT_V4L2_BUFFER, @@ -398,9 +395,9 @@ static const struct sc_option options[] = { "frames. This increases latency to compensate for jitter.\n" "This option is similar to --display-buffer, but specific to " "V4L2 sink.\n" - "Default is 0 (no buffering).", + "Default is 0 (no buffering).\n" + "This option is only available on Linux.", }, -#endif { .shortopt = 'V', .longopt = "verbosity", @@ -1470,16 +1467,24 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->tcpip = true; opts->tcpip_dst = optarg; break; -#ifdef HAVE_V4L2 case OPT_V4L2_SINK: +#ifdef HAVE_V4L2 opts->v4l2_device = optarg; +#else + LOGE("V4L2 (--v4l2-sink) is only available on Linux."); + return false; +#endif break; case OPT_V4L2_BUFFER: +#ifdef HAVE_V4L2 if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) { return false; } - break; +#else + LOGE("V4L2 (--v4l2-buffer) is only available on Linux."); + return false; #endif + break; default: // getopt prints the error message on stderr return false; From cd5891fee6de32a79fe76b6269bd5fe8da0b7972 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 01:34:54 +0100 Subject: [PATCH 0251/1133] Remove actions bitset The input manager exposed functions taking an "actions" parameter, containing a bitmask-OR of ACTION_UP and ACTION_DOWN. But they are never called with both actions simultaneously anymore, so simplify. Refs 964b6d2243fa1921543e48810f3064b9bd2d50d1 Refs d0739911a3e413b70275ded3eef839f9dc57ba7a --- app/src/input_manager.c | 79 +++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 9a553842..7230414a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -5,8 +5,10 @@ #include "util/log.h" -static const int ACTION_DOWN = 1; -static const int ACTION_UP = 1 << 1; +enum sc_action { + SC_ACTION_DOWN, + SC_ACTION_UP, +}; #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) @@ -89,85 +91,70 @@ input_manager_init(struct input_manager *im, struct controller *controller, static void send_keycode(struct controller *controller, enum android_keycode keycode, - int actions, const char *name) { + enum sc_action action, const char *name) { // send DOWN event struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + msg.inject_keycode.action = action == SC_ACTION_DOWN + ? AKEY_EVENT_ACTION_DOWN + : AKEY_EVENT_ACTION_UP; msg.inject_keycode.keycode = keycode; msg.inject_keycode.metastate = 0; msg.inject_keycode.repeat = 0; - if (actions & ACTION_DOWN) { - msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN; - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'inject %s (DOWN)'", name); - return; - } - } - - if (actions & ACTION_UP) { - msg.inject_keycode.action = AKEY_EVENT_ACTION_UP; - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'inject %s (UP)'", name); - } + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'inject %s'", name); } } static inline void -action_home(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); +action_home(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_HOME, action, "HOME"); } static inline void -action_back(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); +action_back(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_BACK, action, "BACK"); } static inline void -action_app_switch(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); +action_app_switch(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH"); } static inline void -action_power(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); +action_power(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_POWER, action, "POWER"); } static inline void -action_volume_up(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); +action_volume_up(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP"); } static inline void -action_volume_down(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); +action_volume_down(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN"); } static inline void -action_menu(struct controller *controller, int actions) { - send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); +action_menu(struct controller *controller, enum sc_action action) { + send_keycode(controller, AKEYCODE_MENU, action, "MENU"); } // turn the screen on if it was off, press BACK otherwise // If the screen is off, it is turned on only on ACTION_DOWN static void -press_back_or_turn_screen_on(struct controller *controller, int actions) { +press_back_or_turn_screen_on(struct controller *controller, + enum sc_action action) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; + msg.back_or_screen_on.action = action == SC_ACTION_DOWN + ? AKEY_EVENT_ACTION_DOWN + : AKEY_EVENT_ACTION_UP; - if (actions & ACTION_DOWN) { - msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN; - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'press back or turn screen on'"); - return; - } - } - - if (actions & ACTION_UP) { - msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP; - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'press back or turn screen on'"); - } + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'press back or turn screen on'"); } } @@ -396,7 +383,7 @@ input_manager_process_key(struct input_manager *im, // The shortcut modifier is pressed if (smod) { - int action = down ? ACTION_DOWN : ACTION_UP; + enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: if (control && !shift && !repeat) { @@ -601,7 +588,7 @@ input_manager_process_mouse_button(struct input_manager *im, bool down = event->type == SDL_MOUSEBUTTONDOWN; if (!im->forward_all_clicks) { - int action = down ? ACTION_DOWN : ACTION_UP; + enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; if (control && event->button == SDL_BUTTON_X1) { action_app_switch(im->controller, action); From d540c72e7cbfe40fc746542c9d2a3e3a1f3193d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Dec 2021 15:24:15 +0100 Subject: [PATCH 0252/1133] Rename SC_MOD_* to SC_SHORTCUT_MOD_* This will avoid conflicts with new SC_MOD_* constants. --- app/src/cli.c | 14 +++++++------- app/src/input_manager.c | 14 +++++++------- app/src/options.c | 2 +- app/src/options.h | 12 ++++++------ app/tests/test_cli.c | 15 ++++++++------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 5b727fff..ec53e5ec 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1118,7 +1118,7 @@ parse_log_level(const char *s, enum sc_log_level *log_level) { } // item is a list of mod keys separated by '+' (e.g. "lctrl+lalt") -// returns a bitwise-or of SC_MOD_* constants (or 0 on error) +// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error) static unsigned parse_shortcut_mods_item(const char *item, size_t len) { unsigned mod = 0; @@ -1136,17 +1136,17 @@ parse_shortcut_mods_item(const char *item, size_t len) { ((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) if (STREQ("lctrl", item, key_len)) { - mod |= SC_MOD_LCTRL; + mod |= SC_SHORTCUT_MOD_LCTRL; } else if (STREQ("rctrl", item, key_len)) { - mod |= SC_MOD_RCTRL; + mod |= SC_SHORTCUT_MOD_RCTRL; } else if (STREQ("lalt", item, key_len)) { - mod |= SC_MOD_LALT; + mod |= SC_SHORTCUT_MOD_LALT; } else if (STREQ("ralt", item, key_len)) { - mod |= SC_MOD_RALT; + mod |= SC_SHORTCUT_MOD_RALT; } else if (STREQ("lsuper", item, key_len)) { - mod |= SC_MOD_LSUPER; + mod |= SC_SHORTCUT_MOD_LSUPER; } else if (STREQ("rsuper", item, key_len)) { - mod |= SC_MOD_RSUPER; + mod |= SC_SHORTCUT_MOD_RSUPER; } else { LOGE("Unknown modifier key: %.*s " "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7230414a..0688dcab 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -13,24 +13,24 @@ enum sc_action { #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t -to_sdl_mod(unsigned mod) { +to_sdl_mod(unsigned shortcut_mod) { uint16_t sdl_mod = 0; - if (mod & SC_MOD_LCTRL) { + if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) { sdl_mod |= KMOD_LCTRL; } - if (mod & SC_MOD_RCTRL) { + if (shortcut_mod & SC_SHORTCUT_MOD_RCTRL) { sdl_mod |= KMOD_RCTRL; } - if (mod & SC_MOD_LALT) { + if (shortcut_mod & SC_SHORTCUT_MOD_LALT) { sdl_mod |= KMOD_LALT; } - if (mod & SC_MOD_RALT) { + if (shortcut_mod & SC_SHORTCUT_MOD_RALT) { sdl_mod |= KMOD_RALT; } - if (mod & SC_MOD_LSUPER) { + if (shortcut_mod & SC_SHORTCUT_MOD_LSUPER) { sdl_mod |= KMOD_LGUI; } - if (mod & SC_MOD_RSUPER) { + if (shortcut_mod & SC_SHORTCUT_MOD_RSUPER) { sdl_mod |= KMOD_RGUI; } return sdl_mod; diff --git a/app/src/options.c b/app/src/options.c index 4860fa07..7a5b71af 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -22,7 +22,7 @@ const struct scrcpy_options scrcpy_options_default = { .tunnel_host = 0, .tunnel_port = 0, .shortcut_mods = { - .data = {SC_MOD_LALT, SC_MOD_LSUPER}, + .data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER}, .count = 2, }, .max_size = 0, diff --git a/app/src/options.h b/app/src/options.h index 39703210..533f4a3b 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -55,12 +55,12 @@ enum sc_key_inject_mode { #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, + SC_SHORTCUT_MOD_LCTRL = 1 << 0, + SC_SHORTCUT_MOD_RCTRL = 1 << 1, + SC_SHORTCUT_MOD_LALT = 1 << 2, + SC_SHORTCUT_MOD_RALT = 1 << 3, + SC_SHORTCUT_MOD_LSUPER = 1 << 4, + SC_SHORTCUT_MOD_RSUPER = 1 << 5, }; struct sc_shortcut_mods { diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index a29d5fdd..5ea54b7f 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -129,25 +129,26 @@ static void test_parse_shortcut_mods(void) { ok = sc_parse_shortcut_mods("lctrl", &mods); assert(ok); assert(mods.count == 1); - assert(mods.data[0] == SC_MOD_LCTRL); + assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL); ok = sc_parse_shortcut_mods("lctrl+lalt", &mods); assert(ok); assert(mods.count == 1); - assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT)); + assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT)); ok = sc_parse_shortcut_mods("rctrl,lalt", &mods); assert(ok); assert(mods.count == 2); - assert(mods.data[0] == SC_MOD_RCTRL); - assert(mods.data[1] == SC_MOD_LALT); + assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL); + assert(mods.data[1] == SC_SHORTCUT_MOD_LALT); ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); assert(ok); assert(mods.count == 3); - assert(mods.data[0] == SC_MOD_LSUPER); - assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT)); - assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT)); + assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER); + assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT)); + assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL | + SC_SHORTCUT_MOD_RALT)); ok = sc_parse_shortcut_mods("", &mods); assert(!ok); From b8fed50639fe3bf8e02a8cc2409494cd6bf30fb7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 15:51:32 +0100 Subject: [PATCH 0253/1133] Add intermediate input events layer This aims to make the key/mouse processors independent of the "screen", by processing scrcpy-specific input events instead of SDL events. In particular, these scrcpy events are not impacted by any UI window scaling or rotation (contrary to SDL events). --- app/src/input_events.h | 378 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 app/src/input_events.h diff --git a/app/src/input_events.h b/app/src/input_events.h new file mode 100644 index 00000000..b40995e6 --- /dev/null +++ b/app/src/input_events.h @@ -0,0 +1,378 @@ +#ifndef SC_INPUT_EVENTS_H +#define SC_INPUT_EVENTS_H + +#include "common.h" + +#include +#include +#include +#include + +#include "coords.h" + +/* The representation of input events in scrcpy is very close to the SDL API, + * for simplicity. + * + * This scrcpy input events API is designed to be consumed by input event + * processors (sc_key_processor and sc_mouse_processor, see app/src/trait/). + * + * One major semantic difference between SDL input events and scrcpy input + * events is their frame of reference (for mouse and touch events): SDL events + * coordinates are expressed in SDL window coordinates (the visible UI), while + * scrcpy events are expressed in device frame coordinates. + * + * In particular, the window may be visually scaled or rotated (with --rotation + * or MOD+Left/Right), but this does not impact scrcpy input events (contrary + * to SDL input events). This allows to abstract these display details from the + * input event processors (and to make them independent from the "screen"). + * + * For many enums below, the values are purposely the same as the SDL + * constants (though not all SDL values are represented), so that the + * implementation to convert from the SDL version to the scrcpy version is + * straightforward. + * + * In practice, there are 3 levels of input events: + * 1. SDL input events (as received from SDL) + * 2. scrcpy input events (this API) + * 3. the key/mouse processors input events (Android API or HID events) + * + * An input event is first received (1), then (if accepted) converted to an + * scrcpy input event (2), then submitted to the relevant key/mouse processor, + * which (if accepted) is converted to an Android event (to be sent to the + * server) or to an HID event (to be sent over USB/AOA directly). + */ + +enum sc_mod { + SC_MOD_LSHIFT = KMOD_LSHIFT, + SC_MOD_RSHIFT = KMOD_RSHIFT, + SC_MOD_LCTRL = KMOD_LCTRL, + SC_MOD_RCTRL = KMOD_RCTRL, + SC_MOD_LALT = KMOD_LALT, + SC_MOD_RALT = KMOD_RALT, + SC_MOD_LGUI = KMOD_LGUI, + SC_MOD_RGUI = KMOD_RGUI, + + SC_MOD_NUM = KMOD_NUM, + SC_MOD_CAPS = KMOD_CAPS, + SC_MOD_SCROLL = KMOD_SCROLL, +}; + +enum sc_action { + SC_ACTION_DOWN, // key or button pressed + SC_ACTION_UP, // key or button released +}; + +enum sc_keycode { + SC_KEYCODE_UNKNOWN = SDLK_UNKNOWN, + + SC_KEYCODE_RETURN = SDLK_RETURN, + SC_KEYCODE_ESCAPE = SDLK_ESCAPE, + SC_KEYCODE_BACKSPACE = SDLK_BACKSPACE, + SC_KEYCODE_TAB = SDLK_TAB, + SC_KEYCODE_SPACE = SDLK_SPACE, + SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM, + SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL, + SC_KEYCODE_HASH = SDLK_HASH, + SC_KEYCODE_PERCENT = SDLK_PERCENT, + SC_KEYCODE_DOLLAR = SDLK_DOLLAR, + SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND, + SC_KEYCODE_QUOTE = SDLK_QUOTE, + SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN, + SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN, + SC_KEYCODE_ASTERISK = SDLK_ASTERISK, + SC_KEYCODE_PLUS = SDLK_PLUS, + SC_KEYCODE_COMMA = SDLK_COMMA, + SC_KEYCODE_MINUS = SDLK_MINUS, + SC_KEYCODE_PERIOD = SDLK_PERIOD, + SC_KEYCODE_SLASH = SDLK_SLASH, + SC_KEYCODE_0 = SDLK_0, + SC_KEYCODE_1 = SDLK_1, + SC_KEYCODE_2 = SDLK_2, + SC_KEYCODE_3 = SDLK_3, + SC_KEYCODE_4 = SDLK_4, + SC_KEYCODE_5 = SDLK_5, + SC_KEYCODE_6 = SDLK_6, + SC_KEYCODE_7 = SDLK_7, + SC_KEYCODE_8 = SDLK_8, + SC_KEYCODE_9 = SDLK_9, + SC_KEYCODE_COLON = SDLK_COLON, + SC_KEYCODE_SEMICOLON = SDLK_SEMICOLON, + SC_KEYCODE_LESS = SDLK_LESS, + SC_KEYCODE_EQUALS = SDLK_EQUALS, + SC_KEYCODE_GREATER = SDLK_GREATER, + SC_KEYCODE_QUESTION = SDLK_QUESTION, + SC_KEYCODE_AT = SDLK_AT, + + SC_KEYCODE_LEFTBRACKET = SDLK_LEFTBRACKET, + SC_KEYCODE_BACKSLASH = SDLK_BACKSLASH, + SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET, + SC_KEYCODE_CARET = SDLK_CARET, + SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE, + SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE, + SC_KEYCODE_a = SDLK_a, + SC_KEYCODE_b = SDLK_b, + SC_KEYCODE_c = SDLK_c, + SC_KEYCODE_d = SDLK_d, + SC_KEYCODE_e = SDLK_e, + SC_KEYCODE_f = SDLK_f, + SC_KEYCODE_g = SDLK_g, + SC_KEYCODE_h = SDLK_h, + SC_KEYCODE_i = SDLK_i, + SC_KEYCODE_j = SDLK_j, + SC_KEYCODE_k = SDLK_k, + SC_KEYCODE_l = SDLK_l, + SC_KEYCODE_m = SDLK_m, + SC_KEYCODE_n = SDLK_n, + SC_KEYCODE_o = SDLK_o, + SC_KEYCODE_p = SDLK_p, + SC_KEYCODE_q = SDLK_q, + SC_KEYCODE_r = SDLK_r, + SC_KEYCODE_s = SDLK_s, + SC_KEYCODE_t = SDLK_t, + SC_KEYCODE_u = SDLK_u, + SC_KEYCODE_v = SDLK_v, + SC_KEYCODE_w = SDLK_w, + SC_KEYCODE_x = SDLK_x, + SC_KEYCODE_y = SDLK_y, + SC_KEYCODE_z = SDLK_z, + + SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK, + + SC_KEYCODE_F1 = SDLK_F1, + SC_KEYCODE_F2 = SDLK_F2, + SC_KEYCODE_F3 = SDLK_F3, + SC_KEYCODE_F4 = SDLK_F4, + SC_KEYCODE_F5 = SDLK_F5, + SC_KEYCODE_F6 = SDLK_F6, + SC_KEYCODE_F7 = SDLK_F7, + SC_KEYCODE_F8 = SDLK_F8, + SC_KEYCODE_F9 = SDLK_F9, + SC_KEYCODE_F10 = SDLK_F10, + SC_KEYCODE_F11 = SDLK_F11, + SC_KEYCODE_F12 = SDLK_F12, + + SC_KEYCODE_PRINTSCREEN = SDLK_PRINTSCREEN, + SC_KEYCODE_SCROLLLOCK = SDLK_SCROLLLOCK, + SC_KEYCODE_PAUSE = SDLK_PAUSE, + SC_KEYCODE_INSERT = SDLK_INSERT, + SC_KEYCODE_HOME = SDLK_HOME, + SC_KEYCODE_PAGEUP = SDLK_PAGEUP, + SC_KEYCODE_DELETE = SDLK_DELETE, + SC_KEYCODE_END = SDLK_END, + SC_KEYCODE_PAGEDOWN = SDLK_PAGEDOWN, + SC_KEYCODE_RIGHT = SDLK_RIGHT, + SC_KEYCODE_LEFT = SDLK_LEFT, + SC_KEYCODE_DOWN = SDLK_DOWN, + SC_KEYCODE_UP = SDLK_UP, + + SC_KEYCODE_KP_DIVIDE = SDLK_KP_DIVIDE, + SC_KEYCODE_KP_MULTIPLY = SDLK_KP_MULTIPLY, + SC_KEYCODE_KP_MINUS = SDLK_KP_MINUS, + SC_KEYCODE_KP_PLUS = SDLK_KP_PLUS, + SC_KEYCODE_KP_ENTER = SDLK_KP_ENTER, + SC_KEYCODE_KP_1 = SDLK_KP_1, + SC_KEYCODE_KP_2 = SDLK_KP_2, + SC_KEYCODE_KP_3 = SDLK_KP_3, + SC_KEYCODE_KP_4 = SDLK_KP_4, + SC_KEYCODE_KP_5 = SDLK_KP_5, + SC_KEYCODE_KP_6 = SDLK_KP_6, + SC_KEYCODE_KP_7 = SDLK_KP_7, + SC_KEYCODE_KP_8 = SDLK_KP_8, + SC_KEYCODE_KP_9 = SDLK_KP_9, + SC_KEYCODE_KP_0 = SDLK_KP_0, + SC_KEYCODE_KP_PERIOD = SDLK_KP_PERIOD, + SC_KEYCODE_KP_EQUALS = SDLK_KP_EQUALS, + SC_KEYCODE_KP_LEFTPAREN = SDLK_KP_LEFTPAREN, + SC_KEYCODE_KP_RIGHTPAREN = SDLK_KP_RIGHTPAREN, + + SC_KEYCODE_LCTRL = SDLK_LCTRL, + SC_KEYCODE_LSHIFT = SDLK_LSHIFT, + SC_KEYCODE_LALT = SDLK_LALT, + SC_KEYCODE_LGUI = SDLK_LGUI, + SC_KEYCODE_RCTRL = SDLK_RCTRL, + SC_KEYCODE_RSHIFT = SDLK_RSHIFT, + SC_KEYCODE_RALT = SDLK_RALT, + SC_KEYCODE_RGUI = SDLK_RGUI, +}; + +enum sc_scancode { + SC_SCANCODE_UNKNOWN = SDL_SCANCODE_UNKNOWN, + + SC_SCANCODE_A = SDL_SCANCODE_A, + SC_SCANCODE_B = SDL_SCANCODE_B, + SC_SCANCODE_C = SDL_SCANCODE_C, + SC_SCANCODE_D = SDL_SCANCODE_D, + SC_SCANCODE_E = SDL_SCANCODE_E, + SC_SCANCODE_F = SDL_SCANCODE_F, + SC_SCANCODE_G = SDL_SCANCODE_G, + SC_SCANCODE_H = SDL_SCANCODE_H, + SC_SCANCODE_I = SDL_SCANCODE_I, + SC_SCANCODE_J = SDL_SCANCODE_J, + SC_SCANCODE_K = SDL_SCANCODE_K, + SC_SCANCODE_L = SDL_SCANCODE_L, + SC_SCANCODE_M = SDL_SCANCODE_M, + SC_SCANCODE_N = SDL_SCANCODE_N, + SC_SCANCODE_O = SDL_SCANCODE_O, + SC_SCANCODE_P = SDL_SCANCODE_P, + SC_SCANCODE_Q = SDL_SCANCODE_Q, + SC_SCANCODE_R = SDL_SCANCODE_R, + SC_SCANCODE_S = SDL_SCANCODE_S, + SC_SCANCODE_T = SDL_SCANCODE_T, + SC_SCANCODE_U = SDL_SCANCODE_U, + SC_SCANCODE_V = SDL_SCANCODE_V, + SC_SCANCODE_W = SDL_SCANCODE_W, + SC_SCANCODE_X = SDL_SCANCODE_X, + SC_SCANCODE_Y = SDL_SCANCODE_Y, + SC_SCANCODE_Z = SDL_SCANCODE_Z, + + SC_SCANCODE_1 = SDL_SCANCODE_1, + SC_SCANCODE_2 = SDL_SCANCODE_2, + SC_SCANCODE_3 = SDL_SCANCODE_3, + SC_SCANCODE_4 = SDL_SCANCODE_4, + SC_SCANCODE_5 = SDL_SCANCODE_5, + SC_SCANCODE_6 = SDL_SCANCODE_6, + SC_SCANCODE_7 = SDL_SCANCODE_7, + SC_SCANCODE_8 = SDL_SCANCODE_8, + SC_SCANCODE_9 = SDL_SCANCODE_9, + SC_SCANCODE_0 = SDL_SCANCODE_0, + + SC_SCANCODE_RETURN = SDL_SCANCODE_RETURN, + SC_SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE, + SC_SCANCODE_BACKSPACE = SDL_SCANCODE_BACKSPACE, + SC_SCANCODE_TAB = SDL_SCANCODE_TAB, + SC_SCANCODE_SPACE = SDL_SCANCODE_SPACE, + + SC_SCANCODE_MINUS = SDL_SCANCODE_MINUS, + SC_SCANCODE_EQUALS = SDL_SCANCODE_EQUALS, + SC_SCANCODE_LEFTBRACKET = SDL_SCANCODE_LEFTBRACKET, + SC_SCANCODE_RIGHTBRACKET = SDL_SCANCODE_RIGHTBRACKET, + SC_SCANCODE_BACKSLASH = SDL_SCANCODE_BACKSLASH, + SC_SCANCODE_NONUSHASH = SDL_SCANCODE_NONUSHASH, + SC_SCANCODE_SEMICOLON = SDL_SCANCODE_SEMICOLON, + SC_SCANCODE_APOSTROPHE = SDL_SCANCODE_APOSTROPHE, + SC_SCANCODE_GRAVE = SDL_SCANCODE_GRAVE, + SC_SCANCODE_COMMA = SDL_SCANCODE_COMMA, + SC_SCANCODE_PERIOD = SDL_SCANCODE_PERIOD, + SC_SCANCODE_SLASH = SDL_SCANCODE_SLASH, + + SC_SCANCODE_CAPSLOCK = SDL_SCANCODE_CAPSLOCK, + + SC_SCANCODE_F1 = SDL_SCANCODE_F1, + SC_SCANCODE_F2 = SDL_SCANCODE_F2, + SC_SCANCODE_F3 = SDL_SCANCODE_F3, + SC_SCANCODE_F4 = SDL_SCANCODE_F4, + SC_SCANCODE_F5 = SDL_SCANCODE_F5, + SC_SCANCODE_F6 = SDL_SCANCODE_F6, + SC_SCANCODE_F7 = SDL_SCANCODE_F7, + SC_SCANCODE_F8 = SDL_SCANCODE_F8, + SC_SCANCODE_F9 = SDL_SCANCODE_F9, + SC_SCANCODE_F10 = SDL_SCANCODE_F10, + SC_SCANCODE_F11 = SDL_SCANCODE_F11, + SC_SCANCODE_F12 = SDL_SCANCODE_F12, + + SC_SCANCODE_PRINTSCREEN = SDL_SCANCODE_PRINTSCREEN, + SC_SCANCODE_SCROLLLOCK = SDL_SCANCODE_SCROLLLOCK, + SC_SCANCODE_PAUSE = SDL_SCANCODE_PAUSE, + SC_SCANCODE_INSERT = SDL_SCANCODE_INSERT, + SC_SCANCODE_HOME = SDL_SCANCODE_HOME, + SC_SCANCODE_PAGEUP = SDL_SCANCODE_PAGEUP, + SC_SCANCODE_DELETE = SDL_SCANCODE_DELETE, + SC_SCANCODE_END = SDL_SCANCODE_END, + SC_SCANCODE_PAGEDOWN = SDL_SCANCODE_PAGEDOWN, + SC_SCANCODE_RIGHT = SDL_SCANCODE_RIGHT, + SC_SCANCODE_LEFT = SDL_SCANCODE_LEFT, + SC_SCANCODE_DOWN = SDL_SCANCODE_DOWN, + SC_SCANCODE_UP = SDL_SCANCODE_UP, + + SC_SCANCODE_NUMLOCK = SDL_SCANCODE_NUMLOCKCLEAR, + SC_SCANCODE_KP_DIVIDE = SDL_SCANCODE_KP_DIVIDE, + SC_SCANCODE_KP_MULTIPLY = SDL_SCANCODE_KP_MULTIPLY, + SC_SCANCODE_KP_MINUS = SDL_SCANCODE_KP_MINUS, + SC_SCANCODE_KP_PLUS = SDL_SCANCODE_KP_PLUS, + SC_SCANCODE_KP_ENTER = SDL_SCANCODE_KP_ENTER, + SC_SCANCODE_KP_1 = SDL_SCANCODE_KP_1, + SC_SCANCODE_KP_2 = SDL_SCANCODE_KP_2, + SC_SCANCODE_KP_3 = SDL_SCANCODE_KP_3, + SC_SCANCODE_KP_4 = SDL_SCANCODE_KP_4, + SC_SCANCODE_KP_5 = SDL_SCANCODE_KP_5, + SC_SCANCODE_KP_6 = SDL_SCANCODE_KP_6, + SC_SCANCODE_KP_7 = SDL_SCANCODE_KP_7, + SC_SCANCODE_KP_8 = SDL_SCANCODE_KP_8, + SC_SCANCODE_KP_9 = SDL_SCANCODE_KP_9, + SC_SCANCODE_KP_0 = SDL_SCANCODE_KP_0, + SC_SCANCODE_KP_PERIOD = SDL_SCANCODE_KP_PERIOD, + + SC_SCANCODE_LCTRL = SDL_SCANCODE_LCTRL, + SC_SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT, + SC_SCANCODE_LALT = SDL_SCANCODE_LALT, + SC_SCANCODE_LGUI = SDL_SCANCODE_LGUI, + SC_SCANCODE_RCTRL = SDL_SCANCODE_RCTRL, + SC_SCANCODE_RSHIFT = SDL_SCANCODE_RSHIFT, + SC_SCANCODE_RALT = SDL_SCANCODE_RALT, + SC_SCANCODE_RGUI = SDL_SCANCODE_RGUI, +}; + +// On purpose, only use the "mask" values (1, 2, 4, 8, 16) for a single button, +// to avoid unnecessary conversions (and confusion). +enum sc_mouse_button { + SC_MOUSE_BUTTON_UNKNOWN = 0, + SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT), + SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT), + SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE), + SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1), + SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2), +}; + +static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod), + "SDL_Keymod must be convertible to sc_mod"); + +static_assert(sizeof(enum sc_keycode) >= sizeof(SDL_Keycode), + "SDL_Keycode must be convertible to sc_keycode"); + +static_assert(sizeof(enum sc_scancode) >= sizeof(SDL_Scancode), + "SDL_Scancode must be convertible to sc_scancode"); + +enum sc_touch_action { + SC_TOUCH_ACTION_MOVE, + SC_TOUCH_ACTION_DOWN, + SC_TOUCH_ACTION_UP, +}; + +struct sc_key_event { + enum sc_action action; + enum sc_keycode keycode; + enum sc_scancode scancode; + uint16_t mods_state; // bitwise-OR of sc_mod values + bool repeat; +}; + +struct sc_text_event { + const char *text; // not owned +}; + +struct sc_mouse_click_event { + struct sc_position position; + enum sc_action action; + enum sc_mouse_button button; + uint8_t buttons_state; // bitwise-OR of sc_mouse_button values +}; + +struct sc_mouse_scroll_event { + struct sc_position position; + int32_t hscroll; + int32_t vscroll; +}; + +struct sc_mouse_motion_event { + struct sc_position position; + uint8_t buttons_state; // bitwise-OR of sc_mouse_button values +}; + +struct sc_touch_event { + struct sc_position position; + enum sc_touch_action action; + uint64_t pointer_id; + float pressure; +}; + +#endif From e4396e34c226bd5adfd6c011718a7f101967cce0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 15:56:59 +0100 Subject: [PATCH 0254/1133] Use common sc_action in input manager Now that the scrcpy input events API exposes a sc_action enum, use the same from the input manager. --- app/src/input_manager.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 0688dcab..0ed364b1 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -3,13 +3,9 @@ #include #include +#include "input_events.h" #include "util/log.h" -enum sc_action { - SC_ACTION_DOWN, - SC_ACTION_UP, -}; - #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t From b4b638e8fe2a632b045478e6038e6eef19562c5e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 16:14:34 +0100 Subject: [PATCH 0255/1133] Use scrcpy input events for key processors Pass scrcpy input events instead of SDL input events to key processors. This makes the source code of key processors independent of the SDL API. --- app/src/hid_keyboard.c | 50 ++++--- app/src/input_manager.c | 40 +++++- app/src/keyboard_inject.c | 259 +++++++++++++++++----------------- app/src/trait/key_processor.h | 8 +- 4 files changed, 194 insertions(+), 163 deletions(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index cf8eab61..be5f8a19 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -1,8 +1,8 @@ #include "hid_keyboard.h" #include -#include +#include "input_events.h" #include "util/log.h" /** Downcast key processor to hid_keyboard */ @@ -201,30 +201,30 @@ static const unsigned char keyboard_report_desc[] = { */ static unsigned char -sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { +sdl_keymod_to_hid_modifiers(uint16_t mod) { unsigned char modifiers = HID_MODIFIER_NONE; - if (mod & KMOD_LCTRL) { + if (mod & SC_MOD_LCTRL) { modifiers |= HID_MODIFIER_LEFT_CONTROL; } - if (mod & KMOD_LSHIFT) { + if (mod & SC_MOD_LSHIFT) { modifiers |= HID_MODIFIER_LEFT_SHIFT; } - if (mod & KMOD_LALT) { + if (mod & SC_MOD_LALT) { modifiers |= HID_MODIFIER_LEFT_ALT; } - if (mod & KMOD_LGUI) { + if (mod & SC_MOD_LGUI) { modifiers |= HID_MODIFIER_LEFT_GUI; } - if (mod & KMOD_RCTRL) { + if (mod & SC_MOD_RCTRL) { modifiers |= HID_MODIFIER_RIGHT_CONTROL; } - if (mod & KMOD_RSHIFT) { + if (mod & SC_MOD_RSHIFT) { modifiers |= HID_MODIFIER_RIGHT_SHIFT; } - if (mod & KMOD_RALT) { + if (mod & SC_MOD_RALT) { modifiers |= HID_MODIFIER_RIGHT_ALT; } - if (mod & KMOD_RGUI) { + if (mod & SC_MOD_RGUI) { modifiers |= HID_MODIFIER_RIGHT_GUI; } return modifiers; @@ -248,15 +248,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { } static inline bool -scancode_is_modifier(SDL_Scancode scancode) { - return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI; +scancode_is_modifier(enum sc_scancode scancode) { + return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI; } static bool convert_hid_keyboard_event(struct sc_hid_keyboard *kb, struct sc_hid_event *hid_event, - const SDL_KeyboardEvent *event) { - SDL_Scancode scancode = event->keysym.scancode; + const struct sc_key_event *event) { + enum sc_scancode scancode = event->scancode; assert(scancode >= 0); // SDL also generates events when only modifiers are pressed, we cannot @@ -272,11 +272,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, return false; } - unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod); + unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state); if (scancode < SC_HID_KEYBOARD_KEYS) { // Pressed is true and released is false - kb->keys[scancode] = (event->type == SDL_KEYDOWN); + kb->keys[scancode] = (event->action == SC_ACTION_DOWN); LOGV("keys[%02x] = %s", scancode, kb->keys[scancode] ? "true" : "false"); } @@ -306,17 +306,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, end: LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x", - event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode, - event->keysym.scancode, modifiers); + event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode, + event->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; +push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { + bool capslock = mods_state & SC_MOD_CAPS; + bool numlock = mods_state & SC_MOD_NUM; if (!capslock && !numlock) { // Nothing to do return true; @@ -328,8 +328,6 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) { 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; @@ -353,7 +351,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) { static void sc_key_processor_process_key(struct sc_key_processor *kp, - const SDL_KeyboardEvent *event, + const struct sc_key_event *event, uint64_t ack_to_wait) { if (event->repeat) { // In USB HID protocol, key repeat is handled by the host (Android), so @@ -369,7 +367,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, 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)) { + if (push_mod_lock_state(kb, event->mods_state)) { kb->mod_lock_synchronized = true; } } @@ -391,7 +389,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, static void sc_key_processor_process_text(struct sc_key_processor *kp, - const SDL_TextInputEvent *event) { + const struct sc_text_event *event) { (void) kp; (void) event; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 0ed364b1..03d19b0e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -6,6 +6,30 @@ #include "input_events.h" #include "util/log.h" +static inline uint16_t +sc_mods_state_from_sdl(uint16_t mods_state) { + return mods_state; +} + +static inline enum sc_keycode +sc_keycode_from_sdl(SDL_Keycode keycode) { + return (enum sc_keycode) keycode; +} + +static inline enum sc_scancode +sc_scancode_from_sdl(SDL_Scancode scancode) { + return (enum sc_scancode) scancode; +} + +static inline enum sc_action +sc_action_from_sdl_keyboard_type(uint32_t type) { + assert(type == SDL_KEYDOWN || type == SDL_KEYUP); + if (type == SDL_KEYDOWN) { + return SC_ACTION_DOWN; + } + return SC_ACTION_UP; +} + #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t @@ -317,7 +341,11 @@ input_manager_process_text_input(struct input_manager *im, return; } - im->kp->ops->process_text(im->kp, event); + struct sc_text_event evt = { + .text = event->text, + }; + + im->kp->ops->process_text(im->kp, &evt); } static bool @@ -536,7 +564,15 @@ input_manager_process_key(struct input_manager *im, } } - im->kp->ops->process_key(im->kp, event, ack_to_wait); + struct sc_key_event evt = { + .action = sc_action_from_sdl_keyboard_type(event->type), + .keycode = sc_keycode_from_sdl(event->keysym.sym), + .scancode = sc_scancode_from_sdl(event->keysym.scancode), + .repeat = event->repeat, + .mods_state = sc_mods_state_from_sdl(event->keysym.mod), + }; + + im->kp->ops->process_key(im->kp, &evt, ack_to_wait); } static void diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 5143eafc..968a3c4f 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -1,11 +1,11 @@ #include "keyboard_inject.h" #include -#include #include "android/input.h" #include "control_msg.h" #include "controller.h" +#include "input_events.h" #include "util/intmap.h" #include "util/log.h" @@ -13,10 +13,10 @@ #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) static bool -convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { +convert_keycode_action(enum sc_action from, enum android_keyevent_action *to) { static const struct sc_intmap_entry actions[] = { - {SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN}, - {SDL_KEYUP, AKEY_EVENT_ACTION_UP}, + {SC_ACTION_DOWN, AKEY_EVENT_ACTION_DOWN}, + {SC_ACTION_UP, AKEY_EVENT_ACTION_UP}, }; const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); @@ -29,125 +29,125 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { } static bool -convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, +convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod, enum sc_key_inject_mode key_inject_mode) { // Navigation keys and ENTER. // Used in all modes. static const struct sc_intmap_entry special_keys[] = { - {SDLK_RETURN, AKEYCODE_ENTER}, - {SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER}, - {SDLK_ESCAPE, AKEYCODE_ESCAPE}, - {SDLK_BACKSPACE, AKEYCODE_DEL}, - {SDLK_TAB, AKEYCODE_TAB}, - {SDLK_PAGEUP, AKEYCODE_PAGE_UP}, - {SDLK_DELETE, AKEYCODE_FORWARD_DEL}, - {SDLK_HOME, AKEYCODE_MOVE_HOME}, - {SDLK_END, AKEYCODE_MOVE_END}, - {SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN}, - {SDLK_RIGHT, AKEYCODE_DPAD_RIGHT}, - {SDLK_LEFT, AKEYCODE_DPAD_LEFT}, - {SDLK_DOWN, AKEYCODE_DPAD_DOWN}, - {SDLK_UP, AKEYCODE_DPAD_UP}, - {SDLK_LCTRL, AKEYCODE_CTRL_LEFT}, - {SDLK_RCTRL, AKEYCODE_CTRL_RIGHT}, - {SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT}, - {SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT}, + {SC_KEYCODE_RETURN, AKEYCODE_ENTER}, + {SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER}, + {SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE}, + {SC_KEYCODE_BACKSPACE, AKEYCODE_DEL}, + {SC_KEYCODE_TAB, AKEYCODE_TAB}, + {SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP}, + {SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL}, + {SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME}, + {SC_KEYCODE_END, AKEYCODE_MOVE_END}, + {SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN}, + {SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT}, + {SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT}, + {SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN}, + {SC_KEYCODE_UP, AKEYCODE_DPAD_UP}, + {SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT}, + {SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT}, + {SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT}, + {SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT}, }; // Numpad navigation keys. // Used in all modes, when NumLock and Shift are disabled. static const struct sc_intmap_entry kp_nav_keys[] = { - {SDLK_KP_0, AKEYCODE_INSERT}, - {SDLK_KP_1, AKEYCODE_MOVE_END}, - {SDLK_KP_2, AKEYCODE_DPAD_DOWN}, - {SDLK_KP_3, AKEYCODE_PAGE_DOWN}, - {SDLK_KP_4, AKEYCODE_DPAD_LEFT}, - {SDLK_KP_6, AKEYCODE_DPAD_RIGHT}, - {SDLK_KP_7, AKEYCODE_MOVE_HOME}, - {SDLK_KP_8, AKEYCODE_DPAD_UP}, - {SDLK_KP_9, AKEYCODE_PAGE_UP}, - {SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL}, + {SC_KEYCODE_KP_0, AKEYCODE_INSERT}, + {SC_KEYCODE_KP_1, AKEYCODE_MOVE_END}, + {SC_KEYCODE_KP_2, AKEYCODE_DPAD_DOWN}, + {SC_KEYCODE_KP_3, AKEYCODE_PAGE_DOWN}, + {SC_KEYCODE_KP_4, AKEYCODE_DPAD_LEFT}, + {SC_KEYCODE_KP_6, AKEYCODE_DPAD_RIGHT}, + {SC_KEYCODE_KP_7, AKEYCODE_MOVE_HOME}, + {SC_KEYCODE_KP_8, AKEYCODE_DPAD_UP}, + {SC_KEYCODE_KP_9, AKEYCODE_PAGE_UP}, + {SC_KEYCODE_KP_PERIOD, AKEYCODE_FORWARD_DEL}, }; // Letters and space. // Used in non-text mode. static const struct sc_intmap_entry alphaspace_keys[] = { - {SDLK_a, AKEYCODE_A}, - {SDLK_b, AKEYCODE_B}, - {SDLK_c, AKEYCODE_C}, - {SDLK_d, AKEYCODE_D}, - {SDLK_e, AKEYCODE_E}, - {SDLK_f, AKEYCODE_F}, - {SDLK_g, AKEYCODE_G}, - {SDLK_h, AKEYCODE_H}, - {SDLK_i, AKEYCODE_I}, - {SDLK_j, AKEYCODE_J}, - {SDLK_k, AKEYCODE_K}, - {SDLK_l, AKEYCODE_L}, - {SDLK_m, AKEYCODE_M}, - {SDLK_n, AKEYCODE_N}, - {SDLK_o, AKEYCODE_O}, - {SDLK_p, AKEYCODE_P}, - {SDLK_q, AKEYCODE_Q}, - {SDLK_r, AKEYCODE_R}, - {SDLK_s, AKEYCODE_S}, - {SDLK_t, AKEYCODE_T}, - {SDLK_u, AKEYCODE_U}, - {SDLK_v, AKEYCODE_V}, - {SDLK_w, AKEYCODE_W}, - {SDLK_x, AKEYCODE_X}, - {SDLK_y, AKEYCODE_Y}, - {SDLK_z, AKEYCODE_Z}, - {SDLK_SPACE, AKEYCODE_SPACE}, + {SC_KEYCODE_a, AKEYCODE_A}, + {SC_KEYCODE_b, AKEYCODE_B}, + {SC_KEYCODE_c, AKEYCODE_C}, + {SC_KEYCODE_d, AKEYCODE_D}, + {SC_KEYCODE_e, AKEYCODE_E}, + {SC_KEYCODE_f, AKEYCODE_F}, + {SC_KEYCODE_g, AKEYCODE_G}, + {SC_KEYCODE_h, AKEYCODE_H}, + {SC_KEYCODE_i, AKEYCODE_I}, + {SC_KEYCODE_j, AKEYCODE_J}, + {SC_KEYCODE_k, AKEYCODE_K}, + {SC_KEYCODE_l, AKEYCODE_L}, + {SC_KEYCODE_m, AKEYCODE_M}, + {SC_KEYCODE_n, AKEYCODE_N}, + {SC_KEYCODE_o, AKEYCODE_O}, + {SC_KEYCODE_p, AKEYCODE_P}, + {SC_KEYCODE_q, AKEYCODE_Q}, + {SC_KEYCODE_r, AKEYCODE_R}, + {SC_KEYCODE_s, AKEYCODE_S}, + {SC_KEYCODE_t, AKEYCODE_T}, + {SC_KEYCODE_u, AKEYCODE_U}, + {SC_KEYCODE_v, AKEYCODE_V}, + {SC_KEYCODE_w, AKEYCODE_W}, + {SC_KEYCODE_x, AKEYCODE_X}, + {SC_KEYCODE_y, AKEYCODE_Y}, + {SC_KEYCODE_z, AKEYCODE_Z}, + {SC_KEYCODE_SPACE, AKEYCODE_SPACE}, }; // Numbers and punctuation keys. // Used in raw mode only. static const struct sc_intmap_entry numbers_punct_keys[] = { - {SDLK_HASH, AKEYCODE_POUND}, - {SDLK_PERCENT, AKEYCODE_PERIOD}, - {SDLK_QUOTE, AKEYCODE_APOSTROPHE}, - {SDLK_ASTERISK, AKEYCODE_STAR}, - {SDLK_PLUS, AKEYCODE_PLUS}, - {SDLK_COMMA, AKEYCODE_COMMA}, - {SDLK_MINUS, AKEYCODE_MINUS}, - {SDLK_PERIOD, AKEYCODE_PERIOD}, - {SDLK_SLASH, AKEYCODE_SLASH}, - {SDLK_0, AKEYCODE_0}, - {SDLK_1, AKEYCODE_1}, - {SDLK_2, AKEYCODE_2}, - {SDLK_3, AKEYCODE_3}, - {SDLK_4, AKEYCODE_4}, - {SDLK_5, AKEYCODE_5}, - {SDLK_6, AKEYCODE_6}, - {SDLK_7, AKEYCODE_7}, - {SDLK_8, AKEYCODE_8}, - {SDLK_9, AKEYCODE_9}, - {SDLK_SEMICOLON, AKEYCODE_SEMICOLON}, - {SDLK_EQUALS, AKEYCODE_EQUALS}, - {SDLK_AT, AKEYCODE_AT}, - {SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET}, - {SDLK_BACKSLASH, AKEYCODE_BACKSLASH}, - {SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET}, - {SDLK_BACKQUOTE, AKEYCODE_GRAVE}, - {SDLK_KP_1, AKEYCODE_NUMPAD_1}, - {SDLK_KP_2, AKEYCODE_NUMPAD_2}, - {SDLK_KP_3, AKEYCODE_NUMPAD_3}, - {SDLK_KP_4, AKEYCODE_NUMPAD_4}, - {SDLK_KP_5, AKEYCODE_NUMPAD_5}, - {SDLK_KP_6, AKEYCODE_NUMPAD_6}, - {SDLK_KP_7, AKEYCODE_NUMPAD_7}, - {SDLK_KP_8, AKEYCODE_NUMPAD_8}, - {SDLK_KP_9, AKEYCODE_NUMPAD_9}, - {SDLK_KP_0, AKEYCODE_NUMPAD_0}, - {SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE}, - {SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY}, - {SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT}, - {SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD}, - {SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT}, - {SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS}, - {SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN}, - {SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN}, + {SC_KEYCODE_HASH, AKEYCODE_POUND}, + {SC_KEYCODE_PERCENT, AKEYCODE_PERIOD}, + {SC_KEYCODE_QUOTE, AKEYCODE_APOSTROPHE}, + {SC_KEYCODE_ASTERISK, AKEYCODE_STAR}, + {SC_KEYCODE_PLUS, AKEYCODE_PLUS}, + {SC_KEYCODE_COMMA, AKEYCODE_COMMA}, + {SC_KEYCODE_MINUS, AKEYCODE_MINUS}, + {SC_KEYCODE_PERIOD, AKEYCODE_PERIOD}, + {SC_KEYCODE_SLASH, AKEYCODE_SLASH}, + {SC_KEYCODE_0, AKEYCODE_0}, + {SC_KEYCODE_1, AKEYCODE_1}, + {SC_KEYCODE_2, AKEYCODE_2}, + {SC_KEYCODE_3, AKEYCODE_3}, + {SC_KEYCODE_4, AKEYCODE_4}, + {SC_KEYCODE_5, AKEYCODE_5}, + {SC_KEYCODE_6, AKEYCODE_6}, + {SC_KEYCODE_7, AKEYCODE_7}, + {SC_KEYCODE_8, AKEYCODE_8}, + {SC_KEYCODE_9, AKEYCODE_9}, + {SC_KEYCODE_SEMICOLON, AKEYCODE_SEMICOLON}, + {SC_KEYCODE_EQUALS, AKEYCODE_EQUALS}, + {SC_KEYCODE_AT, AKEYCODE_AT}, + {SC_KEYCODE_LEFTBRACKET, AKEYCODE_LEFT_BRACKET}, + {SC_KEYCODE_BACKSLASH, AKEYCODE_BACKSLASH}, + {SC_KEYCODE_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET}, + {SC_KEYCODE_BACKQUOTE, AKEYCODE_GRAVE}, + {SC_KEYCODE_KP_1, AKEYCODE_NUMPAD_1}, + {SC_KEYCODE_KP_2, AKEYCODE_NUMPAD_2}, + {SC_KEYCODE_KP_3, AKEYCODE_NUMPAD_3}, + {SC_KEYCODE_KP_4, AKEYCODE_NUMPAD_4}, + {SC_KEYCODE_KP_5, AKEYCODE_NUMPAD_5}, + {SC_KEYCODE_KP_6, AKEYCODE_NUMPAD_6}, + {SC_KEYCODE_KP_7, AKEYCODE_NUMPAD_7}, + {SC_KEYCODE_KP_8, AKEYCODE_NUMPAD_8}, + {SC_KEYCODE_KP_9, AKEYCODE_NUMPAD_9}, + {SC_KEYCODE_KP_0, AKEYCODE_NUMPAD_0}, + {SC_KEYCODE_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE}, + {SC_KEYCODE_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY}, + {SC_KEYCODE_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT}, + {SC_KEYCODE_KP_PLUS, AKEYCODE_NUMPAD_ADD}, + {SC_KEYCODE_KP_PERIOD, AKEYCODE_NUMPAD_DOT}, + {SC_KEYCODE_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS}, + {SC_KEYCODE_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN}, + {SC_KEYCODE_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN}, }; const struct sc_intmap_entry *entry = @@ -157,7 +157,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, return true; } - if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { + if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) { // Handle Numpad events when Num Lock is disabled // If SHIFT is pressed, a text event will be sent instead entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from); @@ -167,12 +167,13 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, } } - if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) { + if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && + !(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) { // do not forward alpha and space key events (unless Ctrl is pressed) return false; } - if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { + if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) { return false; } @@ -214,70 +215,66 @@ autocomplete_metastate(enum android_metastate metastate) { } static enum android_metastate -convert_meta_state(SDL_Keymod mod) { +convert_meta_state(uint16_t mod) { enum android_metastate metastate = 0; - if (mod & KMOD_LSHIFT) { + if (mod & SC_MOD_LSHIFT) { metastate |= AMETA_SHIFT_LEFT_ON; } - if (mod & KMOD_RSHIFT) { + if (mod & SC_MOD_RSHIFT) { metastate |= AMETA_SHIFT_RIGHT_ON; } - if (mod & KMOD_LCTRL) { + if (mod & SC_MOD_LCTRL) { metastate |= AMETA_CTRL_LEFT_ON; } - if (mod & KMOD_RCTRL) { + if (mod & SC_MOD_RCTRL) { metastate |= AMETA_CTRL_RIGHT_ON; } - if (mod & KMOD_LALT) { + if (mod & SC_MOD_LALT) { metastate |= AMETA_ALT_LEFT_ON; } - if (mod & KMOD_RALT) { + if (mod & SC_MOD_RALT) { metastate |= AMETA_ALT_RIGHT_ON; } - if (mod & KMOD_LGUI) { // Windows key + if (mod & SC_MOD_LGUI) { // Windows key metastate |= AMETA_META_LEFT_ON; } - if (mod & KMOD_RGUI) { // Windows key + if (mod & SC_MOD_RGUI) { // Windows key metastate |= AMETA_META_RIGHT_ON; } - if (mod & KMOD_NUM) { + if (mod & SC_MOD_NUM) { metastate |= AMETA_NUM_LOCK_ON; } - if (mod & KMOD_CAPS) { + if (mod & SC_MOD_CAPS) { metastate |= AMETA_CAPS_LOCK_ON; } - if (mod & KMOD_MODE) { // Alt Gr - // no mapping? - } // fill the dependent fields return autocomplete_metastate(metastate); } static bool -convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, +convert_input_key(const struct sc_key_event *event, struct control_msg *msg, enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { - to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; - if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { + if (!convert_keycode_action(event->action, &msg->inject_keycode.action)) { return false; } - uint16_t mod = from->keysym.mod; - if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, - key_inject_mode)) { + if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode, + event->mods_state, key_inject_mode)) { return false; } - to->inject_keycode.repeat = repeat; - to->inject_keycode.metastate = convert_meta_state(mod); + msg->inject_keycode.repeat = repeat; + msg->inject_keycode.metastate = convert_meta_state(event->mods_state); return true; } static void sc_key_processor_process_key(struct sc_key_processor *kp, - const SDL_KeyboardEvent *event, + const struct sc_key_event *event, uint64_t ack_to_wait) { // The device clipboard synchronization and the key event messages are // serialized, there is nothing special to do to ensure that the clipboard @@ -305,7 +302,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, static void sc_key_processor_process_text(struct sc_key_processor *kp, - const SDL_TextInputEvent *event) { + const struct sc_text_event *event) { struct sc_keyboard_inject *ki = DOWNCAST(kp); if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index f4afe27b..045e5e7b 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -6,7 +6,7 @@ #include #include -#include +#include "input_events.h" /** * Key processor trait. @@ -37,12 +37,12 @@ struct sc_key_processor_ops { * Ctrl+v on the device. */ void - (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event, - uint64_t ack_to_wait); + (*process_key)(struct sc_key_processor *kp, + const struct sc_key_event *event, uint64_t ack_to_wait); void (*process_text)(struct sc_key_processor *kp, - const SDL_TextInputEvent *event); + const struct sc_text_event *event); }; #endif From 9460bdd87ba76f03ff27e98973073a6acd8ed306 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 16:24:20 +0100 Subject: [PATCH 0256/1133] Use scrcpy input events for mouse processors Pass scrcpy input events instead of SDL input events to mouse processors. These events represent exactly what mouse processors need, abstracted from any visual orientation and scaling applied on the SDL window. This makes the mouse processors independent of the "screen" instance, and the implementation source code independent of the SDL API. --- app/src/input_manager.c | 101 ++++++++++++++++++++- app/src/mouse_inject.c | 152 +++++++++++++------------------- app/src/mouse_inject.h | 4 +- app/src/scrcpy.c | 2 +- app/src/trait/mouse_processor.h | 14 +-- 5 files changed, 169 insertions(+), 104 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 03d19b0e..ba6a4e2a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -30,6 +30,44 @@ sc_action_from_sdl_keyboard_type(uint32_t type) { return SC_ACTION_UP; } +static inline enum sc_action +sc_action_from_sdl_mousebutton_type(uint32_t type) { + assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP); + if (type == SDL_MOUSEBUTTONDOWN) { + return SC_ACTION_DOWN; + } + return SC_ACTION_UP; +} + +static inline enum sc_touch_action +sc_touch_action_from_sdl(uint32_t type) { + assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN || + type == SDL_FINGERUP); + if (type == SDL_FINGERMOTION) { + return SC_TOUCH_ACTION_MOVE; + } + if (type == SDL_FINGERDOWN) { + return SC_TOUCH_ACTION_DOWN; + } + return SC_TOUCH_ACTION_UP; +} + +static inline enum sc_mouse_button +sc_mouse_button_from_sdl(uint8_t button) { + if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) { + // SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index) + return SDL_BUTTON(button); + } + + return SC_MOUSE_BUTTON_UNKNOWN; +} + +static inline uint8_t +sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) { + assert(buttons_state < 0x100); // fits in uint8_t + return buttons_state; +} + #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t @@ -591,7 +629,16 @@ input_manager_process_mouse_motion(struct input_manager *im, return; } - im->mp->ops->process_mouse_motion(im->mp, event); + struct sc_mouse_motion_event evt = { + .position = { + .screen_size = im->screen->frame_size, + .point = screen_convert_window_to_frame_coords(im->screen, + event->x, event->y), + }, + .buttons_state = sc_mouse_buttons_state_from_sdl(event->state), + }; + + im->mp->ops->process_mouse_motion(im->mp, &evt); if (im->vfinger_down) { struct sc_point mouse = @@ -605,7 +652,25 @@ input_manager_process_mouse_motion(struct input_manager *im, static void input_manager_process_touch(struct input_manager *im, const SDL_TouchFingerEvent *event) { - im->mp->ops->process_touch(im->mp, event); + int dw; + int dh; + SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh); + + // SDL touch event coordinates are normalized in the range [0; 1] + int32_t x = event->x * dw; + int32_t y = event->y * dh; + + struct sc_touch_event evt = { + .position = { + .screen_size = im->screen->frame_size, + .point = screen_convert_drawable_to_frame_coords(im->screen, x, y), + }, + .action = sc_touch_action_from_sdl(event->type), + .pointer_id = event->fingerId, + .pressure = event->pressure, + }; + + im->mp->ops->process_touch(im->mp, &evt); } static void @@ -665,7 +730,20 @@ input_manager_process_mouse_button(struct input_manager *im, return; } - im->mp->ops->process_mouse_button(im->mp, event); + uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL); + + struct sc_mouse_click_event evt = { + .position = { + .screen_size = im->screen->frame_size, + .point = screen_convert_window_to_frame_coords(im->screen, event->x, + event->y), + }, + .action = sc_action_from_sdl_mousebutton_type(event->type), + .button = sc_mouse_button_from_sdl(event->button), + .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state), + }; + + im->mp->ops->process_mouse_click(im->mp, &evt); // Pinch-to-zoom simulation. // @@ -696,7 +774,22 @@ input_manager_process_mouse_button(struct input_manager *im, static void input_manager_process_mouse_wheel(struct input_manager *im, const SDL_MouseWheelEvent *event) { - im->mp->ops->process_mouse_wheel(im->mp, event); + // mouse_x and mouse_y are expressed in pixels relative to the window + int mouse_x; + int mouse_y; + SDL_GetMouseState(&mouse_x, &mouse_y); + + struct sc_mouse_scroll_event evt = { + .position = { + .screen_size = im->screen->frame_size, + .point = screen_convert_window_to_frame_coords(im->screen, + mouse_x, mouse_y), + }, + .hscroll = event->x, + .vscroll = event->y, + }; + + im->mp->ops->process_mouse_scroll(im->mp, &evt); } bool diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 8f7e363d..a4d2e1a7 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -1,11 +1,11 @@ #include "mouse_inject.h" #include -#include #include "android/input.h" #include "control_msg.h" #include "controller.h" +#include "input_events.h" #include "util/intmap.h" #include "util/log.h" @@ -15,29 +15,29 @@ static enum android_motionevent_buttons convert_mouse_buttons(uint32_t state) { enum android_motionevent_buttons buttons = 0; - if (state & SDL_BUTTON_LMASK) { + if (state & SC_MOUSE_BUTTON_LEFT) { buttons |= AMOTION_EVENT_BUTTON_PRIMARY; } - if (state & SDL_BUTTON_RMASK) { + if (state & SC_MOUSE_BUTTON_RIGHT) { buttons |= AMOTION_EVENT_BUTTON_SECONDARY; } - if (state & SDL_BUTTON_MMASK) { + if (state & SC_MOUSE_BUTTON_MIDDLE) { buttons |= AMOTION_EVENT_BUTTON_TERTIARY; } - if (state & SDL_BUTTON_X1MASK) { + if (state & SC_MOUSE_BUTTON_X1) { buttons |= AMOTION_EVENT_BUTTON_BACK; } - if (state & SDL_BUTTON_X2MASK) { + if (state & SC_MOUSE_BUTTON_X2) { buttons |= AMOTION_EVENT_BUTTON_FORWARD; } return buttons; } static bool -convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { +convert_mouse_action(enum sc_action from, enum android_motionevent_action *to) { static const struct sc_intmap_entry actions[] = { - {SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN}, - {SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP}, + {SC_ACTION_DOWN, AMOTION_EVENT_ACTION_DOWN}, + {SC_ACTION_UP, AMOTION_EVENT_ACTION_UP}, }; const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); @@ -50,11 +50,12 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { } static bool -convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { +convert_touch_action(enum sc_touch_action from, + enum android_motionevent_action *to) { static const struct sc_intmap_entry actions[] = { - {SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE}, - {SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN}, - {SDL_FINGERUP, AMOTION_EVENT_ACTION_UP}, + {SC_TOUCH_ACTION_MOVE, AMOTION_EVENT_ACTION_MOVE}, + {SC_TOUCH_ACTION_DOWN, AMOTION_EVENT_ACTION_DOWN}, + {SC_TOUCH_ACTION_UP, AMOTION_EVENT_ACTION_UP}, }; const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); @@ -67,99 +68,73 @@ convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { } static bool -convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; - to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point = - screen_convert_window_to_frame_coords(screen, from->x, from->y); - to->inject_touch_event.pressure = 1.f; - to->inject_touch_event.buttons = convert_mouse_buttons(from->state); +convert_mouse_motion(const struct sc_mouse_motion_event *event, + struct control_msg *msg) { + msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + msg->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; + msg->inject_touch_event.pointer_id = POINTER_ID_MOUSE; + msg->inject_touch_event.position = event->position; + msg->inject_touch_event.pressure = 1.f; + msg->inject_touch_event.buttons = + convert_mouse_buttons(event->buttons_state); return true; } static bool -convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; +convert_touch(const struct sc_touch_event *event, + struct control_msg *msg) { + msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - if (!convert_touch_action(from->type, &to->inject_touch_event.action)) { + if (!convert_touch_action(event->action, &msg->inject_touch_event.action)) { return false; } - to->inject_touch_event.pointer_id = from->fingerId; - to->inject_touch_event.position.screen_size = screen->frame_size; + msg->inject_touch_event.pointer_id = event->pointer_id; + msg->inject_touch_event.position = event->position; + msg->inject_touch_event.pressure = event->pressure; + msg->inject_touch_event.buttons = 0; - int dw; - int dh; - SDL_GL_GetDrawableSize(screen->window, &dw, &dh); - - // SDL touch event coordinates are normalized in the range [0; 1] - int32_t x = from->x * dw; - int32_t y = from->y * dh; - to->inject_touch_event.position.point = - screen_convert_drawable_to_frame_coords(screen, x, y); - - to->inject_touch_event.pressure = from->pressure; - to->inject_touch_event.buttons = 0; return true; } static bool -convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, - struct control_msg *to) { - to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; +convert_mouse_click(const struct sc_mouse_click_event *event, + struct control_msg *msg) { + msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) { + if (!convert_mouse_action(event->action, &msg->inject_touch_event.action)) { return false; } - to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - to->inject_touch_event.position.screen_size = screen->frame_size; - to->inject_touch_event.position.point = - screen_convert_window_to_frame_coords(screen, from->x, from->y); - to->inject_touch_event.pressure = - from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f; - to->inject_touch_event.buttons = - convert_mouse_buttons(SDL_BUTTON(from->button)); + msg->inject_touch_event.pointer_id = POINTER_ID_MOUSE; + msg->inject_touch_event.position = event->position; + msg->inject_touch_event.pressure = event->action == SC_ACTION_DOWN + ? 1.f : 0.f; + msg->inject_touch_event.buttons = + convert_mouse_buttons(event->buttons_state); return true; } static bool -convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, - struct control_msg *to) { - - // mouse_x and mouse_y are expressed in pixels relative to the window - int mouse_x; - int mouse_y; - SDL_GetMouseState(&mouse_x, &mouse_y); - - struct sc_position position = { - .screen_size = screen->frame_size, - .point = screen_convert_window_to_frame_coords(screen, - mouse_x, mouse_y), - }; - - to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; - - to->inject_scroll_event.position = position; - to->inject_scroll_event.hscroll = from->x; - to->inject_scroll_event.vscroll = from->y; +convert_mouse_scroll(const struct sc_mouse_scroll_event *event, + struct control_msg *msg) { + msg->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; + msg->inject_scroll_event.position = event->position; + msg->inject_scroll_event.hscroll = event->hscroll; + msg->inject_scroll_event.vscroll = event->vscroll; return true; } static void sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, - const SDL_MouseMotionEvent *event) { + const struct sc_mouse_motion_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (!convert_mouse_motion(event, mi->screen, &msg)) { + if (!convert_mouse_motion(event, &msg)) { return; } @@ -170,11 +145,11 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, static void sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, - const SDL_TouchFingerEvent *event) { + const struct sc_touch_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_touch(event, mi->screen, &msg)) { + if (convert_touch(event, &msg)) { if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject touch event'"); } @@ -182,42 +157,41 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, } static void -sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp, - const SDL_MouseButtonEvent *event) { +sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_mouse_button(event, mi->screen, &msg)) { + if (convert_mouse_click(event, &msg)) { if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject mouse button event'"); + LOGW("Could not request 'inject mouse click event'"); } } } static void -sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp, - const SDL_MouseWheelEvent *event) { +sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_mouse_wheel(event, mi->screen, &msg)) { + if (convert_mouse_scroll(event, &msg)) { if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject mouse wheel event'"); + LOGW("Could not request 'inject mouse scroll event'"); } } } void -sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, - struct screen *screen) { +sc_mouse_inject_init(struct sc_mouse_inject *mi, + struct controller *controller) { mi->controller = controller; - mi->screen = screen; static const struct sc_mouse_processor_ops ops = { .process_mouse_motion = sc_mouse_processor_process_mouse_motion, .process_touch = sc_mouse_processor_process_touch, - .process_mouse_button = sc_mouse_processor_process_mouse_button, - .process_mouse_wheel = sc_mouse_processor_process_mouse_wheel, + .process_mouse_click = sc_mouse_processor_process_mouse_click, + .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, }; mi->mouse_processor.ops = &ops; diff --git a/app/src/mouse_inject.h b/app/src/mouse_inject.h index 7dcf7e83..50591e2b 100644 --- a/app/src/mouse_inject.h +++ b/app/src/mouse_inject.h @@ -13,11 +13,9 @@ struct sc_mouse_inject { struct sc_mouse_processor mouse_processor; // mouse processor trait struct controller *controller; - struct screen *screen; }; void -sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller, - struct screen *screen); +sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3a271ee5..51eb19fb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -578,7 +578,7 @@ aoa_hid_end: kp = &s->keyboard_inject.key_processor; } - sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen); + sc_mouse_inject_init(&s->mouse_inject, &s->controller); mp = &s->mouse_inject.mouse_processor; } diff --git a/app/src/trait/mouse_processor.h b/app/src/trait/mouse_processor.h index f3548574..e06545bf 100644 --- a/app/src/trait/mouse_processor.h +++ b/app/src/trait/mouse_processor.h @@ -6,7 +6,7 @@ #include #include -#include +#include "input_events.h" /** * Mouse processor trait. @@ -21,19 +21,19 @@ struct sc_mouse_processor { struct sc_mouse_processor_ops { void (*process_mouse_motion)(struct sc_mouse_processor *mp, - const SDL_MouseMotionEvent *event); + const struct sc_mouse_motion_event *event); void (*process_touch)(struct sc_mouse_processor *mp, - const SDL_TouchFingerEvent *event); + const struct sc_touch_event *event); void - (*process_mouse_button)(struct sc_mouse_processor *mp, - const SDL_MouseButtonEvent *event); + (*process_mouse_click)(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event); void - (*process_mouse_wheel)(struct sc_mouse_processor *mp, - const SDL_MouseWheelEvent *event); + (*process_mouse_scroll)(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event); }; #endif From a1f2f5fbd3591be594e1b70fe08f05e7e562c123 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 16:55:01 +0100 Subject: [PATCH 0257/1133] Make some event conversions infallible When the implementation handles all possible input values, it may never fail. --- app/src/keyboard_inject.c | 24 +++------ app/src/mouse_inject.c | 100 +++++++++++++------------------------- 2 files changed, 42 insertions(+), 82 deletions(-) diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 968a3c4f..76357d85 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -12,20 +12,13 @@ /** Downcast key processor to sc_keyboard_inject */ #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) -static bool -convert_keycode_action(enum sc_action from, enum android_keyevent_action *to) { - static const struct sc_intmap_entry actions[] = { - {SC_ACTION_DOWN, AKEY_EVENT_ACTION_DOWN}, - {SC_ACTION_UP, AKEY_EVENT_ACTION_UP}, - }; - - const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); - if (entry) { - *to = entry->value; - return true; +static enum android_keyevent_action +convert_keycode_action(enum sc_action action) { + if (action == SC_ACTION_DOWN) { + return AKEY_EVENT_ACTION_DOWN; } - - return false; + assert(action == SC_ACTION_UP); + return AKEY_EVENT_ACTION_UP; } static bool @@ -257,15 +250,12 @@ convert_input_key(const struct sc_key_event *event, struct control_msg *msg, enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; - if (!convert_keycode_action(event->action, &msg->inject_keycode.action)) { - return false; - } - if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode, event->mods_state, key_inject_mode)) { return false; } + msg->inject_keycode.action = convert_keycode_action(event->action); msg->inject_keycode.repeat = repeat; msg->inject_keycode.metastate = convert_meta_state(event->mods_state); diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index a4d2e1a7..99b0b858 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -33,41 +33,29 @@ convert_mouse_buttons(uint32_t state) { return buttons; } -static bool -convert_mouse_action(enum sc_action from, enum android_motionevent_action *to) { - static const struct sc_intmap_entry actions[] = { - {SC_ACTION_DOWN, AMOTION_EVENT_ACTION_DOWN}, - {SC_ACTION_UP, AMOTION_EVENT_ACTION_UP}, - }; - - const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); - if (entry) { - *to = entry->value; - return true; +static enum android_motionevent_action +convert_mouse_action(enum sc_action action) { + if (action == SC_ACTION_DOWN) { + return AMOTION_EVENT_ACTION_DOWN; } - - return false; + assert(action == SC_ACTION_UP); + return AMOTION_EVENT_ACTION_UP; } -static bool -convert_touch_action(enum sc_touch_action from, - enum android_motionevent_action *to) { - static const struct sc_intmap_entry actions[] = { - {SC_TOUCH_ACTION_MOVE, AMOTION_EVENT_ACTION_MOVE}, - {SC_TOUCH_ACTION_DOWN, AMOTION_EVENT_ACTION_DOWN}, - {SC_TOUCH_ACTION_UP, AMOTION_EVENT_ACTION_UP}, - }; - - const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from); - if (entry) { - *to = entry->value; - return true; +static enum android_motionevent_action +convert_touch_action(enum sc_touch_action action) { + switch (action) { + case SC_TOUCH_ACTION_MOVE: + return AMOTION_EVENT_ACTION_MOVE; + case SC_TOUCH_ACTION_DOWN: + return AMOTION_EVENT_ACTION_DOWN; + default: + assert(action == SC_TOUCH_ACTION_UP); + return AMOTION_EVENT_ACTION_UP; } - - return false; } -static bool +static void convert_mouse_motion(const struct sc_mouse_motion_event *event, struct control_msg *msg) { msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; @@ -77,55 +65,39 @@ convert_mouse_motion(const struct sc_mouse_motion_event *event, msg->inject_touch_event.pressure = 1.f; msg->inject_touch_event.buttons = convert_mouse_buttons(event->buttons_state); - - return true; } -static bool +static void convert_touch(const struct sc_touch_event *event, struct control_msg *msg) { msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - - if (!convert_touch_action(event->action, &msg->inject_touch_event.action)) { - return false; - } - + msg->inject_touch_event.action = convert_touch_action(event->action); msg->inject_touch_event.pointer_id = event->pointer_id; msg->inject_touch_event.position = event->position; msg->inject_touch_event.pressure = event->pressure; msg->inject_touch_event.buttons = 0; - - return true; } -static bool +static void convert_mouse_click(const struct sc_mouse_click_event *event, struct control_msg *msg) { msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - - if (!convert_mouse_action(event->action, &msg->inject_touch_event.action)) { - return false; - } - + msg->inject_touch_event.action = convert_mouse_action(event->action); msg->inject_touch_event.pointer_id = POINTER_ID_MOUSE; msg->inject_touch_event.position = event->position; msg->inject_touch_event.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f; msg->inject_touch_event.buttons = convert_mouse_buttons(event->buttons_state); - - return true; } -static bool +static void convert_mouse_scroll(const struct sc_mouse_scroll_event *event, struct control_msg *msg) { msg->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; msg->inject_scroll_event.position = event->position; msg->inject_scroll_event.hscroll = event->hscroll; msg->inject_scroll_event.vscroll = event->vscroll; - - return true; } static void @@ -134,9 +106,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (!convert_mouse_motion(event, &msg)) { - return; - } + convert_mouse_motion(event, &msg); if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse motion event'"); @@ -149,10 +119,10 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_touch(event, &msg)) { - if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject touch event'"); - } + convert_touch(event, &msg); + + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject touch event'"); } } @@ -162,10 +132,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_mouse_click(event, &msg)) { - if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject mouse click event'"); - } + convert_mouse_click(event, &msg); + + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject mouse click event'"); } } @@ -175,10 +145,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg; - if (convert_mouse_scroll(event, &msg)) { - if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject mouse scroll event'"); - } + convert_mouse_scroll(event, &msg); + + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject mouse scroll event'"); } } From 96e0e8974065312305642cbd2bc203d5f48422cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 17:10:12 +0100 Subject: [PATCH 0258/1133] Simplify mouse injection implementation The static functions are now so simple that they become unnecessary: the control message may be initialized directly instead. --- app/src/mouse_inject.c | 91 ++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 99b0b858..887fc0b1 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -55,58 +55,21 @@ convert_touch_action(enum sc_touch_action action) { } } -static void -convert_mouse_motion(const struct sc_mouse_motion_event *event, - struct control_msg *msg) { - msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - msg->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; - msg->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - msg->inject_touch_event.position = event->position; - msg->inject_touch_event.pressure = 1.f; - msg->inject_touch_event.buttons = - convert_mouse_buttons(event->buttons_state); -} - -static void -convert_touch(const struct sc_touch_event *event, - struct control_msg *msg) { - msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - msg->inject_touch_event.action = convert_touch_action(event->action); - msg->inject_touch_event.pointer_id = event->pointer_id; - msg->inject_touch_event.position = event->position; - msg->inject_touch_event.pressure = event->pressure; - msg->inject_touch_event.buttons = 0; -} - -static void -convert_mouse_click(const struct sc_mouse_click_event *event, - struct control_msg *msg) { - msg->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; - msg->inject_touch_event.action = convert_mouse_action(event->action); - msg->inject_touch_event.pointer_id = POINTER_ID_MOUSE; - msg->inject_touch_event.position = event->position; - msg->inject_touch_event.pressure = event->action == SC_ACTION_DOWN - ? 1.f : 0.f; - msg->inject_touch_event.buttons = - convert_mouse_buttons(event->buttons_state); -} - -static void -convert_mouse_scroll(const struct sc_mouse_scroll_event *event, - struct control_msg *msg) { - msg->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; - msg->inject_scroll_event.position = event->position; - msg->inject_scroll_event.hscroll = event->hscroll; - msg->inject_scroll_event.vscroll = event->vscroll; -} - static void sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, const struct sc_mouse_motion_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg; - convert_mouse_motion(event, &msg); + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .inject_touch_event = { + .action = AMOTION_EVENT_ACTION_MOVE, + .pointer_id = POINTER_ID_MOUSE, + .position = event->position, + .pressure = 1.f, + .buttons = convert_mouse_buttons(event->buttons_state), + }, + }; if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse motion event'"); @@ -118,8 +81,16 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, const struct sc_touch_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg; - convert_touch(event, &msg); + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .inject_touch_event = { + .action = convert_touch_action(event->action), + .pointer_id = event->pointer_id, + .position = event->position, + .pressure = event->pressure, + .buttons = 0, + }, + }; if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject touch event'"); @@ -131,8 +102,16 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg; - convert_mouse_click(event, &msg); + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .inject_touch_event = { + .action = convert_mouse_action(event->action), + .pointer_id = POINTER_ID_MOUSE, + .position = event->position, + .pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f, + .buttons = convert_mouse_buttons(event->buttons_state), + }, + }; if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse click event'"); @@ -144,8 +123,14 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, const struct sc_mouse_scroll_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg; - convert_mouse_scroll(event, &msg); + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, + .inject_scroll_event = { + .position = event->position, + .hscroll = event->hscroll, + .vscroll = event->vscroll, + }, + }; if (!controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse scroll event'"); From 3c15cbdaf89d352c0b2e79debe9ae04a8100e8b1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 23:20:35 +0100 Subject: [PATCH 0259/1133] Reorder mouse processor ops Group the mouse events callbacks before the touch event callback. --- app/src/mouse_inject.c | 44 ++++++++++++++++----------------- app/src/trait/mouse_processor.h | 8 +++--- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 887fc0b1..1d14509d 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -76,27 +76,6 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, } } -static void -sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, - const struct sc_touch_event *event) { - struct sc_mouse_inject *mi = DOWNCAST(mp); - - struct control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, - .inject_touch_event = { - .action = convert_touch_action(event->action), - .pointer_id = event->pointer_id, - .position = event->position, - .pressure = event->pressure, - .buttons = 0, - }, - }; - - if (!controller_push_msg(mi->controller, &msg)) { - LOGW("Could not request 'inject touch event'"); - } -} - static void sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event) { @@ -137,6 +116,27 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, } } +static void +sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, + const struct sc_touch_event *event) { + struct sc_mouse_inject *mi = DOWNCAST(mp); + + struct control_msg msg = { + .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .inject_touch_event = { + .action = convert_touch_action(event->action), + .pointer_id = event->pointer_id, + .position = event->position, + .pressure = event->pressure, + .buttons = 0, + }, + }; + + if (!controller_push_msg(mi->controller, &msg)) { + LOGW("Could not request 'inject touch event'"); + } +} + void sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller) { @@ -144,9 +144,9 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi, static const struct sc_mouse_processor_ops ops = { .process_mouse_motion = sc_mouse_processor_process_mouse_motion, - .process_touch = sc_mouse_processor_process_touch, .process_mouse_click = sc_mouse_processor_process_mouse_click, .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, + .process_touch = sc_mouse_processor_process_touch, }; mi->mouse_processor.ops = &ops; diff --git a/app/src/trait/mouse_processor.h b/app/src/trait/mouse_processor.h index e06545bf..b08d15bb 100644 --- a/app/src/trait/mouse_processor.h +++ b/app/src/trait/mouse_processor.h @@ -23,10 +23,6 @@ struct sc_mouse_processor_ops { (*process_mouse_motion)(struct sc_mouse_processor *mp, const struct sc_mouse_motion_event *event); - void - (*process_touch)(struct sc_mouse_processor *mp, - const struct sc_touch_event *event); - void (*process_mouse_click)(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event); @@ -34,6 +30,10 @@ struct sc_mouse_processor_ops { void (*process_mouse_scroll)(struct sc_mouse_processor *mp, const struct sc_mouse_scroll_event *event); + + void + (*process_touch)(struct sc_mouse_processor *mp, + const struct sc_touch_event *event); }; #endif From 63e29b1782f18606984550c151a41209c4c809af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Dec 2021 23:52:08 +0100 Subject: [PATCH 0260/1133] Apply buttons mask if not --forward-all-clicks If --forward-all-clicks is not set, then only left clicks are forwarded. For consistency, also mask the buttons state in other events. --- app/src/input_manager.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index ba6a4e2a..4dc8c4eb 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -63,9 +63,19 @@ sc_mouse_button_from_sdl(uint8_t button) { } static inline uint8_t -sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) { +sc_mouse_buttons_state_from_sdl(uint32_t buttons_state, + bool forward_all_clicks) { assert(buttons_state < 0x100); // fits in uint8_t - return buttons_state; + + uint8_t mask = SC_MOUSE_BUTTON_LEFT; + if (forward_all_clicks) { + mask |= SC_MOUSE_BUTTON_RIGHT + | SC_MOUSE_BUTTON_MIDDLE + | SC_MOUSE_BUTTON_X1 + | SC_MOUSE_BUTTON_X2; + } + + return buttons_state & mask; } #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) @@ -616,6 +626,7 @@ input_manager_process_key(struct input_manager *im, static void input_manager_process_mouse_motion(struct input_manager *im, const SDL_MouseMotionEvent *event) { + uint32_t mask = SDL_BUTTON_LMASK; if (im->forward_all_clicks) { mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK; @@ -635,7 +646,9 @@ input_manager_process_mouse_motion(struct input_manager *im, .point = screen_convert_window_to_frame_coords(im->screen, event->x, event->y), }, - .buttons_state = sc_mouse_buttons_state_from_sdl(event->state), + .buttons_state = + sc_mouse_buttons_state_from_sdl(event->state, + im->forward_all_clicks), }; im->mp->ops->process_mouse_motion(im->mp, &evt); @@ -740,7 +753,9 @@ input_manager_process_mouse_button(struct input_manager *im, }, .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), - .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state), + .buttons_state = + sc_mouse_buttons_state_from_sdl(sdl_buttons_state, + im->forward_all_clicks), }; im->mp->ops->process_mouse_click(im->mp, &evt); From bc674721dcc9b5b5461443c1b519333b4585fa7d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Dec 2021 15:03:39 +0100 Subject: [PATCH 0261/1133] Make process_text() optional Not all key processors support text injection (HID keyboard does not support it). Instead of providing a dummy op function, set it to NULL and check on the caller side before calling it. --- app/src/hid_keyboard.c | 13 +++---------- app/src/input_manager.c | 6 ++++++ app/src/trait/key_processor.h | 9 ++++++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index be5f8a19..4a0e64ba 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -387,15 +387,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } } -static void -sc_key_processor_process_text(struct sc_key_processor *kp, - const struct sc_text_event *event) { - (void) kp; - (void) event; - - // Never forward text input via HID (all the keys are injected separately) -} - bool sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { kb->aoa = aoa; @@ -415,7 +406,9 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { static const struct sc_key_processor_ops ops = { .process_key = sc_key_processor_process_key, - .process_text = sc_key_processor_process_text, + // Never forward text input via HID (all the keys are injected + // separately) + .process_text = NULL, }; // Clipboard synchronization is requested over the control socket, while HID diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 4dc8c4eb..aa0708c0 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -384,6 +384,11 @@ rotate_client_right(struct screen *screen) { static void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event) { + if (!im->kp->ops->process_text) { + // The key processor does not support text input + return; + } + if (is_shortcut_mod(im, SDL_GetModState())) { // A shortcut must never generate text events return; @@ -620,6 +625,7 @@ input_manager_process_key(struct input_manager *im, .mods_state = sc_mods_state_from_sdl(event->keysym.mod), }; + assert(im->kp->ops->process_key); im->kp->ops->process_key(im->kp, &evt, ack_to_wait); } diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index 045e5e7b..8c51b11d 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -29,17 +29,24 @@ struct sc_key_processor { struct sc_key_processor_ops { /** - * Process the keyboard event + * Process a keyboard event * * The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates * the acknowledgement number to wait for before injecting this event. * This allows to ensure that the device clipboard is set before injecting * Ctrl+v on the device. + * + * This function is mandatory. */ void (*process_key)(struct sc_key_processor *kp, const struct sc_key_event *event, uint64_t ack_to_wait); + /** + * Process an input text + * + * This function is optional. + */ void (*process_text)(struct sc_key_processor *kp, const struct sc_text_event *event); From 57f1655d4bb18452615f59d6bb8058bc8d5adfc7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Dec 2021 15:08:42 +0100 Subject: [PATCH 0262/1133] Make some mouse processors ops optional Do not force all mouse processors to implement scroll events or touch events. --- app/src/input_manager.c | 12 ++++++++++++ app/src/trait/mouse_processor.h | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index aa0708c0..dfd78484 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -657,6 +657,7 @@ input_manager_process_mouse_motion(struct input_manager *im, im->forward_all_clicks), }; + assert(im->mp->ops->process_mouse_motion); im->mp->ops->process_mouse_motion(im->mp, &evt); if (im->vfinger_down) { @@ -671,6 +672,11 @@ input_manager_process_mouse_motion(struct input_manager *im, static void input_manager_process_touch(struct input_manager *im, const SDL_TouchFingerEvent *event) { + if (!im->mp->ops->process_touch) { + // The mouse processor does not support touch events + return; + } + int dw; int dh; SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh); @@ -764,6 +770,7 @@ input_manager_process_mouse_button(struct input_manager *im, im->forward_all_clicks), }; + assert(im->mp->ops->process_mouse_click); im->mp->ops->process_mouse_click(im->mp, &evt); // Pinch-to-zoom simulation. @@ -795,6 +802,11 @@ input_manager_process_mouse_button(struct input_manager *im, static void input_manager_process_mouse_wheel(struct input_manager *im, const SDL_MouseWheelEvent *event) { + if (!im->mp->ops->process_mouse_scroll) { + // The mouse processor does not support scroll events + return; + } + // mouse_x and mouse_y are expressed in pixels relative to the window int mouse_x; int mouse_y; diff --git a/app/src/trait/mouse_processor.h b/app/src/trait/mouse_processor.h index b08d15bb..0252d2c6 100644 --- a/app/src/trait/mouse_processor.h +++ b/app/src/trait/mouse_processor.h @@ -19,18 +19,38 @@ struct sc_mouse_processor { }; struct sc_mouse_processor_ops { + /** + * Process a mouse motion event + * + * This function is mandatory. + */ void (*process_mouse_motion)(struct sc_mouse_processor *mp, const struct sc_mouse_motion_event *event); + /** + * Process a mouse click event + * + * This function is mandatory. + */ void (*process_mouse_click)(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event); + /** + * Process a mouse scroll event + * + * This function is optional. + */ void (*process_mouse_scroll)(struct sc_mouse_processor *mp, const struct sc_mouse_scroll_event *event); + /** + * Process a touch event + * + * This function is optional. + */ void (*process_touch)(struct sc_mouse_processor *mp, const struct sc_touch_event *event); From cca3c953dae05493f7a076b6d8b1de6bf22d56f9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 23:59:20 +0100 Subject: [PATCH 0263/1133] Enable virtual finger only on left click The pinch-to-zoom feature must only be enabled with Ctrl+left_click. --- app/src/input_manager.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index dfd78484..34bab246 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -783,8 +783,9 @@ input_manager_process_mouse_button(struct input_manager *im, // In other words, the center of the rotation/scaling is the center of the // screen. #define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) - if ((down && !im->vfinger_down && CTRL_PRESSED) - || (!down && im->vfinger_down)) { + if (event->button == SDL_BUTTON_LEFT && + ((down && !im->vfinger_down && CTRL_PRESSED) || + (!down && im->vfinger_down))) { struct sc_point mouse = screen_convert_window_to_frame_coords(im->screen, event->x, event->y); From a9d23400cdb763703a6d757b0c445e7700271abb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 4 Jan 2022 15:11:33 +0100 Subject: [PATCH 0264/1133] Remove unused enum value requiring SDL 2.0.18 Refs b8fed50639fe3bf8e02a8cc2409494cd6bf30fb7 Fixes #2924 --- app/src/input_events.h | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/input_events.h b/app/src/input_events.h index b40995e6..48c51ee7 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -54,7 +54,6 @@ enum sc_mod { SC_MOD_NUM = KMOD_NUM, SC_MOD_CAPS = KMOD_CAPS, - SC_MOD_SCROLL = KMOD_SCROLL, }; enum sc_action { From 2b34e1224e97e21ca04ce751bf513d85fbcc4a45 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Dec 2021 16:15:41 +0100 Subject: [PATCH 0265/1133] Use separate struct for input manager params This avoids to directly pass the options instance (which contains more data than strictly necessary), and limit the number of parameters for the init function. --- app/src/input_manager.c | 34 ++++++++++++++++------------------ app/src/input_manager.h | 19 +++++++++++++++---- app/src/scrcpy.c | 15 +++++++++++++-- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 34bab246..ce138012 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -121,24 +121,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { } void -input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, struct sc_key_processor *kp, - struct sc_mouse_processor *mp, - const struct scrcpy_options *options) { - assert(!options->control || (kp && kp->ops)); - assert(!options->control || (mp && mp->ops)); - - im->controller = controller; - im->screen = screen; - im->kp = kp; - im->mp = mp; - - im->control = options->control; - im->forward_all_clicks = options->forward_all_clicks; - im->legacy_paste = options->legacy_paste; - im->clipboard_autosync = options->clipboard_autosync; - - const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods; +input_manager_init(struct input_manager *im, + const struct input_manager_params *params) { + assert(!params->control || (params->kp && params->kp->ops)); + assert(!params->control || (params->mp && params->mp->ops)); + + im->controller = params->controller; + im->screen = params->screen; + im->kp = params->kp; + im->mp = params->mp; + + im->control = params->control; + im->forward_all_clicks = params->forward_all_clicks; + im->legacy_paste = params->legacy_paste; + im->clipboard_autosync = params->clipboard_autosync; + + const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods; assert(shortcut_mods->count); assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); for (unsigned i = 0; i < shortcut_mods->count; ++i) { diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 5e02b457..38d0d703 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -43,11 +43,22 @@ struct input_manager { uint64_t next_sequence; // used for request acknowledgements }; +struct input_manager_params { + struct controller *controller; + struct screen *screen; + struct sc_key_processor *kp; + struct sc_mouse_processor *mp; + + bool control; + bool forward_all_clicks; + bool legacy_paste; + bool clipboard_autosync; + const struct sc_shortcut_mods *shortcut_mods; +}; + void -input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, struct sc_key_processor *kp, - struct sc_mouse_processor *mp, - const struct scrcpy_options *options); +input_manager_init(struct input_manager *im, + const struct input_manager_params *params); bool input_manager_handle_event(struct input_manager *im, SDL_Event *event); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 51eb19fb..972e2d99 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -582,8 +582,19 @@ aoa_hid_end: mp = &s->mouse_inject.mouse_processor; } - input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp, - options); + struct input_manager_params im_params = { + .controller = &s->controller, + .screen = &s->screen, + .kp = kp, + .mp = mp, + .control = options->control, + .forward_all_clicks = options->forward_all_clicks, + .legacy_paste = options->legacy_paste, + .clipboard_autosync = options->clipboard_autosync, + .shortcut_mods = &options->shortcut_mods, + }; + + input_manager_init(&s->input_manager, &im_params); ret = event_loop(s, options); LOGD("quit..."); From 6102a0b5bb5b7c8473c407592707539be48da19e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Dec 2021 16:32:07 +0100 Subject: [PATCH 0266/1133] Move input_manager into screen The input_manager is strongly tied to the screen, it could not work independently of the specific screen implementation. To implement a user-friendly HID mouse behavior, some SDL events will need to be handled both by the screen and by the input manager. For example, a click must typically be handled by the input_manager so that it is forwarded to the device, but in HID mouse mode, the first click should be handled by the screen to capture the mouse (enable relative mouse mode). Make the input_manager a descendant of the screen, so that the screen decides what to do on SDL events. Concretely, replace this structure hierarchy: +- struct scrcpy +- struct input_manager +- struct screen by this one: +- struct scrcpy +- struct screen +- struct input_manager --- app/src/input_manager.c | 1 + app/src/input_manager.h | 1 - app/src/scrcpy.c | 127 ++++++++++++++++++---------------------- app/src/screen.c | 16 ++++- app/src/screen.h | 15 +++++ 5 files changed, 87 insertions(+), 73 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index ce138012..ec91787a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -4,6 +4,7 @@ #include #include "input_events.h" +#include "screen.h" #include "util/log.h" static inline uint16_t diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 38d0d703..088406f6 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -10,7 +10,6 @@ #include "controller.h" #include "fps_counter.h" #include "options.h" -#include "screen.h" #include "trait/key_processor.h" #include "trait/mouse_processor.h" diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 972e2d99..5370f448 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -17,7 +17,6 @@ #include "decoder.h" #include "events.h" #include "file_handler.h" -#include "input_manager.h" #ifdef HAVE_AOA_HID # include "hid_keyboard.h" #endif @@ -57,7 +56,6 @@ struct scrcpy { #endif }; struct sc_mouse_inject mouse_inject; - struct input_manager input_manager; }; static inline void @@ -189,11 +187,6 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, } bool consumed = screen_handle_event(&s->screen, event); - if (consumed) { - goto end; - } - - consumed = input_manager_handle_event(&s->input_manager, event); (void) consumed; end: @@ -450,6 +443,9 @@ scrcpy(struct scrcpy_options *options) { stream_add_sink(&s->stream, &rec->packet_sink); } + struct sc_key_processor *kp = NULL; + struct sc_mouse_processor *mp = NULL; + if (options->control) { #ifdef HAVE_AOA_HID if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { @@ -481,59 +477,7 @@ scrcpy(struct scrcpy_options *options) { LOGW("Could not request 'set screen power mode'"); } } - } - - if (options->display) { - const char *window_title = - options->window_title ? options->window_title : info->device_name; - - struct screen_params screen_params = { - .window_title = window_title, - .frame_size = info->frame_size, - .always_on_top = options->always_on_top, - .window_x = options->window_x, - .window_y = options->window_y, - .window_width = options->window_width, - .window_height = options->window_height, - .window_borderless = options->window_borderless, - .rotation = options->rotation, - .mipmaps = options->mipmaps, - .fullscreen = options->fullscreen, - .buffering_time = options->display_buffer, - }; - - if (!screen_init(&s->screen, &screen_params)) { - goto end; - } - screen_initialized = true; - - decoder_add_sink(&s->decoder, &s->screen.frame_sink); - } - -#ifdef HAVE_V4L2 - if (options->v4l2_device) { - if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, - info->frame_size, options->v4l2_buffer)) { - goto end; - } - - decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); - - v4l2_sink_initialized = true; - } -#endif - - // now we consumed the header values, the socket receives the video stream - // start the stream - if (!stream_start(&s->stream)) { - goto end; - } - stream_started = true; - - struct sc_key_processor *kp = NULL; - struct sc_mouse_processor *mp = NULL; - if (options->control) { #ifdef HAVE_AOA_HID if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { bool aoa_hid_ok = false; @@ -582,19 +526,60 @@ aoa_hid_end: mp = &s->mouse_inject.mouse_processor; } - struct input_manager_params im_params = { - .controller = &s->controller, - .screen = &s->screen, - .kp = kp, - .mp = mp, - .control = options->control, - .forward_all_clicks = options->forward_all_clicks, - .legacy_paste = options->legacy_paste, - .clipboard_autosync = options->clipboard_autosync, - .shortcut_mods = &options->shortcut_mods, - }; + if (options->display) { + const char *window_title = + options->window_title ? options->window_title : info->device_name; - input_manager_init(&s->input_manager, &im_params); + struct screen_params screen_params = { + .controller = &s->controller, + .kp = kp, + .mp = mp, + .control = options->control, + .forward_all_clicks = options->forward_all_clicks, + .legacy_paste = options->legacy_paste, + .clipboard_autosync = options->clipboard_autosync, + .shortcut_mods = &options->shortcut_mods, + .window_title = window_title, + .frame_size = info->frame_size, + .always_on_top = options->always_on_top, + .window_x = options->window_x, + .window_y = options->window_y, + .window_width = options->window_width, + .window_height = options->window_height, + .window_borderless = options->window_borderless, + .rotation = options->rotation, + .mipmaps = options->mipmaps, + .fullscreen = options->fullscreen, + .buffering_time = options->display_buffer, + }; + + if (!screen_init(&s->screen, &screen_params)) { + goto end; + } + screen_initialized = true; + + decoder_add_sink(&s->decoder, &s->screen.frame_sink); + } + +#ifdef HAVE_V4L2 + if (options->v4l2_device) { + if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, + info->frame_size, options->v4l2_buffer)) { + goto end; + } + + decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); + + v4l2_sink_initialized = true; + } +#endif + + // now we consumed the header values, the socket receives the video stream + // start the stream + if (!stream_start(&s->stream)) { + goto end; + } + stream_started = true; ret = event_loop(s, options); LOGD("quit..."); diff --git a/app/src/screen.c b/app/src/screen.c index 0cb09a4b..9ad81cb8 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -470,6 +470,20 @@ screen_init(struct screen *screen, const struct screen_params *params) { goto error_destroy_texture; } + struct input_manager_params im_params = { + .controller = params->controller, + .screen = screen, + .kp = params->kp, + .mp = params->mp, + .control = params->control, + .forward_all_clicks = params->forward_all_clicks, + .legacy_paste = params->legacy_paste, + .clipboard_autosync = params->clipboard_autosync, + .shortcut_mods = params->shortcut_mods, + }; + + input_manager_init(&screen->im, &im_params); + // Reset the window size to trigger a SIZE_CHANGED event, to workaround // HiDPI issues with some SDL renderers when several displays having // different HiDPI scaling are connected @@ -773,7 +787,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { return true; } - return false; + return input_manager_handle_event(&screen->im, event); } struct sc_point diff --git a/app/src/screen.h b/app/src/screen.h index b82bf631..bc7696f1 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -7,10 +7,14 @@ #include #include +#include "controller.h" #include "coords.h" #include "fps_counter.h" +#include "input_manager.h" #include "opengl.h" +#include "trait/key_processor.h" #include "trait/frame_sink.h" +#include "trait/mouse_processor.h" #include "video_buffer.h" struct screen { @@ -20,6 +24,7 @@ struct screen { bool open; // track the open/close state to assert correct behavior #endif + struct input_manager im; struct sc_video_buffer vb; struct fps_counter fps_counter; @@ -50,6 +55,16 @@ struct screen { }; struct screen_params { + struct controller *controller; + struct sc_key_processor *kp; + struct sc_mouse_processor *mp; + + bool control; + bool forward_all_clicks; + bool legacy_paste; + bool clipboard_autosync; + const struct sc_shortcut_mods *shortcut_mods; + const char *window_title; struct sc_size frame_size; bool always_on_top; From 5ce1ccde8518295ddd67179e1ed658f618bea296 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 19:38:27 +0100 Subject: [PATCH 0267/1133] Reorder controller and HID initialization This allows to merge two "#ifdef HAVE_AOA_HID" blocks to simplify. --- app/src/scrcpy.c | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5370f448..fad7049b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -455,34 +455,10 @@ scrcpy(struct scrcpy_options *options) { } acksync = &s->acksync; - } -#endif - if (!controller_init(&s->controller, s->server.control_socket, - acksync)) { - goto end; - } - controller_initialized = true; - - if (!controller_start(&s->controller)) { - goto end; - } - controller_started = true; - if (options->turn_screen_off) { - struct control_msg msg; - msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; - msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; - - if (!controller_push_msg(&s->controller, &msg)) { - LOGW("Could not request 'set screen power mode'"); - } - } - -#ifdef HAVE_AOA_HID - if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { bool aoa_hid_ok = false; - bool ok = sc_aoa_init(&s->aoa, serial, acksync); + ok = sc_aoa_init(&s->aoa, serial, acksync); if (!ok) { goto aoa_hid_end; } @@ -524,6 +500,28 @@ aoa_hid_end: sc_mouse_inject_init(&s->mouse_inject, &s->controller); mp = &s->mouse_inject.mouse_processor; + + if (!controller_init(&s->controller, s->server.control_socket, + acksync)) { + goto end; + } + controller_initialized = true; + + if (!controller_start(&s->controller)) { + goto end; + } + controller_started = true; + + if (options->turn_screen_off) { + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; + msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; + + if (!controller_push_msg(&s->controller, &msg)) { + LOGW("Could not request 'set screen power mode'"); + } + } + } if (options->display) { From f04812fc714f2fdb6b5ebadabc1007cc4895f70d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 19:45:50 +0100 Subject: [PATCH 0268/1133] Remove duplicate boolean The AOA initialization state is already tracked by aoa_hid_initialized. --- app/src/scrcpy.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index fad7049b..52799da4 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -456,8 +456,6 @@ scrcpy(struct scrcpy_options *options) { acksync = &s->acksync; - bool aoa_hid_ok = false; - ok = sc_aoa_init(&s->aoa, serial, acksync); if (!ok) { goto aoa_hid_end; @@ -474,13 +472,12 @@ scrcpy(struct scrcpy_options *options) { goto aoa_hid_end; } - aoa_hid_ok = true; kp = &s->keyboard_hid.key_processor; aoa_hid_initialized = true; aoa_hid_end: - if (!aoa_hid_ok) { + if (!aoa_hid_initialized) { LOGE("Failed to enable HID over AOA, " "fallback to default keyboard injection method " "(-K/--hid-keyboard ignored)"); From 7121a0dc5310182cc1b003e40d36bd81bc1f9471 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 19:47:47 +0100 Subject: [PATCH 0269/1133] Destroy acksync immediately on error If AOA or HID keyboard may not be initialized for some reason, acksync is useless. --- app/src/scrcpy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 52799da4..6b8074e9 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -454,25 +454,27 @@ scrcpy(struct scrcpy_options *options) { goto end; } - acksync = &s->acksync; - ok = sc_aoa_init(&s->aoa, serial, acksync); if (!ok) { + sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { + sc_acksync_destroy(&s->acksync); sc_aoa_destroy(&s->aoa); goto aoa_hid_end; } if (!sc_aoa_start(&s->aoa)) { + sc_acksync_destroy(&s->acksync); sc_hid_keyboard_destroy(&s->keyboard_hid); sc_aoa_destroy(&s->aoa); goto aoa_hid_end; } kp = &s->keyboard_hid.key_processor; + acksync = &s->acksync; aoa_hid_initialized = true; From 924375487e63a5a9114182015a68aedef2f52d14 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Dec 2021 10:38:05 +0100 Subject: [PATCH 0270/1133] Pass buttons state in scroll events A scroll event might be produced when a mouse button is pressed (for example when scrolling while selecting a text). For consistency, pass the actual buttons state (instead of 0). In practice, it seems that this use case does not work properly with Android event injection, but it will work with HID mouse. --- app/src/control_msg.c | 8 +++++--- app/src/control_msg.h | 1 + app/src/input_events.h | 1 + app/src/input_manager.c | 4 +++- app/src/mouse_inject.c | 1 + app/tests/test_control_msg_serialize.c | 4 +++- .../main/java/com/genymobile/scrcpy/ControlMessage.java | 3 ++- .../java/com/genymobile/scrcpy/ControlMessageReader.java | 5 +++-- .../src/main/java/com/genymobile/scrcpy/Controller.java | 6 +++--- .../com/genymobile/scrcpy/ControlMessageReaderTest.java | 2 ++ 10 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 6ccdc054..75c74628 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -119,7 +119,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { (uint32_t) msg->inject_scroll_event.hscroll); buffer_write32be(&buf[17], (uint32_t) msg->inject_scroll_event.vscroll); - return 21; + buffer_write32be(&buf[21], msg->inject_scroll_event.buttons); + return 25; case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: buf[1] = msg->inject_keycode.action; return 2; @@ -192,11 +193,12 @@ control_msg_log(const struct control_msg *msg) { } case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 - " vscroll=%" PRIi32, + " vscroll=%" PRIi32 " buttons=%06lx", msg->inject_scroll_event.position.point.x, msg->inject_scroll_event.position.point.y, msg->inject_scroll_event.hscroll, - msg->inject_scroll_event.vscroll); + msg->inject_scroll_event.vscroll, + (long) msg->inject_scroll_event.buttons); break; case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: LOG_CMSG("back-or-screen-on %s", diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 7f3235d7..0eadd4f2 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -70,6 +70,7 @@ struct control_msg { struct sc_position position; int32_t hscroll; int32_t vscroll; + enum android_motionevent_buttons buttons; } inject_scroll_event; struct { enum android_keyevent_action action; // action for the BACK key diff --git a/app/src/input_events.h b/app/src/input_events.h index 48c51ee7..c27a7a47 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -360,6 +360,7 @@ struct sc_mouse_scroll_event { struct sc_position position; int32_t hscroll; int32_t vscroll; + uint8_t buttons_state; // bitwise-OR of sc_mouse_button values }; struct sc_mouse_motion_event { diff --git a/app/src/input_manager.c b/app/src/input_manager.c index ec91787a..64922f28 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -810,7 +810,7 @@ input_manager_process_mouse_wheel(struct input_manager *im, // mouse_x and mouse_y are expressed in pixels relative to the window int mouse_x; int mouse_y; - SDL_GetMouseState(&mouse_x, &mouse_y); + uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y); struct sc_mouse_scroll_event evt = { .position = { @@ -820,6 +820,8 @@ input_manager_process_mouse_wheel(struct input_manager *im, }, .hscroll = event->x, .vscroll = event->y, + .buttons_state = + sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks), }; im->mp->ops->process_mouse_scroll(im->mp, &evt); diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 1d14509d..38bfa404 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -108,6 +108,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, .position = event->position, .hscroll = event->hscroll, .vscroll = event->vscroll, + .buttons = convert_mouse_buttons(event->buttons_state), }, }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 42b72b59..d1f0f161 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -126,12 +126,13 @@ static void test_serialize_inject_scroll_event(void) { }, .hscroll = 1, .vscroll = -1, + .buttons = 1, }, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; size_t size = control_msg_serialize(&msg, buf); - assert(size == 21); + assert(size == 25); const unsigned char expected[] = { CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, @@ -139,6 +140,7 @@ static void test_serialize_inject_scroll_event(void) { 0x04, 0x38, 0x07, 0x80, // 1080 1920 0x00, 0x00, 0x00, 0x01, // 1 0xFF, 0xFF, 0xFF, 0xFF, // -1 + 0x00, 0x00, 0x00, 0x01, // 1 }; assert(!memcmp(buf, expected, sizeof(expected))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 63ba0fa3..99eb805f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -71,12 +71,13 @@ public final class ControlMessage { return msg; } - public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) { + public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_INJECT_SCROLL_EVENT; msg.position = position; msg.hScroll = hScroll; msg.vScroll = vScroll; + msg.buttons = buttons; return msg; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index f09ed26f..24dc5e50 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -10,7 +10,7 @@ public class ControlMessageReader { static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; - static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; + static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1; @@ -154,7 +154,8 @@ public class ControlMessageReader { Position position = readPosition(buffer); int hScroll = buffer.getInt(); int vScroll = buffer.getInt(); - return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); + int buttons = buffer.getInt(); + return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); } private ControlMessage parseBackOrScreenOnEvent() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 9246004a..481c512f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -98,7 +98,7 @@ public class Controller { break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: if (device.supportsInputEvents()) { - injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); + injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons()); } break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: @@ -221,7 +221,7 @@ public class Controller { return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } - private boolean injectScroll(Position position, int hScroll, int vScroll) { + private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) { long now = SystemClock.uptimeMillis(); Point point = device.getPhysicalPoint(position); if (point == null) { @@ -239,7 +239,7 @@ public class Controller { coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, + .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 5e79d4f0..2a4ffe75 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -128,6 +128,7 @@ public class ControlMessageReaderTest { dos.writeShort(1920); dos.writeInt(1); dos.writeInt(-1); + dos.writeInt(1); byte[] packet = bos.toByteArray(); @@ -144,6 +145,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1, event.getHScroll()); Assert.assertEquals(-1, event.getVScroll()); + Assert.assertEquals(1, event.getButtons()); } @Test From b5855e5deb7482f96e1a0217218442ecdf21e3ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Dec 2021 15:46:00 +0100 Subject: [PATCH 0271/1133] Add relative mode flag to mouse processors The default mouse injection works in absolute mode: it forwards clicks at a specific position on screen. To support HID mouse, add a flag to indicate that the mouse processor works in relative mode: it forwards mouse motion vectors, without any absolute reference to the screen. --- app/src/input_manager.c | 10 ++++++++++ app/src/mouse_inject.c | 2 ++ app/src/trait/mouse_processor.h | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 64922f28..5cfb5b7e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -659,7 +659,11 @@ input_manager_process_mouse_motion(struct input_manager *im, assert(im->mp->ops->process_mouse_motion); im->mp->ops->process_mouse_motion(im->mp, &evt); + // vfinger must never be used in relative mode + assert(!im->mp->relative_mode || !im->vfinger_down); + if (im->vfinger_down) { + assert(!im->mp->relative_mode); // assert one more time struct sc_point mouse = screen_convert_window_to_frame_coords(im->screen, event->x, event->y); @@ -772,6 +776,12 @@ input_manager_process_mouse_button(struct input_manager *im, assert(im->mp->ops->process_mouse_click); im->mp->ops->process_mouse_click(im->mp, &evt); + if (im->mp->relative_mode) { + assert(!im->vfinger_down); // vfinger must not be used in relative mode + // No pinch-to-zoom simulation + return; + } + // Pinch-to-zoom simulation. // // If Ctrl is hold when the left-click button is pressed, then diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 38bfa404..dcb4f611 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -151,4 +151,6 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi, }; mi->mouse_processor.ops = &ops; + + mi->mouse_processor.relative_mode = false; } diff --git a/app/src/trait/mouse_processor.h b/app/src/trait/mouse_processor.h index 0252d2c6..6e0b596e 100644 --- a/app/src/trait/mouse_processor.h +++ b/app/src/trait/mouse_processor.h @@ -16,6 +16,13 @@ */ struct sc_mouse_processor { const struct sc_mouse_processor_ops *ops; + + /** + * If set, the mouse processor works in relative mode (the absolute + * position is irrelevant). In particular, it indicates that the mouse + * pointer must be "captured" by the UI. + */ + bool relative_mode; }; struct sc_mouse_processor_ops { From 643293752dc92c2e175b6ad1c8ebb75ffc195a3d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Dec 2021 00:05:30 +0100 Subject: [PATCH 0272/1133] Provide relative mouse motion vector in event This will allow the mouse processor to handle relative motion easily. --- app/src/input_events.h | 2 ++ app/src/input_manager.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/input_events.h b/app/src/input_events.h index c27a7a47..4a4cf356 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -365,6 +365,8 @@ struct sc_mouse_scroll_event { struct sc_mouse_motion_event { struct sc_position position; + int32_t xrel; + int32_t yrel; uint8_t buttons_state; // bitwise-OR of sc_mouse_button values }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 5cfb5b7e..cc7883d9 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -651,6 +651,8 @@ input_manager_process_mouse_motion(struct input_manager *im, .point = screen_convert_window_to_frame_coords(im->screen, event->x, event->y), }, + .xrel = event->xrel, + .yrel = event->yrel, .buttons_state = sc_mouse_buttons_state_from_sdl(event->state, im->forward_all_clicks), From 40fca82b6002662c43a054079c0b8e0cbaa7d692 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 2 Jan 2022 00:11:00 +0100 Subject: [PATCH 0273/1133] Forward all motion events to mouse processors The decision to not send motion events when no click is pressed is specific to Android mouse injection. Other mouse processors (e.g. for HID mouse) will need to receive all events. --- app/src/input_manager.c | 8 -------- app/src/mouse_inject.c | 5 +++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index cc7883d9..e407a541 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -632,14 +632,6 @@ static void input_manager_process_mouse_motion(struct input_manager *im, const SDL_MouseMotionEvent *event) { - uint32_t mask = SDL_BUTTON_LMASK; - if (im->forward_all_clicks) { - mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK; - } - if (!(event->state & mask)) { - // do not send motion events when no click is pressed - return; - } if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate return; diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index dcb4f611..8a4a0531 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -58,6 +58,11 @@ convert_touch_action(enum sc_touch_action action) { static void sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, const struct sc_mouse_motion_event *event) { + if (!event->buttons_state) { + // Do not send motion events when no click is pressed + return; + } + struct sc_mouse_inject *mi = DOWNCAST(mp); struct control_msg msg = { From 17d01b5bf7832d4c65435e6076a1c75f7aec66be Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Dec 2021 18:52:34 +0100 Subject: [PATCH 0274/1133] Add UI/UX support for relative mouse mode In relative mouse mode, the mouse pointer must be "captured" from the computer. Toggle (disable/enable) relative mouse mode using any of the hardcoded capture keys: - left-Alt - left-Super - right-Super These capture keys do not conflict with shortcuts, since a shortcut is always a combination of the MOD key and some other key, while the capture key triggers an action only if it is pressed and released alone. The relative mouse mode is also automatically enabled on any click in the window, and automatically disabled on focus lost (it is possible to lose focus even without the mouse). --- app/src/screen.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ app/src/screen.h | 5 ++++ 2 files changed, 83 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 9ad81cb8..9c9f80bc 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -156,6 +156,17 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, return window_size; } +static inline void +screen_capture_mouse(struct screen *screen, bool capture) { + if (SDL_SetRelativeMouseMode(capture)) { + LOGE("Could not set relative mouse mode to %s: %s", + capture ? "true" : "false", SDL_GetError()); + return; + } + + screen->mouse_captured = capture; +} + static void screen_update_content_rect(struct screen *screen) { int dw; @@ -354,6 +365,8 @@ screen_init(struct screen *screen, const struct screen_params *params) { screen->fullscreen = false; screen->maximized = false; screen->event_failed = false; + screen->mouse_captured = false; + screen->mouse_capture_key_pressed = 0; static const struct sc_video_buffer_callbacks cbs = { .on_new_frame = sc_video_buffer_on_new_frame, @@ -741,6 +754,11 @@ screen_resize_to_pixel_perfect(struct screen *screen) { content_size.height); } +static inline bool +screen_is_mouse_capture_key(SDL_Keycode key) { + return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI; +} + bool screen_handle_event(struct screen *screen, SDL_Event *event) { switch (event->type) { @@ -783,8 +801,68 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { apply_pending_resize(screen); screen_render(screen, true); break; + case SDL_WINDOWEVENT_FOCUS_LOST: + if (screen->im.mp->relative_mode) { + screen_capture_mouse(screen, false); + } + break; } return true; + case SDL_KEYDOWN: + if (screen->im.mp->relative_mode) { + SDL_Keycode key = event->key.keysym.sym; + if (screen_is_mouse_capture_key(key)) { + if (!screen->mouse_capture_key_pressed) { + screen->mouse_capture_key_pressed = key; + return true; + } else { + // Another mouse capture key has been pressed, cancel + // mouse (un)capture + screen->mouse_capture_key_pressed = 0; + // Do not return, the event must be forwarded to the + // input manager + } + } + } + break; + case SDL_KEYUP: + if (screen->im.mp->relative_mode) { + SDL_Keycode key = event->key.keysym.sym; + SDL_Keycode cap = screen->mouse_capture_key_pressed; + screen->mouse_capture_key_pressed = 0; + if (key == cap) { + // A mouse capture key has been pressed then released: + // toggle the capture mouse mode + screen_capture_mouse(screen, !screen->mouse_captured); + return true; + } + // Do not return, the event must be forwarded to the input + // manager + } + break; + case SDL_MOUSEWHEEL: + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + if (screen->im.mp->relative_mode && !screen->mouse_captured) { + // Do not forward to input manager, the mouse will be captured + // on SDL_MOUSEBUTTONUP + return true; + } + break; + case SDL_FINGERMOTION: + case SDL_FINGERDOWN: + case SDL_FINGERUP: + if (screen->im.mp->relative_mode) { + // Touch events are not compatible with relative mode + // (coordinates are not relative) + return true; + } + break; + case SDL_MOUSEBUTTONUP: + if (screen->im.mp->relative_mode && !screen->mouse_captured) { + screen_capture_mouse(screen, true); + return true; + } } return input_manager_handle_event(&screen->im, event); diff --git a/app/src/screen.h b/app/src/screen.h index bc7696f1..f0e15a7d 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -51,6 +51,11 @@ struct screen { bool event_failed; // in case SDL_PushEvent() returned an error + bool mouse_captured; // only relevant in relative mouse mode + // To enable/disable mouse capture, a mouse capture key (LALT, LGUI or + // RGUI) must be pressed. This variable tracks the pressed capture key. + SDL_Keycode mouse_capture_key_pressed; + AVFrame *frame; }; From aee1b397905ae9108fe239c9e3bb41ef33d0f43f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Dec 2021 11:50:24 +0100 Subject: [PATCH 0275/1133] Add CLAMP() macro --- app/src/common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/common.h b/app/src/common.h index accbc615..ce9ccad4 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -7,6 +7,7 @@ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y) +#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) ) #define container_of(ptr, type, member) \ ((type *) (((char *) (ptr)) - offsetof(type, member))) From ed2e45ee29099cb6656782067f28376f65f94289 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Jan 2022 20:00:33 +0100 Subject: [PATCH 0276/1133] Refactor AOA/HID keyboard initialization This paves the way to add support for HID mouse initialization. --- app/src/scrcpy.c | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6b8074e9..5907c91e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -329,6 +329,7 @@ scrcpy(struct scrcpy_options *options) { bool stream_started = false; #ifdef HAVE_AOA_HID bool aoa_hid_initialized = false; + bool hid_keyboard_initialized = false; #endif bool controller_initialized = false; bool controller_started = false; @@ -448,40 +449,52 @@ scrcpy(struct scrcpy_options *options) { if (options->control) { #ifdef HAVE_AOA_HID - if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) { + bool use_hid_keyboard = + options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; + if (use_hid_keyboard) { bool ok = sc_acksync_init(&s->acksync); if (!ok) { goto end; } - ok = sc_aoa_init(&s->aoa, serial, acksync); + ok = sc_aoa_init(&s->aoa, serial, &s->acksync); if (!ok) { + LOGE("Failed to enable HID over AOA"); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } - if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { - sc_acksync_destroy(&s->acksync); - sc_aoa_destroy(&s->aoa); - goto aoa_hid_end; + if (use_hid_keyboard) { + if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { + hid_keyboard_initialized = true; + kp = &s->keyboard_hid.key_processor; + } else { + LOGE("Could not initialize HID keyboard"); + } } - if (!sc_aoa_start(&s->aoa)) { + bool need_aoa = hid_keyboard_initialized; + + if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); - sc_hid_keyboard_destroy(&s->keyboard_hid); sc_aoa_destroy(&s->aoa); goto aoa_hid_end; } - kp = &s->keyboard_hid.key_processor; acksync = &s->acksync; aoa_hid_initialized = true; aoa_hid_end: if (!aoa_hid_initialized) { - LOGE("Failed to enable HID over AOA, " - "fallback to default keyboard injection method " + if (hid_keyboard_initialized) { + sc_hid_keyboard_destroy(&s->keyboard_hid); + hid_keyboard_initialized = false; + } + } + + if (use_hid_keyboard && !hid_keyboard_initialized) { + LOGE("Fallback to default keyboard injection method " "(-K/--hid-keyboard ignored)"); options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; } @@ -590,7 +603,9 @@ end: // end-of-stream #ifdef HAVE_AOA_HID if (aoa_hid_initialized) { - sc_hid_keyboard_destroy(&s->keyboard_hid); + if (hid_keyboard_initialized) { + sc_hid_keyboard_destroy(&s->keyboard_hid); + } sc_aoa_stop(&s->aoa); } if (acksync) { From cba84f69993e3d7bdbb0e44493b70c0a14689487 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Dec 2021 22:32:51 +0100 Subject: [PATCH 0277/1133] Add support for HID mouse --- app/meson.build | 1 + app/scrcpy.1 | 14 +++ app/src/cli.c | 24 +++- app/src/hid_mouse.c | 267 ++++++++++++++++++++++++++++++++++++++++++++ app/src/hid_mouse.h | 23 ++++ app/src/options.h | 6 + app/src/scrcpy.c | 42 ++++++- 7 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 app/src/hid_mouse.c create mode 100644 app/src/hid_mouse.h diff --git a/app/meson.build b/app/meson.build index 38393f0c..cee261bb 100644 --- a/app/meson.build +++ b/app/meson.build @@ -77,6 +77,7 @@ if aoa_hid_support src += [ 'src/aoa_hid.c', 'src/hid_keyboard.c', + 'src/hid_mouse.c', ] endif diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 971c8a19..4d02982c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -96,6 +96,8 @@ The keyboard layout must be configured (once and for all) on the device, via Set However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected). +Also see \fB\-\-hid\-mouse\fR. + .TP .B \-\-legacy\-paste Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). @@ -120,6 +122,18 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension Default is 0 (unlimited). +.TP +.B \-M, \-\-hid\-mouse +Simulate a physical mouse by using HID over AOAv2. + +In this mode, the computer mouse is captured to control the device directly (relative mouse mode). + +LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. + +It may only work over USB, and is currently only supported on Linux. + +Also see \fB\-\-hid\-keyboard\fR. + .TP .B \-\-no\-clipboard\-autosync By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes. diff --git a/app/src/cli.c b/app/src/cli.c index ec53e5ec..3fcf94eb 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -178,7 +178,8 @@ static const struct sc_option options[] = { "directly: `adb shell am start -a " "android.settings.HARD_KEYBOARD_SETTINGS`.\n" "However, the option is only available when the HID keyboard " - "is enabled (or a physical keyboard is connected).", + "is enabled (or a physical keyboard is connected).\n" + "Also see --hid-mouse.", }, { .shortopt = 'h', @@ -214,6 +215,18 @@ static const struct sc_option options[] = { .text = "Limit the frame rate of screen capture (officially supported " "since Android 10, but may work on earlier versions).", }, + { + .shortopt = 'M', + .longopt = "hid-mouse", + .text = "Simulate a physical mouse by using HID over AOAv2.\n" + "In this mode, the computer mouse is captured to control the " + "device directly (relative mouse mode).\n" + "LAlt, LSuper or RSuper toggle the capture mode, to give " + "control of the mouse back to the computer.\n" + "It may only work over USB, and is currently only supported " + "on Linux.\n" + "Also see --hid-keyboard.", + }, { .shortopt = 'm', .longopt = "max-size", @@ -1315,6 +1328,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case 'M': +#ifdef HAVE_AOA_HID + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; +#else + LOGE("HID over AOA (-M/--hid-mouse) is not supported on this" + "platform. It is only available on Linux."); + return false; +#endif + break; case OPT_LOCK_VIDEO_ORIENTATION: if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { diff --git a/app/src/hid_mouse.c b/app/src/hid_mouse.c new file mode 100644 index 00000000..0e26c7c3 --- /dev/null +++ b/app/src/hid_mouse.c @@ -0,0 +1,267 @@ +#include "hid_mouse.h" + +#include + +#include "input_events.h" +#include "util/log.h" + +/** Downcast mouse processor to hid_mouse */ +#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor) + +#define HID_MOUSE_ACCESSORY_ID 2 + +// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position +#define HID_MOUSE_EVENT_SIZE 4 + +/** + * Mouse descriptor from the specification: + * + * + * Appendix E (p71): §E.10 Report Descriptor (Mouse) + * + * The usage tags (like Wheel) are listed in "HID Usage Tables": + * + * §4 Generic Desktop Page (0x01) (p26) + */ +static const unsigned char mouse_report_desc[] = { + // Usage Page (Generic Desktop) + 0x05, 0x01, + // Usage (Mouse) + 0x09, 0x02, + + // Collection (Application) + 0xA1, 0x01, + + // Usage (Pointer) + 0x09, 0x01, + + // Collection (Physical) + 0xA1, 0x00, + + // Usage Page (Buttons) + 0x05, 0x09, + + // Usage Minimum (1) + 0x19, 0x01, + // Usage Maximum (5) + 0x29, 0x05, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum (1) + 0x25, 0x01, + // Report Count (5) + 0x95, 0x05, + // Report Size (1) + 0x75, 0x01, + // Input (Data, Variable, Absolute): 5 buttons bits + 0x81, 0x02, + + // Report Count (1) + 0x95, 0x01, + // Report Size (3) + 0x75, 0x03, + // Input (Constant): 3 bits padding + 0x81, 0x01, + + // Usage Page (Generic Desktop) + 0x05, 0x01, + // Usage (X) + 0x09, 0x30, + // Usage (Y) + 0x09, 0x31, + // Usage (Wheel) + 0x09, 0x38, + // Local Minimum (-127) + 0x15, 0x81, + // Local Maximum (127) + 0x25, 0x7F, + // Report Size (8) + 0x75, 0x08, + // Report Count (3) + 0x95, 0x03, + // Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel) + 0x81, 0x06, + + // End Collection + 0xC0, + + // End Collection + 0xC0, +}; + +/** + * A mouse HID event is 3 bytes long: + * + * - byte 0: buttons state + * - byte 1: relative x motion (signed byte from -127 to 127) + * - byte 2: relative y motion (signed byte from -127 to 127) + * + * 7 6 5 4 3 2 1 0 + * +---------------+ + * byte 0: |0 0 0 . . . . .| buttons state + * +---------------+ + * ^ ^ ^ ^ ^ + * | | | | `- left button + * | | | `--- right button + * | | `----- middle button + * | `------- button 4 + * `--------- button 5 + * + * +---------------+ + * byte 1: |. . . . . . . .| relative x motion + * +---------------+ + * byte 2: |. . . . . . . .| relative y motion + * +---------------+ + * byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1) + * +---------------+ + * + * As an example, here is the report for a motion of (x=5, y=-4) with left + * button pressed: + * + * +---------------+ + * |0 0 0 0 0 0 0 1| left button pressed + * +---------------+ + * |0 0 0 0 0 1 0 1| horizontal motion (x = 5) + * +---------------+ + * |1 1 1 1 1 1 0 0| relative y motion (y = -4) + * +---------------+ + * |0 0 0 0 0 0 0 0| wheel motion + * +---------------+ + */ + +static bool +sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { + unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE); + if (!buffer) { + LOG_OOM(); + return false; + } + + sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer, + HID_MOUSE_EVENT_SIZE); + return true; +} + +static unsigned char +buttons_state_to_hid_buttons(uint8_t buttons_state) { + unsigned char c = 0; + if (buttons_state & SC_MOUSE_BUTTON_LEFT) { + c |= 1 << 0; + } + if (buttons_state & SC_MOUSE_BUTTON_RIGHT) { + c |= 1 << 1; + } + if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) { + c |= 1 << 2; + } + if (buttons_state & SC_MOUSE_BUTTON_X1) { + c |= 1 << 3; + } + if (buttons_state & SC_MOUSE_BUTTON_X2) { + c |= 1 << 4; + } + return c; +} + +static void +sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, + const struct sc_mouse_motion_event *event) { + struct sc_hid_mouse *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + if (!sc_hid_mouse_event_init(&hid_event)) { + return; + } + + unsigned char *buffer = hid_event.buffer; + buffer[0] = buttons_state_to_hid_buttons(event->buttons_state); + buffer[1] = CLAMP(event->xrel, -127, 127); + buffer[2] = CLAMP(event->yrel, -127, 127); + buffer[3] = 0; // wheel coordinates only used for scrolling + + if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + sc_hid_event_destroy(&hid_event); + LOGW("Could request HID event"); + } +} + +static void +sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event) { + struct sc_hid_mouse *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + if (!sc_hid_mouse_event_init(&hid_event)) { + return; + } + + unsigned char *buffer = hid_event.buffer; + buffer[0] = buttons_state_to_hid_buttons(event->buttons_state); + buffer[1] = 0; // no x motion + buffer[2] = 0; // no y motion + buffer[3] = 0; // wheel coordinates only used for scrolling + + if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + sc_hid_event_destroy(&hid_event); + LOGW("Could request HID event"); + } +} + +static void +sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event) { + struct sc_hid_mouse *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + if (!sc_hid_mouse_event_init(&hid_event)) { + return; + } + + unsigned char *buffer = hid_event.buffer; + buffer[0] = 0; // buttons state irrelevant (and unknown) + buffer[1] = 0; // no x motion + buffer[2] = 0; // no y motion + // In practice, vscroll is always -1, 0 or 1, but in theory other values + // are possible + buffer[3] = CLAMP(event->vscroll, -127, 127); + // Horizontal scrolling ignored + + if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + sc_hid_event_destroy(&hid_event); + LOGW("Could request HID event"); + } +} + +bool +sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) { + mouse->aoa = aoa; + + bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc, + ARRAY_LEN(mouse_report_desc)); + if (!ok) { + LOGW("Register HID mouse failed"); + return false; + } + + static const struct sc_mouse_processor_ops ops = { + .process_mouse_motion = sc_mouse_processor_process_mouse_motion, + .process_mouse_click = sc_mouse_processor_process_mouse_click, + .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, + // Touch events not supported (coordinates are not relative) + .process_touch = NULL, + }; + + mouse->mouse_processor.ops = &ops; + + mouse->mouse_processor.relative_mode = true; + + return true; +} + +void +sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) { + bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); + if (!ok) { + LOGW("Could not unregister HID"); + } +} diff --git a/app/src/hid_mouse.h b/app/src/hid_mouse.h new file mode 100644 index 00000000..2819b1ff --- /dev/null +++ b/app/src/hid_mouse.h @@ -0,0 +1,23 @@ +#ifndef HID_MOUSE_H +#define HID_MOUSE_H + +#include "common.h" + +#include + +#include "aoa_hid.h" +#include "trait/mouse_processor.h" + +struct sc_hid_mouse { + struct sc_mouse_processor mouse_processor; // mouse processor trait + + struct sc_aoa *aoa; +}; + +bool +sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa); + +void +sc_hid_mouse_destroy(struct sc_hid_mouse *mouse); + +#endif diff --git a/app/src/options.h b/app/src/options.h index 533f4a3b..b2c69664 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -38,6 +38,11 @@ enum sc_keyboard_input_mode { SC_KEYBOARD_INPUT_MODE_HID, }; +enum sc_mouse_input_mode { + SC_MOUSE_INPUT_MODE_INJECT, + SC_MOUSE_INPUT_MODE_HID, +}; + enum sc_key_inject_mode { // Inject special keys, letters and space as key events. // Inject numbers and punctuation as text events. @@ -90,6 +95,7 @@ struct scrcpy_options { enum sc_log_level log_level; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; + enum sc_mouse_input_mode mouse_input_mode; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5907c91e..3e50aaca 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -19,6 +19,7 @@ #include "file_handler.h" #ifdef HAVE_AOA_HID # include "hid_keyboard.h" +# include "hid_mouse.h" #endif #include "keyboard_inject.h" #include "mouse_inject.h" @@ -55,7 +56,12 @@ struct scrcpy { struct sc_hid_keyboard keyboard_hid; #endif }; - struct sc_mouse_inject mouse_inject; + union { + struct sc_mouse_inject mouse_inject; +#ifdef HAVE_AOA_HID + struct sc_hid_mouse mouse_hid; +#endif + }; }; static inline void @@ -330,6 +336,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_AOA_HID bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; + bool hid_mouse_initialized = false; #endif bool controller_initialized = false; bool controller_started = false; @@ -451,7 +458,9 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_AOA_HID bool use_hid_keyboard = options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; - if (use_hid_keyboard) { + bool use_hid_mouse = + options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; + if (use_hid_keyboard || use_hid_mouse) { bool ok = sc_acksync_init(&s->acksync); if (!ok) { goto end; @@ -473,7 +482,16 @@ scrcpy(struct scrcpy_options *options) { } } - bool need_aoa = hid_keyboard_initialized; + if (use_hid_mouse) { + if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) { + hid_mouse_initialized = true; + mp = &s->mouse_hid.mouse_processor; + } else { + LOGE("Could not initialized HID mouse"); + } + } + + bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized; if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); @@ -491,6 +509,10 @@ aoa_hid_end: sc_hid_keyboard_destroy(&s->keyboard_hid); hid_keyboard_initialized = false; } + if (hid_mouse_initialized) { + sc_hid_mouse_destroy(&s->mouse_hid); + hid_mouse_initialized = false; + } } if (use_hid_keyboard && !hid_keyboard_initialized) { @@ -498,9 +520,16 @@ aoa_hid_end: "(-K/--hid-keyboard ignored)"); options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; } + + if (use_hid_mouse && !hid_mouse_initialized) { + LOGE("Fallback to default mouse injection method " + "(-M/--hid-mouse ignored)"); + options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT; + } } #else assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID); + assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID); #endif // keyboard_input_mode may have been reset if HID mode failed @@ -510,8 +539,11 @@ aoa_hid_end: kp = &s->keyboard_inject.key_processor; } - sc_mouse_inject_init(&s->mouse_inject, &s->controller); - mp = &s->mouse_inject.mouse_processor; + // mouse_input_mode may have been reset if HID mode failed + if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) { + sc_mouse_inject_init(&s->mouse_inject, &s->controller); + mp = &s->mouse_inject.mouse_processor; + } if (!controller_init(&s->controller, s->server.control_socket, acksync)) { From 43aff4af73261a46ee291d5e8fbfd70c7d9cd0ea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 2 Jan 2022 00:37:53 +0100 Subject: [PATCH 0278/1133] Document HID mouse in README --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 8a177406..edf48cfd 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Its features include: - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) (Linux-only) + - [physical mouse simulation (HID)](#physical-mouse-simulation-hid) + (Linux-only) - and more… ## Requirements @@ -815,6 +817,35 @@ a physical keyboard is connected). [Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 +#### Physical mouse simulation (HID) + +Similarly to the physical keyboard simulation, it is possible to simulate a +physical mouse. Likewise, it only works if the device is connected by USB, and +is currently only supported on Linux. + +By default, scrcpy uses Android mouse events injection, using absolute +coordinates. By simulating a physical mouse, a mouse pointer appears on the +Android device, and relative mouse motion, clicks and scrolls are injected. + +To enable this mode: + +```bash +scrcpy --hid-mouse +scrcpy -M # short version +``` + +You could also add `--forward-all-clicks` to [forward all mouse +buttons][forward_all_clicks]. + +[forward_all_clicks]: #right-click-and-middle-click + +When this mode is enabled, the computer mouse is "captured" (the mouse pointer +disappears from the computer and appears on the Android device instead). + +Special capture keys, either Alt or Super, toggle +(disable or enable) the mouse capture. Use one of them to give the control of +the mouse back to the computer. + #### Text injection preference From 75655194fb08d8ea92cd72ca42d6bd0bea191f05 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 20:55:44 +0100 Subject: [PATCH 0279/1133] Do not pass scrcpy_options to keyboard inject The components should be configurable independently of the global scrcpy_options instance: their configuration could be provided separately, like it is the case for example for some screen parameters. For consistency, keyboard injection should not depend on scrcpy_options. --- app/src/keyboard_inject.c | 7 ++++--- app/src/keyboard_inject.h | 3 ++- app/src/scrcpy.c | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 76357d85..b6dd69fb 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -325,10 +325,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp, void sc_keyboard_inject_init(struct sc_keyboard_inject *ki, struct controller *controller, - const struct scrcpy_options *options) { + enum sc_key_inject_mode key_inject_mode, + bool forward_key_repeat) { ki->controller = controller; - ki->key_inject_mode = options->key_inject_mode; - ki->forward_key_repeat = options->forward_key_repeat; + ki->key_inject_mode = key_inject_mode; + ki->forward_key_repeat = forward_key_repeat; ki->repeat = 0; diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h index edd5b1ba..9b25425a 100644 --- a/app/src/keyboard_inject.h +++ b/app/src/keyboard_inject.h @@ -25,6 +25,7 @@ struct sc_keyboard_inject { void sc_keyboard_inject_init(struct sc_keyboard_inject *ki, struct controller *controller, - const struct scrcpy_options *options); + enum sc_key_inject_mode key_inject_mode, + bool forward_key_repeat); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3e50aaca..474b007b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -535,7 +535,8 @@ aoa_hid_end: // keyboard_input_mode may have been reset if HID mode failed if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) { sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, - options); + options->key_inject_mode, + options->forward_key_repeat); kp = &s->keyboard_inject.key_processor; } From a6644e831bbd005af1a92f6614bf901764d6eac8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 20:57:03 +0100 Subject: [PATCH 0280/1133] Fix code style Limit to 80 chars. --- app/src/aoa_hid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index abf74cf9..9de918bc 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -343,8 +343,8 @@ run_aoa_thread(void *data) { if (ack_to_wait != SC_SEQUENCE_INVALID) { LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); - // Do not block the loop indefinitely if the ack never comes (it should - // never happen) + // Do not block the loop indefinitely if the ack never comes (it + // should never happen) sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); enum sc_acksync_wait_result result = sc_acksync_wait(aoa->acksync, ack_to_wait, deadline); From 2a0c2e5e99ac5479c2b7ec867f21957deb1c3fbe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 22:17:30 +0100 Subject: [PATCH 0281/1133] Use sc_ prefix for screen --- app/src/input_manager.c | 43 +++++++------- app/src/input_manager.h | 4 +- app/src/scrcpy.c | 16 +++--- app/src/screen.c | 123 ++++++++++++++++++++-------------------- app/src/screen.h | 34 +++++------ 5 files changed, 112 insertions(+), 108 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e407a541..450f0059 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -369,15 +369,15 @@ rotate_device(struct controller *controller) { } static void -rotate_client_left(struct screen *screen) { +rotate_client_left(struct sc_screen *screen) { unsigned new_rotation = (screen->rotation + 1) % 4; - screen_set_rotation(screen, new_rotation); + sc_screen_set_rotation(screen, new_rotation); } static void -rotate_client_right(struct screen *screen) { +rotate_client_right(struct sc_screen *screen) { unsigned new_rotation = (screen->rotation + 3) % 4; - screen_set_rotation(screen, new_rotation); + sc_screen_set_rotation(screen, new_rotation); } static void @@ -544,17 +544,17 @@ input_manager_process_key(struct input_manager *im, return; case SDLK_f: if (!shift && !repeat && down) { - screen_switch_fullscreen(im->screen); + sc_screen_switch_fullscreen(im->screen); } return; case SDLK_w: if (!shift && !repeat && down) { - screen_resize_to_fit(im->screen); + sc_screen_resize_to_fit(im->screen); } return; case SDLK_g: if (!shift && !repeat && down) { - screen_resize_to_pixel_perfect(im->screen); + sc_screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: @@ -640,8 +640,9 @@ input_manager_process_mouse_motion(struct input_manager *im, struct sc_mouse_motion_event evt = { .position = { .screen_size = im->screen->frame_size, - .point = screen_convert_window_to_frame_coords(im->screen, - event->x, event->y), + .point = sc_screen_convert_window_to_frame_coords(im->screen, + event->x, + event->y), }, .xrel = event->xrel, .yrel = event->yrel, @@ -659,8 +660,8 @@ input_manager_process_mouse_motion(struct input_manager *im, if (im->vfinger_down) { assert(!im->mp->relative_mode); // assert one more time struct sc_point mouse = - screen_convert_window_to_frame_coords(im->screen, event->x, - event->y); + sc_screen_convert_window_to_frame_coords(im->screen, event->x, + event->y); struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } @@ -685,7 +686,8 @@ input_manager_process_touch(struct input_manager *im, struct sc_touch_event evt = { .position = { .screen_size = im->screen->frame_size, - .point = screen_convert_drawable_to_frame_coords(im->screen, x, y), + .point = + sc_screen_convert_drawable_to_frame_coords(im->screen, x, y), }, .action = sc_touch_action_from_sdl(event->type), .pointer_id = event->fingerId, @@ -734,13 +736,13 @@ input_manager_process_mouse_button(struct input_manager *im, if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { int32_t x = event->x; int32_t y = event->y; - screen_hidpi_scale_coords(im->screen, &x, &y); + sc_screen_hidpi_scale_coords(im->screen, &x, &y); SDL_Rect *r = &im->screen->rect; bool outside = x < r->x || x >= r->x + r->w || y < r->y || y >= r->y + r->h; if (outside) { if (down) { - screen_resize_to_fit(im->screen); + sc_screen_resize_to_fit(im->screen); } return; } @@ -757,8 +759,9 @@ input_manager_process_mouse_button(struct input_manager *im, struct sc_mouse_click_event evt = { .position = { .screen_size = im->screen->frame_size, - .point = screen_convert_window_to_frame_coords(im->screen, event->x, - event->y), + .point = sc_screen_convert_window_to_frame_coords(im->screen, + event->x, + event->y), }, .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), @@ -790,8 +793,8 @@ input_manager_process_mouse_button(struct input_manager *im, ((down && !im->vfinger_down && CTRL_PRESSED) || (!down && im->vfinger_down))) { struct sc_point mouse = - screen_convert_window_to_frame_coords(im->screen, event->x, - event->y); + sc_screen_convert_window_to_frame_coords(im->screen, event->x, + event->y); struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); enum android_motionevent_action action = down ? AMOTION_EVENT_ACTION_DOWN @@ -819,8 +822,8 @@ input_manager_process_mouse_wheel(struct input_manager *im, struct sc_mouse_scroll_event evt = { .position = { .screen_size = im->screen->frame_size, - .point = screen_convert_window_to_frame_coords(im->screen, - mouse_x, mouse_y), + .point = sc_screen_convert_window_to_frame_coords(im->screen, + mouse_x, mouse_y), }, .hscroll = event->x, .vscroll = event->y, diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 088406f6..4b5e7967 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -15,7 +15,7 @@ struct input_manager { struct controller *controller; - struct screen *screen; + struct sc_screen *screen; struct sc_key_processor *kp; struct sc_mouse_processor *mp; @@ -44,7 +44,7 @@ struct input_manager { struct input_manager_params { struct controller *controller; - struct screen *screen; + struct sc_screen *screen; struct sc_key_processor *kp; struct sc_mouse_processor *mp; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 474b007b..9237c59b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -36,7 +36,7 @@ struct scrcpy { struct sc_server server; - struct screen screen; + struct sc_screen screen; struct stream stream; struct decoder decoder; struct recorder recorder; @@ -192,7 +192,7 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, } } - bool consumed = screen_handle_event(&s->screen, event); + bool consumed = sc_screen_handle_event(&s->screen, event); (void) consumed; end: @@ -573,7 +573,7 @@ aoa_hid_end: const char *window_title = options->window_title ? options->window_title : info->device_name; - struct screen_params screen_params = { + struct sc_screen_params screen_params = { .controller = &s->controller, .kp = kp, .mp = mp, @@ -596,7 +596,7 @@ aoa_hid_end: .buffering_time = options->display_buffer, }; - if (!screen_init(&s->screen, &screen_params)) { + if (!sc_screen_init(&s->screen, &screen_params)) { goto end; } screen_initialized = true; @@ -629,7 +629,7 @@ aoa_hid_end: // Close the window immediately on closing, because screen_destroy() may // only be called once the stream thread is joined (it may take time) - screen_hide_window(&s->screen); + sc_screen_hide_window(&s->screen); end: // The stream is not stopped explicitly, because it will stop by itself on @@ -652,7 +652,7 @@ end: file_handler_stop(&s->file_handler); } if (screen_initialized) { - screen_interrupt(&s->screen); + sc_screen_interrupt(&s->screen); } if (server_started) { @@ -682,8 +682,8 @@ end: // Destroy the screen only after the stream is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { - screen_join(&s->screen); - screen_destroy(&s->screen); + sc_screen_join(&s->screen); + sc_screen_destroy(&s->screen); } if (controller_started) { diff --git a/app/src/screen.c b/app/src/screen.c index 9c9f80bc..3fca6f21 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -12,7 +12,7 @@ #define DISPLAY_MARGINS 96 -#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink) +#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink) static inline struct sc_size get_rotated_size(struct sc_size size, int rotation) { @@ -29,7 +29,7 @@ get_rotated_size(struct sc_size size, int rotation) { // get the window size in a struct sc_size static struct sc_size -get_window_size(const struct screen *screen) { +get_window_size(const struct sc_screen *screen) { int width; int height; SDL_GetWindowSize(screen->window, &width, &height); @@ -41,7 +41,7 @@ get_window_size(const struct screen *screen) { } static struct sc_point -get_window_position(const struct screen *screen) { +get_window_position(const struct sc_screen *screen) { int x; int y; SDL_GetWindowPosition(screen->window, &x, &y); @@ -54,7 +54,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 sc_screen *screen, struct sc_size new_size) { assert(!screen->fullscreen); assert(!screen->maximized); SDL_SetWindowSize(screen->window, new_size.width, new_size.height); @@ -157,7 +157,7 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, } static inline void -screen_capture_mouse(struct screen *screen, bool capture) { +sc_screen_capture_mouse(struct sc_screen *screen, bool capture) { if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); @@ -168,7 +168,7 @@ screen_capture_mouse(struct screen *screen, bool capture) { } static void -screen_update_content_rect(struct screen *screen) { +sc_screen_update_content_rect(struct sc_screen *screen) { int dw; int dh; SDL_GL_GetDrawableSize(screen->window, &dw, &dh); @@ -205,7 +205,7 @@ screen_update_content_rect(struct screen *screen) { } static inline SDL_Texture * -create_texture(struct screen *screen) { +create_texture(struct sc_screen *screen) { SDL_Renderer *renderer = screen->renderer; struct sc_size size = screen->frame_size; SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, @@ -236,9 +236,9 @@ create_texture(struct screen *screen) { // Set the update_content_rect flag if the window or content size may have // changed, so that the content rectangle is recomputed static void -screen_render(struct screen *screen, bool update_content_rect) { +sc_screen_render(struct sc_screen *screen, bool update_content_rect) { if (update_content_rect) { - screen_update_content_rect(screen); + sc_screen_update_content_rect(screen); } SDL_RenderClear(screen->renderer); @@ -282,20 +282,20 @@ screen_render(struct screen *screen, bool update_content_rect) { // static int event_watcher(void *data, SDL_Event *event) { - struct screen *screen = data; + struct sc_screen *screen = data; if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { // In practice, it seems to always be called from the same thread in // that specific case. Anyway, it's just a workaround. - screen_render(screen, true); + sc_screen_render(screen, true); } return 0; } #endif static bool -screen_frame_sink_open(struct sc_frame_sink *sink) { - struct screen *screen = DOWNCAST(sink); +sc_screen_frame_sink_open(struct sc_frame_sink *sink) { + struct sc_screen *screen = DOWNCAST(sink); (void) screen; #ifndef NDEBUG screen->open = true; @@ -306,8 +306,8 @@ screen_frame_sink_open(struct sc_frame_sink *sink) { } static void -screen_frame_sink_close(struct sc_frame_sink *sink) { - struct screen *screen = DOWNCAST(sink); +sc_screen_frame_sink_close(struct sc_frame_sink *sink) { + struct sc_screen *screen = DOWNCAST(sink); (void) screen; #ifndef NDEBUG screen->open = false; @@ -317,8 +317,8 @@ screen_frame_sink_close(struct sc_frame_sink *sink) { } static bool -screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { - struct screen *screen = DOWNCAST(sink); +sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { + struct sc_screen *screen = DOWNCAST(sink); return sc_video_buffer_push(&screen->vb, frame); } @@ -326,7 +326,7 @@ static void sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, void *userdata) { (void) vb; - struct screen *screen = userdata; + struct sc_screen *screen = userdata; // event_failed implies previous_skipped (the previous frame may not have // been consumed if the event was not sent) @@ -359,7 +359,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, } bool -screen_init(struct screen *screen, const struct screen_params *params) { +sc_screen_init(struct sc_screen *screen, + const struct sc_screen_params *params) { screen->resize_pending = false; screen->has_frame = false; screen->fullscreen = false; @@ -502,10 +503,10 @@ screen_init(struct screen *screen, const struct screen_params *params) { // different HiDPI scaling are connected SDL_SetWindowSize(screen->window, window_size.width, window_size.height); - screen_update_content_rect(screen); + sc_screen_update_content_rect(screen); if (params->fullscreen) { - screen_switch_fullscreen(screen); + sc_screen_switch_fullscreen(screen); } #ifdef CONTINUOUS_RESIZING_WORKAROUND @@ -513,9 +514,9 @@ screen_init(struct screen *screen, const struct screen_params *params) { #endif static const struct sc_frame_sink_ops ops = { - .open = screen_frame_sink_open, - .close = screen_frame_sink_close, - .push = screen_frame_sink_push, + .open = sc_screen_frame_sink_open, + .close = sc_screen_frame_sink_close, + .push = sc_screen_frame_sink_push, }; screen->frame_sink.ops = &ops; @@ -544,29 +545,29 @@ error_destroy_video_buffer: } static void -screen_show_window(struct screen *screen) { +sc_screen_show_window(struct sc_screen *screen) { SDL_ShowWindow(screen->window); } void -screen_hide_window(struct screen *screen) { +sc_screen_hide_window(struct sc_screen *screen) { SDL_HideWindow(screen->window); } void -screen_interrupt(struct screen *screen) { +sc_screen_interrupt(struct sc_screen *screen) { sc_video_buffer_stop(&screen->vb); fps_counter_interrupt(&screen->fps_counter); } void -screen_join(struct screen *screen) { +sc_screen_join(struct sc_screen *screen) { sc_video_buffer_join(&screen->vb); fps_counter_join(&screen->fps_counter); } void -screen_destroy(struct screen *screen) { +sc_screen_destroy(struct sc_screen *screen) { #ifndef NDEBUG assert(!screen->open); #endif @@ -579,7 +580,7 @@ screen_destroy(struct screen *screen) { } static void -resize_for_content(struct screen *screen, struct sc_size old_content_size, +resize_for_content(struct sc_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 = { @@ -593,7 +594,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 sc_screen *screen, struct sc_size new_content_size) { if (!screen->fullscreen && !screen->maximized) { resize_for_content(screen, screen->content_size, new_content_size); } else if (!screen->resize_pending) { @@ -607,7 +608,7 @@ set_content_size(struct screen *screen, struct sc_size new_content_size) { } static void -apply_pending_resize(struct screen *screen) { +apply_pending_resize(struct sc_screen *screen) { assert(!screen->fullscreen); assert(!screen->maximized); if (screen->resize_pending) { @@ -618,7 +619,7 @@ apply_pending_resize(struct screen *screen) { } void -screen_set_rotation(struct screen *screen, unsigned rotation) { +sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) { assert(rotation < 4); if (rotation == screen->rotation) { return; @@ -632,12 +633,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) { screen->rotation = rotation; LOGI("Display rotation set to %u", rotation); - screen_render(screen, true); + sc_screen_render(screen, true); } // 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 sc_screen *screen, struct sc_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 @@ -649,7 +650,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) { get_rotated_size(new_frame_size, screen->rotation); set_content_size(screen, new_content_size); - screen_update_content_rect(screen); + sc_screen_update_content_rect(screen); LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); @@ -665,7 +666,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) { // write the frame into the texture static void -update_texture(struct screen *screen, const AVFrame *frame) { +update_texture(struct sc_screen *screen, const AVFrame *frame) { SDL_UpdateYUVTexture(screen->texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], @@ -679,7 +680,7 @@ update_texture(struct screen *screen, const AVFrame *frame) { } static bool -screen_update_frame(struct screen *screen) { +sc_screen_update_frame(struct sc_screen *screen) { av_frame_unref(screen->frame); sc_video_buffer_consume(&screen->vb, screen->frame); AVFrame *frame = screen->frame; @@ -692,12 +693,12 @@ screen_update_frame(struct screen *screen) { } update_texture(screen, frame); - screen_render(screen, false); + sc_screen_render(screen, false); return true; } void -screen_switch_fullscreen(struct screen *screen) { +sc_screen_switch_fullscreen(struct sc_screen *screen) { uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(screen->window, new_mode)) { LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); @@ -710,11 +711,11 @@ screen_switch_fullscreen(struct screen *screen) { } LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); - screen_render(screen, true); + sc_screen_render(screen, true); } void -screen_resize_to_fit(struct screen *screen) { +sc_screen_resize_to_fit(struct sc_screen *screen) { if (screen->fullscreen || screen->maximized) { return; } @@ -738,7 +739,7 @@ screen_resize_to_fit(struct screen *screen) { } void -screen_resize_to_pixel_perfect(struct screen *screen) { +sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) { if (screen->fullscreen) { return; } @@ -755,20 +756,20 @@ screen_resize_to_pixel_perfect(struct screen *screen) { } static inline bool -screen_is_mouse_capture_key(SDL_Keycode key) { +sc_screen_is_mouse_capture_key(SDL_Keycode key) { return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI; } bool -screen_handle_event(struct screen *screen, SDL_Event *event) { +sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { switch (event->type) { case EVENT_NEW_FRAME: if (!screen->has_frame) { screen->has_frame = true; // this is the very first frame, show the window - screen_show_window(screen); + sc_screen_show_window(screen); } - bool ok = screen_update_frame(screen); + bool ok = sc_screen_update_frame(screen); if (!ok) { LOGW("Frame update failed\n"); } @@ -780,10 +781,10 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { } switch (event->window.event) { case SDL_WINDOWEVENT_EXPOSED: - screen_render(screen, true); + sc_screen_render(screen, true); break; case SDL_WINDOWEVENT_SIZE_CHANGED: - screen_render(screen, true); + sc_screen_render(screen, true); break; case SDL_WINDOWEVENT_MAXIMIZED: screen->maximized = true; @@ -799,11 +800,11 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { } screen->maximized = false; apply_pending_resize(screen); - screen_render(screen, true); + sc_screen_render(screen, true); break; case SDL_WINDOWEVENT_FOCUS_LOST: if (screen->im.mp->relative_mode) { - screen_capture_mouse(screen, false); + sc_screen_capture_mouse(screen, false); } break; } @@ -811,7 +812,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { case SDL_KEYDOWN: if (screen->im.mp->relative_mode) { SDL_Keycode key = event->key.keysym.sym; - if (screen_is_mouse_capture_key(key)) { + if (sc_screen_is_mouse_capture_key(key)) { if (!screen->mouse_capture_key_pressed) { screen->mouse_capture_key_pressed = key; return true; @@ -833,7 +834,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { if (key == cap) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode - screen_capture_mouse(screen, !screen->mouse_captured); + sc_screen_capture_mouse(screen, !screen->mouse_captured); return true; } // Do not return, the event must be forwarded to the input @@ -860,7 +861,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { break; case SDL_MOUSEBUTTONUP: if (screen->im.mp->relative_mode && !screen->mouse_captured) { - screen_capture_mouse(screen, true); + sc_screen_capture_mouse(screen, true); return true; } } @@ -869,8 +870,8 @@ screen_handle_event(struct screen *screen, SDL_Event *event) { } struct sc_point -screen_convert_drawable_to_frame_coords(struct screen *screen, - int32_t x, int32_t y) { +sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, + int32_t x, int32_t y) { unsigned rotation = screen->rotation; assert(rotation < 4); @@ -906,14 +907,14 @@ screen_convert_drawable_to_frame_coords(struct screen *screen, } struct sc_point -screen_convert_window_to_frame_coords(struct screen *screen, - int32_t x, int32_t y) { - screen_hidpi_scale_coords(screen, &x, &y); - return screen_convert_drawable_to_frame_coords(screen, x, y); +sc_screen_convert_window_to_frame_coords(struct sc_screen *screen, + int32_t x, int32_t y) { + sc_screen_hidpi_scale_coords(screen, &x, &y); + return sc_screen_convert_drawable_to_frame_coords(screen, x, y); } void -screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) { +sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) { // take the HiDPI scaling (dw/ww and dh/wh) into account int ww, wh, dw, dh; SDL_GetWindowSize(screen->window, &ww, &wh); diff --git a/app/src/screen.h b/app/src/screen.h index f0e15a7d..b3240ae0 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -17,7 +17,7 @@ #include "trait/mouse_processor.h" #include "video_buffer.h" -struct screen { +struct sc_screen { struct sc_frame_sink frame_sink; // frame sink trait #ifndef NDEBUG @@ -59,7 +59,7 @@ struct screen { AVFrame *frame; }; -struct screen_params { +struct sc_screen_params { struct controller *controller; struct sc_key_processor *kp; struct sc_mouse_processor *mp; @@ -91,65 +91,65 @@ struct screen_params { // initialize screen, create window, renderer and texture (window is hidden) bool -screen_init(struct screen *screen, const struct screen_params *params); +sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params); // request to interrupt any inner thread // must be called before screen_join() void -screen_interrupt(struct screen *screen); +sc_screen_interrupt(struct sc_screen *screen); // join any inner thread void -screen_join(struct screen *screen); +sc_screen_join(struct sc_screen *screen); // destroy window, renderer and texture (if any) void -screen_destroy(struct screen *screen); +sc_screen_destroy(struct sc_screen *screen); // hide the window // // It is used to hide the window immediately on closing without waiting for // screen_destroy() void -screen_hide_window(struct screen *screen); +sc_screen_hide_window(struct sc_screen *screen); // switch the fullscreen mode void -screen_switch_fullscreen(struct screen *screen); +sc_screen_switch_fullscreen(struct sc_screen *screen); // resize window to optimal size (remove black borders) void -screen_resize_to_fit(struct screen *screen); +sc_screen_resize_to_fit(struct sc_screen *screen); // resize window to 1:1 (pixel-perfect) void -screen_resize_to_pixel_perfect(struct screen *screen); +sc_screen_resize_to_pixel_perfect(struct sc_screen *screen); // set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise) void -screen_set_rotation(struct screen *screen, unsigned rotation); +sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); // react to SDL events bool -screen_handle_event(struct screen *screen, SDL_Event *event); +sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event); // convert point from window coordinates to frame coordinates // x and y are expressed in pixels struct sc_point -screen_convert_window_to_frame_coords(struct screen *screen, - int32_t x, int32_t y); +sc_screen_convert_window_to_frame_coords(struct sc_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 -screen_convert_drawable_to_frame_coords(struct screen *screen, - int32_t x, int32_t y); +sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, + int32_t x, int32_t y); // Convert coordinates from window to drawable. // Events are expressed in window coordinates, but content is expressed in // drawable coordinates. They are the same if HiDPI scaling is 1, but differ // otherwise. void -screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y); +sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y); #endif From 5f7ddff8ae5e906b3c0835e649ca536ff69316b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 22:17:30 +0100 Subject: [PATCH 0282/1133] Use sc_ prefix for input_manager --- app/src/input_manager.c | 46 ++++++++++++++++++++--------------------- app/src/input_manager.h | 10 ++++----- app/src/screen.c | 6 +++--- app/src/screen.h | 2 +- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 450f0059..6eac46aa 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -106,7 +106,7 @@ to_sdl_mod(unsigned shortcut_mod) { } static bool -is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { +is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) { // keep only the relevant modifier keys sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK; @@ -122,8 +122,8 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { } void -input_manager_init(struct input_manager *im, - const struct input_manager_params *params) { +sc_input_manager_init(struct sc_input_manager *im, + const struct sc_input_manager_params *params) { assert(!params->control || (params->kp && params->kp->ops)); assert(!params->control || (params->mp && params->mp->ops)); @@ -381,8 +381,8 @@ rotate_client_right(struct sc_screen *screen) { } static void -input_manager_process_text_input(struct input_manager *im, - const SDL_TextInputEvent *event) { +sc_input_manager_process_text_input(struct sc_input_manager *im, + const SDL_TextInputEvent *event) { if (!im->kp->ops->process_text) { // The key processor does not support text input return; @@ -401,7 +401,7 @@ input_manager_process_text_input(struct input_manager *im, } static bool -simulate_virtual_finger(struct input_manager *im, +simulate_virtual_finger(struct sc_input_manager *im, enum android_motionevent_action action, struct sc_point point) { bool up = action == AMOTION_EVENT_ACTION_UP; @@ -431,8 +431,8 @@ inverse_point(struct sc_point point, struct sc_size size) { } static void -input_manager_process_key(struct input_manager *im, - const SDL_KeyboardEvent *event) { +sc_input_manager_process_key(struct sc_input_manager *im, + const SDL_KeyboardEvent *event) { // control: indicates the state of the command-line option --no-control bool control = im->control; @@ -629,8 +629,8 @@ input_manager_process_key(struct input_manager *im, } static void -input_manager_process_mouse_motion(struct input_manager *im, - const SDL_MouseMotionEvent *event) { +sc_input_manager_process_mouse_motion(struct sc_input_manager *im, + const SDL_MouseMotionEvent *event) { if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate @@ -668,8 +668,8 @@ input_manager_process_mouse_motion(struct input_manager *im, } static void -input_manager_process_touch(struct input_manager *im, - const SDL_TouchFingerEvent *event) { +sc_input_manager_process_touch(struct sc_input_manager *im, + const SDL_TouchFingerEvent *event) { if (!im->mp->ops->process_touch) { // The mouse processor does not support touch events return; @@ -698,8 +698,8 @@ input_manager_process_touch(struct input_manager *im, } static void -input_manager_process_mouse_button(struct input_manager *im, - const SDL_MouseButtonEvent *event) { +sc_input_manager_process_mouse_button(struct sc_input_manager *im, + const SDL_MouseButtonEvent *event) { bool control = im->control; if (event->which == SDL_TOUCH_MOUSEID) { @@ -807,8 +807,8 @@ input_manager_process_mouse_button(struct input_manager *im, } static void -input_manager_process_mouse_wheel(struct input_manager *im, - const SDL_MouseWheelEvent *event) { +sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, + const SDL_MouseWheelEvent *event) { if (!im->mp->ops->process_mouse_scroll) { // The mouse processor does not support scroll events return; @@ -835,42 +835,42 @@ input_manager_process_mouse_wheel(struct input_manager *im, } bool -input_manager_handle_event(struct input_manager *im, SDL_Event *event) { +sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { switch (event->type) { case SDL_TEXTINPUT: if (!im->control) { return true; } - input_manager_process_text_input(im, &event->text); + sc_input_manager_process_text_input(im, &event->text); return true; case SDL_KEYDOWN: case SDL_KEYUP: // some key events do not interact with the device, so process the // event even if control is disabled - input_manager_process_key(im, &event->key); + sc_input_manager_process_key(im, &event->key); return true; case SDL_MOUSEMOTION: if (!im->control) { break; } - input_manager_process_mouse_motion(im, &event->motion); + sc_input_manager_process_mouse_motion(im, &event->motion); return true; case SDL_MOUSEWHEEL: if (!im->control) { break; } - input_manager_process_mouse_wheel(im, &event->wheel); + sc_input_manager_process_mouse_wheel(im, &event->wheel); return true; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: // some mouse events do not interact with the device, so process // the event even if control is disabled - input_manager_process_mouse_button(im, &event->button); + sc_input_manager_process_mouse_button(im, &event->button); return true; case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: - input_manager_process_touch(im, &event->tfinger); + sc_input_manager_process_touch(im, &event->tfinger); return true; } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 4b5e7967..9eaea23c 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -13,7 +13,7 @@ #include "trait/key_processor.h" #include "trait/mouse_processor.h" -struct input_manager { +struct sc_input_manager { struct controller *controller; struct sc_screen *screen; @@ -42,7 +42,7 @@ struct input_manager { uint64_t next_sequence; // used for request acknowledgements }; -struct input_manager_params { +struct sc_input_manager_params { struct controller *controller; struct sc_screen *screen; struct sc_key_processor *kp; @@ -56,10 +56,10 @@ struct input_manager_params { }; void -input_manager_init(struct input_manager *im, - const struct input_manager_params *params); +sc_input_manager_init(struct sc_input_manager *im, + const struct sc_input_manager_params *params); bool -input_manager_handle_event(struct input_manager *im, SDL_Event *event); +sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event); #endif diff --git a/app/src/screen.c b/app/src/screen.c index 3fca6f21..a2796278 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -484,7 +484,7 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_texture; } - struct input_manager_params im_params = { + struct sc_input_manager_params im_params = { .controller = params->controller, .screen = screen, .kp = params->kp, @@ -496,7 +496,7 @@ sc_screen_init(struct sc_screen *screen, .shortcut_mods = params->shortcut_mods, }; - input_manager_init(&screen->im, &im_params); + sc_input_manager_init(&screen->im, &im_params); // Reset the window size to trigger a SIZE_CHANGED event, to workaround // HiDPI issues with some SDL renderers when several displays having @@ -866,7 +866,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } } - return input_manager_handle_event(&screen->im, event); + return sc_input_manager_handle_event(&screen->im, event); } struct sc_point diff --git a/app/src/screen.h b/app/src/screen.h index b3240ae0..b1a8a58e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -24,7 +24,7 @@ struct sc_screen { bool open; // track the open/close state to assert correct behavior #endif - struct input_manager im; + struct sc_input_manager im; struct sc_video_buffer vb; struct fps_counter fps_counter; From 3a4d5c7f18de27de450191a4b5401ad94f9d7285 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 22:17:30 +0100 Subject: [PATCH 0283/1133] Use sc_ prefix for controller --- app/src/controller.c | 22 +++++++-------- app/src/controller.h | 22 +++++++-------- app/src/input_manager.c | 58 +++++++++++++++++++-------------------- app/src/input_manager.h | 4 +-- app/src/keyboard_inject.c | 6 ++-- app/src/keyboard_inject.h | 4 +-- app/src/mouse_inject.c | 10 +++---- app/src/mouse_inject.h | 5 ++-- app/src/scrcpy.c | 16 +++++------ app/src/screen.h | 2 +- 10 files changed, 75 insertions(+), 74 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 10eceaf2..531b5fb9 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -5,8 +5,8 @@ #include "util/log.h" bool -controller_init(struct controller *controller, sc_socket control_socket, - struct sc_acksync *acksync) { +sc_controller_init(struct sc_controller *controller, sc_socket control_socket, + struct sc_acksync *acksync) { cbuf_init(&controller->queue); bool ok = receiver_init(&controller->receiver, control_socket, acksync); @@ -34,7 +34,7 @@ controller_init(struct controller *controller, sc_socket control_socket, } void -controller_destroy(struct controller *controller) { +sc_controller_destroy(struct sc_controller *controller) { sc_cond_destroy(&controller->msg_cond); sc_mutex_destroy(&controller->mutex); @@ -47,8 +47,8 @@ controller_destroy(struct controller *controller) { } bool -controller_push_msg(struct controller *controller, - const struct control_msg *msg) { +sc_controller_push_msg(struct sc_controller *controller, + const struct control_msg *msg) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { control_msg_log(msg); } @@ -64,7 +64,7 @@ controller_push_msg(struct controller *controller, } static bool -process_msg(struct controller *controller, const struct control_msg *msg) { +process_msg(struct sc_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) { @@ -77,7 +77,7 @@ process_msg(struct controller *controller, const struct control_msg *msg) { static int run_controller(void *data) { - struct controller *controller = data; + struct sc_controller *controller = data; for (;;) { sc_mutex_lock(&controller->mutex); @@ -106,7 +106,7 @@ run_controller(void *data) { } bool -controller_start(struct controller *controller) { +sc_controller_start(struct sc_controller *controller) { LOGD("Starting controller thread"); bool ok = sc_thread_create(&controller->thread, run_controller, @@ -117,7 +117,7 @@ controller_start(struct controller *controller) { } if (!receiver_start(&controller->receiver)) { - controller_stop(controller); + sc_controller_stop(controller); sc_thread_join(&controller->thread, NULL); return false; } @@ -126,7 +126,7 @@ controller_start(struct controller *controller) { } void -controller_stop(struct controller *controller) { +sc_controller_stop(struct sc_controller *controller) { sc_mutex_lock(&controller->mutex); controller->stopped = true; sc_cond_signal(&controller->msg_cond); @@ -134,7 +134,7 @@ controller_stop(struct controller *controller) { } void -controller_join(struct controller *controller) { +sc_controller_join(struct sc_controller *controller) { sc_thread_join(&controller->thread, NULL); receiver_join(&controller->receiver); } diff --git a/app/src/controller.h b/app/src/controller.h index 00267878..06ceb782 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -12,36 +12,36 @@ #include "util/net.h" #include "util/thread.h" -struct control_msg_queue CBUF(struct control_msg, 64); +struct sc_control_msg_queue CBUF(struct control_msg, 64); -struct controller { +struct sc_controller { sc_socket control_socket; sc_thread thread; sc_mutex mutex; sc_cond msg_cond; bool stopped; - struct control_msg_queue queue; + struct sc_control_msg_queue queue; struct receiver receiver; }; bool -controller_init(struct controller *controller, sc_socket control_socket, - struct sc_acksync *acksync); +sc_controller_init(struct sc_controller *controller, sc_socket control_socket, + struct sc_acksync *acksync); void -controller_destroy(struct controller *controller); +sc_controller_destroy(struct sc_controller *controller); bool -controller_start(struct controller *controller); +sc_controller_start(struct sc_controller *controller); void -controller_stop(struct controller *controller); +sc_controller_stop(struct sc_controller *controller); void -controller_join(struct controller *controller); +sc_controller_join(struct sc_controller *controller); bool -controller_push_msg(struct controller *controller, - const struct control_msg *msg); +sc_controller_push_msg(struct sc_controller *controller, + const struct control_msg *msg); #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 6eac46aa..5b355701 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -157,7 +157,7 @@ sc_input_manager_init(struct sc_input_manager *im, } static void -send_keycode(struct controller *controller, enum android_keycode keycode, +send_keycode(struct sc_controller *controller, enum android_keycode keycode, enum sc_action action, const char *name) { // send DOWN event struct control_msg msg; @@ -169,50 +169,50 @@ send_keycode(struct controller *controller, enum android_keycode keycode, msg.inject_keycode.metastate = 0; msg.inject_keycode.repeat = 0; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'inject %s'", name); } } static inline void -action_home(struct controller *controller, enum sc_action action) { +action_home(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_HOME, action, "HOME"); } static inline void -action_back(struct controller *controller, enum sc_action action) { +action_back(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_BACK, action, "BACK"); } static inline void -action_app_switch(struct controller *controller, enum sc_action action) { +action_app_switch(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH"); } static inline void -action_power(struct controller *controller, enum sc_action action) { +action_power(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_POWER, action, "POWER"); } static inline void -action_volume_up(struct controller *controller, enum sc_action action) { +action_volume_up(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP"); } static inline void -action_volume_down(struct controller *controller, enum sc_action action) { +action_volume_down(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN"); } static inline void -action_menu(struct controller *controller, enum sc_action action) { +action_menu(struct sc_controller *controller, enum sc_action action) { send_keycode(controller, AKEYCODE_MENU, action, "MENU"); } // turn the screen on if it was off, press BACK otherwise // If the screen is off, it is turned on only on ACTION_DOWN static void -press_back_or_turn_screen_on(struct controller *controller, +press_back_or_turn_screen_on(struct sc_controller *controller, enum sc_action action) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; @@ -220,49 +220,49 @@ press_back_or_turn_screen_on(struct controller *controller, ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'press back or turn screen on'"); } } static void -expand_notification_panel(struct controller *controller) { +expand_notification_panel(struct sc_controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'expand notification panel'"); } } static void -expand_settings_panel(struct controller *controller) { +expand_settings_panel(struct sc_controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'expand settings panel'"); } } static void -collapse_panels(struct controller *controller) { +collapse_panels(struct sc_controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'collapse notification panel'"); } } static bool -get_device_clipboard(struct controller *controller, +get_device_clipboard(struct sc_controller *controller, enum get_clipboard_copy_key copy_key) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.get_clipboard.copy_key = copy_key; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'get device clipboard'"); return false; } @@ -271,7 +271,7 @@ get_device_clipboard(struct controller *controller, } static bool -set_device_clipboard(struct controller *controller, bool paste, +set_device_clipboard(struct sc_controller *controller, bool paste, uint64_t sequence) { char *text = SDL_GetClipboardText(); if (!text) { @@ -292,7 +292,7 @@ set_device_clipboard(struct controller *controller, bool paste, msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { free(text_dup); LOGW("Could not request 'set device clipboard'"); return false; @@ -302,13 +302,13 @@ set_device_clipboard(struct controller *controller, bool paste, } static void -set_screen_power_mode(struct controller *controller, +set_screen_power_mode(struct sc_controller *controller, enum screen_power_mode mode) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = mode; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'set screen power mode'"); } } @@ -330,7 +330,7 @@ switch_fps_counter_state(struct fps_counter *fps_counter) { } static void -clipboard_paste(struct controller *controller) { +clipboard_paste(struct sc_controller *controller) { char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); @@ -352,18 +352,18 @@ clipboard_paste(struct controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = text_dup; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { free(text_dup); LOGW("Could not request 'paste clipboard'"); } } static void -rotate_device(struct controller *controller) { +rotate_device(struct sc_controller *controller) { struct control_msg msg; msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE; - if (!controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request device rotation"); } } @@ -415,7 +415,7 @@ simulate_virtual_finger(struct sc_input_manager *im, msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.buttons = 0; - if (!controller_push_msg(im->controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'inject virtual finger event'"); return false; } @@ -436,7 +436,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, // control: indicates the state of the command-line option --no-control bool control = im->control; - struct controller *controller = im->controller; + struct sc_controller *controller = im->controller; SDL_Keycode keycode = event->keysym.sym; uint16_t mod = event->keysym.mod; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 9eaea23c..5d552ee2 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -14,7 +14,7 @@ #include "trait/mouse_processor.h" struct sc_input_manager { - struct controller *controller; + struct sc_controller *controller; struct sc_screen *screen; struct sc_key_processor *kp; @@ -43,7 +43,7 @@ struct sc_input_manager { }; struct sc_input_manager_params { - struct controller *controller; + struct sc_controller *controller; struct sc_screen *screen; struct sc_key_processor *kp; struct sc_mouse_processor *mp; diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index b6dd69fb..9c141a37 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -284,7 +284,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, struct control_msg msg; if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { - if (!controller_push_msg(ki->controller, &msg)) { + if (!sc_controller_push_msg(ki->controller, &msg)) { LOGW("Could not request 'inject keycode'"); } } @@ -316,7 +316,7 @@ sc_key_processor_process_text(struct sc_key_processor *kp, LOGW("Could not strdup input text"); return; } - if (!controller_push_msg(ki->controller, &msg)) { + if (!sc_controller_push_msg(ki->controller, &msg)) { free(msg.inject_text.text); LOGW("Could not request 'inject text'"); } @@ -324,7 +324,7 @@ sc_key_processor_process_text(struct sc_key_processor *kp, void sc_keyboard_inject_init(struct sc_keyboard_inject *ki, - struct controller *controller, + struct sc_controller *controller, enum sc_key_inject_mode key_inject_mode, bool forward_key_repeat) { ki->controller = controller; diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_inject.h index 9b25425a..b7781c1f 100644 --- a/app/src/keyboard_inject.h +++ b/app/src/keyboard_inject.h @@ -12,7 +12,7 @@ struct sc_keyboard_inject { struct sc_key_processor key_processor; // key processor trait - struct controller *controller; + struct sc_controller *controller; // SDL reports repeated events as a boolean, but Android expects the actual // number of repetitions. This variable keeps track of the count. @@ -24,7 +24,7 @@ struct sc_keyboard_inject { void sc_keyboard_inject_init(struct sc_keyboard_inject *ki, - struct controller *controller, + struct sc_controller *controller, enum sc_key_inject_mode key_inject_mode, bool forward_key_repeat); diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 8a4a0531..106c215f 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -76,7 +76,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, }, }; - if (!controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse motion event'"); } } @@ -97,7 +97,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, }, }; - if (!controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse click event'"); } } @@ -117,7 +117,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, }, }; - if (!controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject mouse scroll event'"); } } @@ -138,14 +138,14 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, }, }; - if (!controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(mi->controller, &msg)) { LOGW("Could not request 'inject touch event'"); } } void sc_mouse_inject_init(struct sc_mouse_inject *mi, - struct controller *controller) { + struct sc_controller *controller) { mi->controller = controller; static const struct sc_mouse_processor_ops ops = { diff --git a/app/src/mouse_inject.h b/app/src/mouse_inject.h index 50591e2b..59a6a5d8 100644 --- a/app/src/mouse_inject.h +++ b/app/src/mouse_inject.h @@ -12,10 +12,11 @@ struct sc_mouse_inject { struct sc_mouse_processor mouse_processor; // mouse processor trait - struct controller *controller; + struct sc_controller *controller; }; void -sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller); +sc_mouse_inject_init(struct sc_mouse_inject *mi, + struct sc_controller *controller); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9237c59b..e1869eec 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -43,7 +43,7 @@ struct scrcpy { #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; #endif - struct controller controller; + struct sc_controller controller; struct file_handler file_handler; #ifdef HAVE_AOA_HID struct sc_aoa aoa; @@ -546,13 +546,13 @@ aoa_hid_end: mp = &s->mouse_inject.mouse_processor; } - if (!controller_init(&s->controller, s->server.control_socket, - acksync)) { + if (!sc_controller_init(&s->controller, s->server.control_socket, + acksync)) { goto end; } controller_initialized = true; - if (!controller_start(&s->controller)) { + if (!sc_controller_start(&s->controller)) { goto end; } controller_started = true; @@ -562,7 +562,7 @@ aoa_hid_end: msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; - if (!controller_push_msg(&s->controller, &msg)) { + if (!sc_controller_push_msg(&s->controller, &msg)) { LOGW("Could not request 'set screen power mode'"); } } @@ -646,7 +646,7 @@ end: } #endif if (controller_started) { - controller_stop(&s->controller); + sc_controller_stop(&s->controller); } if (file_handler_initialized) { file_handler_stop(&s->file_handler); @@ -687,10 +687,10 @@ end: } if (controller_started) { - controller_join(&s->controller); + sc_controller_join(&s->controller); } if (controller_initialized) { - controller_destroy(&s->controller); + sc_controller_destroy(&s->controller); } if (recorder_initialized) { diff --git a/app/src/screen.h b/app/src/screen.h index b1a8a58e..4810de31 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -60,7 +60,7 @@ struct sc_screen { }; struct sc_screen_params { - struct controller *controller; + struct sc_controller *controller; struct sc_key_processor *kp; struct sc_mouse_processor *mp; From afa4a1b728d6f130acff23c0e3fb4609bcb8a6b2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jan 2022 22:17:30 +0100 Subject: [PATCH 0284/1133] Use sc_ prefix for control_msg --- app/src/control_msg.c | 6 +-- app/src/control_msg.h | 12 +++--- app/src/controller.c | 17 ++++---- app/src/controller.h | 4 +- app/src/input_manager.c | 22 +++++----- app/src/keyboard_inject.c | 6 +-- app/src/mouse_inject.c | 8 ++-- app/src/scrcpy.c | 2 +- app/tests/test_control_msg_serialize.c | 56 +++++++++++++------------- 9 files changed, 67 insertions(+), 66 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 75c74628..d093183a 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -89,7 +89,7 @@ to_fixed_point_16(float f) { } size_t -control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { +sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { buf[0] = msg->type; switch (msg->type) { case CONTROL_MSG_TYPE_INJECT_KEYCODE: @@ -151,7 +151,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) { } void -control_msg_log(const struct control_msg *msg) { +sc_control_msg_log(const struct sc_control_msg *msg) { #define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__) switch (msg->type) { case CONTROL_MSG_TYPE_INJECT_KEYCODE: @@ -237,7 +237,7 @@ control_msg_log(const struct control_msg *msg) { } void -control_msg_destroy(struct control_msg *msg) { +sc_control_msg_destroy(struct sc_control_msg *msg) { switch (msg->type) { case CONTROL_MSG_TYPE_INJECT_TEXT: free(msg->inject_text.text); diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 0eadd4f2..2ac96064 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -20,7 +20,7 @@ #define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) -enum control_msg_type { +enum sc_control_msg_type { CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, @@ -47,8 +47,8 @@ enum get_clipboard_copy_key { GET_CLIPBOARD_COPY_KEY_CUT, }; -struct control_msg { - enum control_msg_type type; +struct sc_control_msg { + enum sc_control_msg_type type; union { struct { enum android_keyevent_action action; @@ -93,12 +93,12 @@ struct control_msg { // buf size must be at least CONTROL_MSG_MAX_SIZE // return the number of bytes written size_t -control_msg_serialize(const struct control_msg *msg, unsigned char *buf); +sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf); void -control_msg_log(const struct control_msg *msg); +sc_control_msg_log(const struct sc_control_msg *msg); void -control_msg_destroy(struct control_msg *msg); +sc_control_msg_destroy(struct sc_control_msg *msg); #endif diff --git a/app/src/controller.c b/app/src/controller.c index 531b5fb9..9135d967 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -38,9 +38,9 @@ sc_controller_destroy(struct sc_controller *controller) { sc_cond_destroy(&controller->msg_cond); sc_mutex_destroy(&controller->mutex); - struct control_msg msg; + struct sc_control_msg msg; while (cbuf_take(&controller->queue, &msg)) { - control_msg_destroy(&msg); + sc_control_msg_destroy(&msg); } receiver_destroy(&controller->receiver); @@ -48,9 +48,9 @@ sc_controller_destroy(struct sc_controller *controller) { bool sc_controller_push_msg(struct sc_controller *controller, - const struct control_msg *msg) { + const struct sc_control_msg *msg) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { - control_msg_log(msg); + sc_control_msg_log(msg); } sc_mutex_lock(&controller->mutex); @@ -64,9 +64,10 @@ sc_controller_push_msg(struct sc_controller *controller, } static bool -process_msg(struct sc_controller *controller, const struct control_msg *msg) { +process_msg(struct sc_controller *controller, + const struct sc_control_msg *msg) { static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; - size_t length = control_msg_serialize(msg, serialized_msg); + size_t length = sc_control_msg_serialize(msg, serialized_msg); if (!length) { return false; } @@ -89,14 +90,14 @@ run_controller(void *data) { sc_mutex_unlock(&controller->mutex); break; } - struct control_msg msg; + struct sc_control_msg msg; bool non_empty = cbuf_take(&controller->queue, &msg); assert(non_empty); (void) non_empty; sc_mutex_unlock(&controller->mutex); bool ok = process_msg(controller, &msg); - control_msg_destroy(&msg); + sc_control_msg_destroy(&msg); if (!ok) { LOGD("Could not write msg to socket"); break; diff --git a/app/src/controller.h b/app/src/controller.h index 06ceb782..f8bc7c02 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -12,7 +12,7 @@ #include "util/net.h" #include "util/thread.h" -struct sc_control_msg_queue CBUF(struct control_msg, 64); +struct sc_control_msg_queue CBUF(struct sc_control_msg, 64); struct sc_controller { sc_socket control_socket; @@ -42,6 +42,6 @@ sc_controller_join(struct sc_controller *controller); bool sc_controller_push_msg(struct sc_controller *controller, - const struct control_msg *msg); + const struct sc_control_msg *msg); #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 5b355701..9eee6e6c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -160,7 +160,7 @@ static void send_keycode(struct sc_controller *controller, enum android_keycode keycode, enum sc_action action, const char *name) { // send DOWN event - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; msg.inject_keycode.action = action == SC_ACTION_DOWN ? AKEY_EVENT_ACTION_DOWN @@ -214,7 +214,7 @@ action_menu(struct sc_controller *controller, enum sc_action action) { static void press_back_or_turn_screen_on(struct sc_controller *controller, enum sc_action action) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.back_or_screen_on.action = action == SC_ACTION_DOWN ? AKEY_EVENT_ACTION_DOWN @@ -227,7 +227,7 @@ press_back_or_turn_screen_on(struct sc_controller *controller, static void expand_notification_panel(struct sc_controller *controller) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; if (!sc_controller_push_msg(controller, &msg)) { @@ -237,7 +237,7 @@ expand_notification_panel(struct sc_controller *controller) { static void expand_settings_panel(struct sc_controller *controller) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; if (!sc_controller_push_msg(controller, &msg)) { @@ -247,7 +247,7 @@ expand_settings_panel(struct sc_controller *controller) { static void collapse_panels(struct sc_controller *controller) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS; if (!sc_controller_push_msg(controller, &msg)) { @@ -258,7 +258,7 @@ collapse_panels(struct sc_controller *controller) { static bool get_device_clipboard(struct sc_controller *controller, enum get_clipboard_copy_key copy_key) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.get_clipboard.copy_key = copy_key; @@ -286,7 +286,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste, return false; } - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.set_clipboard.sequence = sequence; msg.set_clipboard.text = text_dup; @@ -304,7 +304,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste, static void set_screen_power_mode(struct sc_controller *controller, enum screen_power_mode mode) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = mode; @@ -349,7 +349,7 @@ clipboard_paste(struct sc_controller *controller) { return; } - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = text_dup; if (!sc_controller_push_msg(controller, &msg)) { @@ -360,7 +360,7 @@ clipboard_paste(struct sc_controller *controller) { static void rotate_device(struct sc_controller *controller) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE; if (!sc_controller_push_msg(controller, &msg)) { @@ -406,7 +406,7 @@ simulate_virtual_finger(struct sc_input_manager *im, struct sc_point point) { bool up = action == AMOTION_EVENT_ACTION_UP; - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; msg.inject_touch_event.action = action; msg.inject_touch_event.position.screen_size = im->screen->frame_size; diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 9c141a37..7276d325 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -246,7 +246,7 @@ convert_meta_state(uint16_t mod) { } static bool -convert_input_key(const struct sc_key_event *event, struct control_msg *msg, +convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg, enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; @@ -282,7 +282,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, ki->repeat = 0; } - struct control_msg msg; + struct sc_control_msg msg; if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { if (!sc_controller_push_msg(ki->controller, &msg)) { LOGW("Could not request 'inject keycode'"); @@ -309,7 +309,7 @@ sc_key_processor_process_text(struct sc_key_processor *kp, } } - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = strdup(event->text); if (!msg.inject_text.text) { diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 106c215f..855aaa9f 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -65,7 +65,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_MOVE, @@ -86,7 +86,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = convert_mouse_action(event->action), @@ -107,7 +107,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, const struct sc_mouse_scroll_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = event->position, @@ -127,7 +127,7 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, const struct sc_touch_event *event) { struct sc_mouse_inject *mi = DOWNCAST(mp); - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = convert_touch_action(event->action), diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e1869eec..86fdf984 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -558,7 +558,7 @@ aoa_hid_end: controller_started = true; if (options->turn_screen_off) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index d1f0f161..95a54a98 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -6,7 +6,7 @@ #include "control_msg.h" static void test_serialize_inject_keycode(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_KEYCODE, .inject_keycode = { .action = AKEY_EVENT_ACTION_UP, @@ -17,7 +17,7 @@ static void test_serialize_inject_keycode(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 14); const unsigned char expected[] = { @@ -31,7 +31,7 @@ static void test_serialize_inject_keycode(void) { } static void test_serialize_inject_text(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_TEXT, .inject_text = { .text = "hello, world!", @@ -39,7 +39,7 @@ static void test_serialize_inject_text(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 18); const unsigned char expected[] = { @@ -51,7 +51,7 @@ static void test_serialize_inject_text(void) { } static void test_serialize_inject_text_long(void) { - struct control_msg msg; + struct sc_control_msg msg; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); @@ -59,7 +59,7 @@ static void test_serialize_inject_text_long(void) { msg.inject_text.text = text; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; @@ -74,7 +74,7 @@ static void test_serialize_inject_text_long(void) { } static void test_serialize_inject_touch_event(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_DOWN, @@ -95,7 +95,7 @@ static void test_serialize_inject_touch_event(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 28); const unsigned char expected[] = { @@ -111,7 +111,7 @@ static void test_serialize_inject_touch_event(void) { } static void test_serialize_inject_scroll_event(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = { @@ -131,7 +131,7 @@ static void test_serialize_inject_scroll_event(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 25); const unsigned char expected[] = { @@ -146,7 +146,7 @@ static void test_serialize_inject_scroll_event(void) { } static void test_serialize_back_or_screen_on(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, .back_or_screen_on = { .action = AKEY_EVENT_ACTION_UP, @@ -154,7 +154,7 @@ static void test_serialize_back_or_screen_on(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { @@ -165,12 +165,12 @@ static void test_serialize_back_or_screen_on(void) { } static void test_serialize_expand_notification_panel(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -180,12 +180,12 @@ static void test_serialize_expand_notification_panel(void) { } static void test_serialize_expand_settings_panel(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -195,12 +195,12 @@ static void test_serialize_expand_settings_panel(void) { } static void test_serialize_collapse_panels(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { @@ -210,7 +210,7 @@ static void test_serialize_collapse_panels(void) { } static void test_serialize_get_clipboard(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_GET_CLIPBOARD, .get_clipboard = { .copy_key = GET_CLIPBOARD_COPY_KEY_COPY, @@ -218,7 +218,7 @@ static void test_serialize_get_clipboard(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { @@ -229,7 +229,7 @@ static void test_serialize_get_clipboard(void) { } static void test_serialize_set_clipboard(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { .sequence = UINT64_C(0x0102030405060708), @@ -239,7 +239,7 @@ static void test_serialize_set_clipboard(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 27); const unsigned char expected[] = { @@ -253,7 +253,7 @@ static void test_serialize_set_clipboard(void) { } static void test_serialize_set_clipboard_long(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { .sequence = UINT64_C(0x0102030405060708), @@ -268,7 +268,7 @@ static void test_serialize_set_clipboard_long(void) { msg.set_clipboard.text = text; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == CONTROL_MSG_MAX_SIZE); unsigned char expected[CONTROL_MSG_MAX_SIZE] = { @@ -287,7 +287,7 @@ static void test_serialize_set_clipboard_long(void) { } static void test_serialize_set_screen_power_mode(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, .set_screen_power_mode = { .mode = SCREEN_POWER_MODE_NORMAL, @@ -295,7 +295,7 @@ static void test_serialize_set_screen_power_mode(void) { }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { @@ -306,12 +306,12 @@ static void test_serialize_set_screen_power_mode(void) { } static void test_serialize_rotate_device(void) { - struct control_msg msg = { + struct sc_control_msg msg = { .type = CONTROL_MSG_TYPE_ROTATE_DEVICE, }; unsigned char buf[CONTROL_MSG_MAX_SIZE]; - size_t size = control_msg_serialize(&msg, buf); + size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { From 1c71bd16bec1db0788e72a7c6b02f80ed40f1601 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 22:57:38 +0100 Subject: [PATCH 0285/1133] Use constant string for known booleans Boolean options explicitly passed to the server are statically known. --- app/src/server.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index ab5439ad..ea7e5377 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -188,7 +188,6 @@ execute_server(struct sc_server *server, } \ cmd[count++] = p; \ } -#define STRBOOL(v) (v ? "true" : "false") ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); @@ -204,23 +203,23 @@ execute_server(struct sc_server *server, params->lock_video_orientation); } if (server->tunnel.forward) { - ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward)); + ADD_PARAM("tunnel_forward=true"); } if (params->crop) { ADD_PARAM("crop=%s", params->crop); } if (!params->control) { // By default, control is true - ADD_PARAM("control=%s", STRBOOL(params->control)); + ADD_PARAM("control=false"); } if (params->display_id) { ADD_PARAM("display_id=%" PRIu32, params->display_id); } if (params->show_touches) { - ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches)); + ADD_PARAM("show_touches=true"); } if (params->stay_awake) { - ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake)); + ADD_PARAM("stay_awake=true"); } if (params->codec_options) { ADD_PARAM("codec_options=%s", params->codec_options); @@ -229,11 +228,11 @@ execute_server(struct sc_server *server, ADD_PARAM("encoder_name=%s", params->encoder_name); } if (params->power_off_on_close) { - ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close)); + ADD_PARAM("power_off_on_close=true"); } if (!params->clipboard_autosync) { // By default, clipboard_autosync is true - ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync)); + ADD_PARAM("clipboard_autosync=false"); } #undef ADD_PARAM From 60bf133ac28c76bcbef79e6610f34c6b66e5e82e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 23:04:37 +0100 Subject: [PATCH 0286/1133] Add final modifier to ScreenEncoder fields These fields are only set from the constructor. --- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f98c53d0..ce6b556a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -30,11 +30,11 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); - private String encoderName; - private List codecOptions; - private int bitRate; - private int maxFps; - private boolean sendFrameMeta; + private final String encoderName; + private final List codecOptions; + private final int bitRate; + private final int maxFps; + private final boolean sendFrameMeta; private long ptsOrigin; public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName) { From 5e8fa56e7a8b13b7c9c6d0d4ee56636c76b821d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernhard=20Rosenkr=C3=A4nzer?= Date: Sun, 16 Jan 2022 01:45:36 +0100 Subject: [PATCH 0287/1133] Fix build with ffmpeg 5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #2948 Signed-off-by: Bernhard Rosenkränzer Signed-off-by: Romain Vimont --- app/src/decoder.h | 1 + app/src/icon.c | 3 ++- app/src/stream.c | 3 +-- app/src/stream.h | 1 + app/src/v4l2_sink.h | 5 +++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/decoder.h b/app/src/decoder.h index 257f751a..e2972cb1 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -6,6 +6,7 @@ #include "trait/packet_sink.h" #include +#include #include #define DECODER_MAX_SINKS 2 diff --git a/app/src/icon.c b/app/src/icon.c index 1d670242..e709678f 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -85,7 +86,7 @@ decode_image(const char *path) { AVCodecParameters *params = ctx->streams[stream]->codecpar; - AVCodec *codec = avcodec_find_decoder(params->codec_id); + const AVCodec *codec = avcodec_find_decoder(params->codec_id); if (!codec) { LOGE("Could not find image decoder"); goto close_input; diff --git a/app/src/stream.c b/app/src/stream.c index f8d73a27..c873c4ad 100644 --- a/app/src/stream.c +++ b/app/src/stream.c @@ -1,7 +1,6 @@ #include "stream.h" #include -#include #include #include @@ -192,7 +191,7 @@ static int run_stream(void *data) { struct stream *stream = data; - AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); + const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { LOGE("H.264 decoder not found"); goto end; diff --git a/app/src/stream.h b/app/src/stream.h index 362bc4a7..bdcefe39 100644 --- a/app/src/stream.h +++ b/app/src/stream.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "trait/packet_sink.h" diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index 8737a607..339a61f2 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -3,13 +3,14 @@ #include "common.h" +#include +#include + #include "coords.h" #include "trait/frame_sink.h" #include "video_buffer.h" #include "util/tick.h" -#include - struct sc_v4l2_sink { struct sc_frame_sink frame_sink; // frame sink trait From 479abc8c7723d2d05b7c5027528acd91946a328c Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Fri, 14 Jan 2022 11:07:15 +0100 Subject: [PATCH 0288/1133] Reference Windows USB driver for Google devices PR #2945 Signed-off-by: Romain Vimont --- FAQ.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index d5f0e3ee..399f3504 100644 --- a/FAQ.md +++ b/FAQ.md @@ -43,10 +43,11 @@ Check [stackoverflow][device-unauthorized]. Check that you correctly enabled [adb debugging][enable-adb]. -If your device is not detected, you may need some [drivers] (on Windows). +If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver]. [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [drivers]: https://developer.android.com/studio/run/oem-usb.html +[google-usb-driver]: https://developer.android.com/studio/run/win-usb ### Several devices connected From 37c7827d463ba99f87aa87553b661946113d0412 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 Jan 2022 19:00:39 +0100 Subject: [PATCH 0289/1133] Simplify ffmpeg dependencies Makefile The fact that the current prebuilt FFmpeg is split into two separate zipfiles is an implementation detail. Use a single Makefile recipe for both files. PR #2952 --- prebuilt-deps/Makefile | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index fa986978..fe6d8217 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -1,30 +1,24 @@ .PHONY: prepare-win32 prepare-win64 \ - prepare-ffmpeg-shared-win32 \ - prepare-ffmpeg-dev-win32 \ - prepare-ffmpeg-shared-win64 \ - prepare-ffmpeg-dev-win64 \ + prepare-ffmpeg-win32 \ + prepare-ffmpeg-win64 \ prepare-sdl2 \ prepare-adb -prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb -prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb +prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb +prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb -prepare-ffmpeg-shared-win32: +prepare-ffmpeg-win32: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ ffmpeg-4.3.1-win32-shared - -prepare-ffmpeg-dev-win32: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \ 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ ffmpeg-4.3.1-win32-dev -prepare-ffmpeg-shared-win64: +prepare-ffmpeg-win64: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \ dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \ ffmpeg-4.3.1-win64-shared - -prepare-ffmpeg-dev-win64: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \ 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ ffmpeg-4.3.1-win64-dev From a2495c5ef192e86f4dc5204e4e250f3b6cc56ef4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 Jan 2022 19:04:18 +0100 Subject: [PATCH 0290/1133] Use symlink to simplify Windows ffmpeg dependency The FFmpeg dependency is downloaded from two separate zipfiles. Symlink include/ to expose everything from a single directory, to simplify the meson script. PR #2952 --- app/meson.build | 7 +++---- cross_win32.txt | 3 +-- cross_win64.txt | 3 +-- prebuilt-deps/Makefile | 2 ++ 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/meson.build b/app/meson.build index cee261bb..39334a3d 100644 --- a/app/meson.build +++ b/app/meson.build @@ -118,10 +118,9 @@ else include_directories: include_directories(sdl2_include_dir) ) - prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared') - prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev') - ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin' - ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include' + prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') + ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin' + ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include' ffmpeg = declare_dependency( dependencies: [ cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir), diff --git a/cross_win32.txt b/cross_win32.txt index b0e43622..045cb5c6 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,5 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' +prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared' prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 6625c0cf..d48c137a 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,5 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' -prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' +prebuilt_ffmpeg = 'ffmpeg-4.3.1-win64-shared' prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index fe6d8217..41b7cf2a 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -14,6 +14,7 @@ prepare-ffmpeg-win32: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \ 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ ffmpeg-4.3.1-win32-dev + ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/ prepare-ffmpeg-win64: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \ @@ -22,6 +23,7 @@ prepare-ffmpeg-win64: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \ 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ ffmpeg-4.3.1-win64-dev + ln -sf ../ffmpeg-4.3.1-win64-dev/include ffmpeg-4.3.1-win64-shared/ prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \ From b3ff1f6b3b6629a0ed55e759178b3e3378572cc4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 Jan 2022 19:11:23 +0100 Subject: [PATCH 0291/1133] Upgrade FFmpeg (5.0) for Windows 64-bit Use FFmpeg win64 binaries from gyan.dev (referenced from ffmpeg.org): - https://www.gyan.dev/ffmpeg/builds/ - https://ffmpeg.org/download.html#build-windows Keep the old FFmpeg prebuilt binaries (4.3.1) for win32 builds. Fixes #1753 Refs #1838 Refs #2583 PR #2952 Co-authored-by: Yu-Chen Lin Co-authored-by: nkh0472 Signed-off-by: Romain Vimont --- app/meson.build | 12 +++++++++--- cross_win32.txt | 3 +++ cross_win64.txt | 5 ++++- prebuilt-deps/Makefile | 11 ++++------- prebuilt-deps/prepare-dep | 3 +++ release.mk | 10 +++++----- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/meson.build b/app/meson.build index 39334a3d..319dd0b6 100644 --- a/app/meson.build +++ b/app/meson.build @@ -121,11 +121,17 @@ else prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin' ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include' + + # ffmpeg versions are different for win32 and win64 builds + ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec') + ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat') + ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil') + ffmpeg = declare_dependency( dependencies: [ - cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir), - cc.find_library('avformat-58', dirs: ffmpeg_bin_dir), - cc.find_library('avutil-56', dirs: ffmpeg_bin_dir), + cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir), + cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir), + cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir), ], include_directories: include_directories(ffmpeg_include_dir) ) diff --git a/cross_win32.txt b/cross_win32.txt index 045cb5c6..826c044f 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,5 +16,8 @@ cpu = 'i686' endian = 'little' [properties] +ffmpeg_avcodec = 'avcodec-58' +ffmpeg_avformat = 'avformat-58' +ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared' prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index d48c137a..2e049fdb 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,5 +16,8 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-4.3.1-win64-shared' +ffmpeg_avcodec = 'avcodec-59' +ffmpeg_avformat = 'avformat-59' +ffmpeg_avutil = 'avutil-57' +prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared' prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 41b7cf2a..93e8c48d 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -7,6 +7,7 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb +# Use old FFmpeg version for win32, there are no new prebuilts prepare-ffmpeg-win32: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ @@ -17,13 +18,9 @@ prepare-ffmpeg-win32: ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/ prepare-ffmpeg-win64: - @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \ - dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \ - ffmpeg-4.3.1-win64-shared - @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \ - 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ - ffmpeg-4.3.1-win64-dev - ln -sf ../ffmpeg-4.3.1-win64-dev/include ffmpeg-4.3.1-win64-shared/ + @./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \ + e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \ + ffmpeg-5.0-full_build-shared prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \ diff --git a/prebuilt-deps/prepare-dep b/prebuilt-deps/prepare-dep index f152e6cf..a95b9bb6 100755 --- a/prebuilt-deps/prepare-dep +++ b/prebuilt-deps/prepare-dep @@ -34,6 +34,9 @@ extract() { elif [[ "$file" == *.tar.gz ]] then tar xf "$file" + elif [[ "$file" == *.7z ]] + then + 7z x "$file" else echo "Unsupported file: $file" return 1 diff --git a/release.mk b/release.mk index 6414f079..aa26f02d 100644 --- a/release.mk +++ b/release.mk @@ -110,11 +110,11 @@ dist-win64: build-server build-win64 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)/" - cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From b7a06278fee0dbfbb18b651ad563d9fb0797c8bc Mon Sep 17 00:00:00 2001 From: Thomas Rebele Date: Sat, 15 Jan 2022 16:27:51 +0100 Subject: [PATCH 0292/1133] Fix NoSuchMethodException for injectInputEvent() Some devices with modified ROMs expose a different signature for injectInputEvent(). Fixes #2250 PR #2946 Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/wrappers/InputManager.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index e17b5a17..dc7bc8da 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -16,6 +16,7 @@ public final class InputManager { private final IInterface manager; private Method injectInputEventMethod; + boolean alternativeInjectInputEventMethod; private static Method setDisplayIdMethod; @@ -25,7 +26,12 @@ public final class InputManager { private Method getInjectInputEventMethod() throws NoSuchMethodException { if (injectInputEventMethod == null) { - injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); + try { + injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); + } catch (NoSuchMethodException e) { + injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class); + alternativeInjectInputEventMethod = true; + } } return injectInputEventMethod; } @@ -33,6 +39,10 @@ public final class InputManager { public boolean injectInputEvent(InputEvent inputEvent, int mode) { try { Method method = getInjectInputEventMethod(); + if (alternativeInjectInputEventMethod) { + // See + return (boolean) method.invoke(manager, inputEvent, mode, 0); + } return (boolean) method.invoke(manager, inputEvent, mode); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); From 117fe32626892c57a432ccd19347d36ae83661bd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 18:36:44 +0100 Subject: [PATCH 0293/1133] Fix visibility modifier Refs b7a06278fee0dbfbb18b651ad563d9fb0797c8bc --- .../main/java/com/genymobile/scrcpy/wrappers/InputManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index dc7bc8da..61168993 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -16,7 +16,7 @@ public final class InputManager { private final IInterface manager; private Method injectInputEventMethod; - boolean alternativeInjectInputEventMethod; + private boolean alternativeInjectInputEventMethod; private static Method setDisplayIdMethod; From 162043911e1d742489bf836113fe0797191a5160 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 22:13:50 +0100 Subject: [PATCH 0294/1133] Compute screen size without DisplayInfo instance Use the actual rotation and size values directly. This will allow to automatically change the maxSize value on MediaCodec error. PR #2947 --- server/src/main/java/com/genymobile/scrcpy/Device.java | 3 ++- server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index ba833a06..419e996e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -69,7 +69,8 @@ public final class Device { int displayInfoFlags = displayInfo.getFlags(); - screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation()); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), options.getMaxSize(), + options.getLockVideoOrientation()); layerStack = displayInfo.getLayerStack(); SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index c27322ef..8e5b401f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -80,15 +80,12 @@ public final class ScreenInfo { return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation); } - public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) { - int rotation = displayInfo.getRotation(); - + public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) { if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) { // The user requested to lock the video orientation to the current orientation lockedVideoOrientation = rotation; } - Size deviceSize = displayInfo.getSize(); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); if (crop != null) { if (rotation % 2 != 0) { // 180s preserve dimensions From 723faa5dee2915086c3b07fd1bfb6d541680b042 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 22:17:38 +0100 Subject: [PATCH 0295/1133] Remember Device parameters This will allow to reuse them to recreate a ScreenInfo instance in order to change the maxSize value on MediaCodec error. PR #2947 --- .../src/main/java/com/genymobile/scrcpy/Device.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 419e996e..591460b6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -42,6 +42,11 @@ public final class Device { void onClipboardTextChanged(String text); } + private final Size deviceSize; + private final Rect crop; + private final int maxSize; + private final int lockVideoOrientation; + private ScreenInfo screenInfo; private RotationListener rotationListener; private ClipboardListener clipboardListener; @@ -69,8 +74,12 @@ public final class Device { int displayInfoFlags = displayInfo.getFlags(); - screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), options.getMaxSize(), - options.getLockVideoOrientation()); + deviceSize = displayInfo.getSize(); + crop = options.getCrop(); + maxSize = options.getMaxSize(); + lockVideoOrientation = options.getLockVideoOrientation(); + + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); layerStack = displayInfo.getLayerStack(); SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { From 26b4104844fb9516be13ff1f2be34e2945090e79 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 22:47:27 +0100 Subject: [PATCH 0296/1133] Downsize on error Some devices are not able to encode at the device screen definition. Instead of just failing, try with a lower definition on any MediaCodec error. PR #2947 --- .../java/com/genymobile/scrcpy/Device.java | 7 ++++- .../com/genymobile/scrcpy/ScreenEncoder.java | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 591460b6..763a7fad 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -44,7 +44,7 @@ public final class Device { private final Size deviceSize; private final Rect crop; - private final int maxSize; + private int maxSize; private final int lockVideoOrientation; private ScreenInfo screenInfo; @@ -133,6 +133,11 @@ public final class Device { } } + public synchronized void setMaxSize(int newMaxSize) { + maxSize = newMaxSize; + screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); + } + public synchronized ScreenInfo getScreenInfo() { return screenInfo; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index ce6b556a..ba601a22 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -25,6 +25,9 @@ public class ScreenEncoder implements Device.RotationListener { private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; + // Keep the values in descending order + private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; + private static final int NO_PTS = -1; private final AtomicBoolean rotationChanged = new AtomicBoolean(); @@ -91,6 +94,18 @@ public class ScreenEncoder implements Device.RotationListener { alive = encode(codec, fd); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); + } catch (Exception e) { + Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); + int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); + if (newMaxSize == 0) { + // Definitively fail + throw e; + } + + // Retry with a smaller device size + Ln.i("Retrying with -m" + newMaxSize + "..."); + device.setMaxSize(newMaxSize); + alive = true; } finally { destroyDisplay(display); codec.release(); @@ -102,6 +117,18 @@ public class ScreenEncoder implements Device.RotationListener { } } + private static int chooseMaxSizeFallback(Size failedSize) { + int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight()); + for (int value : MAX_SIZE_FALLBACK) { + if (value < currentMaxSize) { + // We found a smaller value to reduce the video size + return value; + } + } + // No fallback, fail definitively + return 0; + } + private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); From 15bf27afdddc64c2887085e075398f71d2748d06 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 23:01:14 +0100 Subject: [PATCH 0297/1133] Make auto-downsize on error optional Add --no-downsize-on-error option to disable attempts to use a lower definition on MediaCodec error. PR #2947 --- app/scrcpy.1 | 6 ++++++ app/src/cli.c | 11 +++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 4 ++++ app/src/server.h | 1 + .../src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../java/com/genymobile/scrcpy/ScreenEncoder.java | 10 +++++++++- .../src/main/java/com/genymobile/scrcpy/Server.java | 6 +++++- 10 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 4d02982c..37431a91 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -140,6 +140,12 @@ By default, scrcpy automatically synchronizes the computer clipboard to the devi This option disables this automatic synchronization. +.TP +.B \-\-no\-downsize\-on\-error +By default, on MediaCodec error, scrcpy automatically tries again with a lower definition. + +This option disables this behavior. + .TP .B \-n, \-\-no\-control Disable device control (mirror the device in read\-only). diff --git a/app/src/cli.c b/app/src/cli.c index 3fcf94eb..76feb130 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -52,6 +52,7 @@ #define OPT_NO_CLIPBOARD_AUTOSYNC 1032 #define OPT_TCPIP 1033 #define OPT_RAW_KEY_EVENTS 1034 +#define OPT_NO_DOWNSIZE_ON_ERROR 1035 struct sc_option { char shortopt; @@ -236,6 +237,13 @@ static const struct sc_option options[] = { "is preserved.\n" "Default is 0 (unlimited).", }, + { + .longopt_id = OPT_NO_DOWNSIZE_ON_ERROR, + .longopt = "no-downsize-on-error", + .text = "By default, on MediaCodec error, scrcpy automatically tries " + "again with a lower definition.\n" + "This option disables this behavior.", + }, { .longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC, .longopt = "no-clipboard-autosync", @@ -1489,6 +1497,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->tcpip = true; opts->tcpip_dst = optarg; break; + case OPT_NO_DOWNSIZE_ON_ERROR: + opts->downsize_on_error = false; + break; case OPT_V4L2_SINK: #ifdef HAVE_V4L2 opts->v4l2_device = optarg; diff --git a/app/src/options.c b/app/src/options.c index 7a5b71af..b8560406 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -54,6 +54,7 @@ const struct scrcpy_options scrcpy_options_default = { .legacy_paste = false, .power_off_on_close = false, .clipboard_autosync = true, + .downsize_on_error = true, .tcpip = false, .tcpip_dst = NULL, }; diff --git a/app/src/options.h b/app/src/options.h index b2c69664..99f03d40 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -129,6 +129,7 @@ struct scrcpy_options { bool legacy_paste; bool power_off_on_close; bool clipboard_autosync; + bool downsize_on_error; bool tcpip; const char *tcpip_dst; }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 86fdf984..e55fef80 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -364,6 +364,7 @@ scrcpy(struct scrcpy_options *options) { .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, + .downsize_on_error = options->downsize_on_error, .tcpip = options->tcpip, .tcpip_dst = options->tcpip_dst, }; diff --git a/app/src/server.c b/app/src/server.c index ea7e5377..b62a74f1 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,6 +234,10 @@ execute_server(struct sc_server *server, // By default, clipboard_autosync is true ADD_PARAM("clipboard_autosync=false"); } + if (!params->downsize_on_error) { + // By default, downsize_on_error is true + ADD_PARAM("downsize_on_error=false"); + } #undef ADD_PARAM #undef STRBOOL diff --git a/app/src/server.h b/app/src/server.h index 8ea20dc7..89cdc2f4 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -42,6 +42,7 @@ struct sc_server_params { bool force_adb_forward; bool power_off_on_close; bool clipboard_autosync; + bool downsize_on_error; bool tcpip; const char *tcpip_dst; }; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 1ac17176..ef7d8572 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -21,6 +21,7 @@ public class Options { private String encoderName; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; + private boolean downsizeOnError = true; public Ln.Level getLogLevel() { return logLevel; @@ -149,4 +150,12 @@ public class Options { public void setClipboardAutosync(boolean clipboardAutosync) { this.clipboardAutosync = clipboardAutosync; } + + public boolean getDownsizeOnError() { + return downsizeOnError; + } + + public void setDownsizeOnError(boolean downsizeOnError) { + this.downsizeOnError = downsizeOnError; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index ba601a22..10ba9fac 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -38,14 +38,17 @@ public class ScreenEncoder implements Device.RotationListener { private final int bitRate; private final int maxFps; private final boolean sendFrameMeta; + private final boolean downsizeOnError; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName, + boolean downsizeOnError) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; this.encoderName = encoderName; + this.downsizeOnError = downsizeOnError; } @Override @@ -96,6 +99,11 @@ public class ScreenEncoder implements Device.RotationListener { codec.stop(); } catch (Exception e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); + if (!downsizeOnError) { + // Fail immediately + throw e; + } + int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); if (newMaxSize == 0) { // Definitively fail diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4f9575ae..21e3fd92 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -70,7 +70,7 @@ public final class Server { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName()); + options.getEncoderName(), options.getDownsizeOnError()); Thread controllerThread = null; Thread deviceMessageSenderThread = null; @@ -237,6 +237,10 @@ public final class Server { boolean clipboardAutosync = Boolean.parseBoolean(value); options.setClipboardAutosync(clipboardAutosync); break; + case "downsize_on_error": + boolean downsizeOnError = Boolean.parseBoolean(value); + options.setDownsizeOnError(downsizeOnError); + break; default: Ln.w("Unknown server option: " + key); break; From 0ec64baad437cc11d7fe7e046adb74eb2cdd9eb7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 22:47:31 +0100 Subject: [PATCH 0298/1133] Remove MediaCodec error suggestion fix Now that scrcpy attempts with a lower definition on any MediaCodec error (or the user explicitly requests to disable auto-downsizing), the suggestion is unnecessary. PR #2947 --- .../src/main/java/com/genymobile/scrcpy/Server.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 21e3fd92..9d7a62e3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,7 +1,6 @@ package com.genymobile.scrcpy; import android.graphics.Rect; -import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.os.BatteryManager; import android.os.Build; @@ -267,16 +266,6 @@ public final class Server { } private static void suggestFix(Throwable e) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (e instanceof MediaCodec.CodecException) { - MediaCodec.CodecException mce = (MediaCodec.CodecException) e; - if (mce.getErrorCode() == 0xfffffc0e) { - Ln.e("The hardware encoder is not able to encode at the given definition."); - Ln.e("Try with a lower definition:"); - Ln.e(" scrcpy -m 1024"); - } - } - } if (e instanceof InvalidDisplayIdException) { InvalidDisplayIdException idie = (InvalidDisplayIdException) e; int[] displayIds = idie.getAvailableDisplayIds(); From 8fa9e6b01a4b5ee715cc6ba51d95ce8522ccc99c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 15 Jan 2022 23:17:52 +0100 Subject: [PATCH 0299/1133] Mention auto-downsize feature in FAQ PR #2947 --- FAQ.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FAQ.md b/FAQ.md index d5f0e3ee..43ba39af 100644 --- a/FAQ.md +++ b/FAQ.md @@ -219,6 +219,9 @@ scrcpy -m 1024 scrcpy -m 800 ``` +Since scrcpy v1.22, scrcpy automatically tries again with a lower definition +before failing. This behavior can be disabled with `--no-downsize-on-error`. + You could also try another [encoder](README.md#encoder). From 4fb61ac83de7c99edcd1febd9635d822b3d0a075 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 Jan 2022 15:27:23 +0100 Subject: [PATCH 0300/1133] Fix screen comments The position fields accept SC_WINDOW_POSITION_UNDEFINED, not the size fields. PR #2947 --- app/src/screen.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/screen.h b/app/src/screen.h index 4810de31..9f65a5f6 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -74,10 +74,10 @@ struct sc_screen_params { struct sc_size frame_size; bool always_on_top; - int16_t window_x; - int16_t window_y; - uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED - uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED + int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED + int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED + uint16_t window_width; + uint16_t window_height; bool window_borderless; From fa30f9806aa928da5010c7110e6f0fafb5220eaf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 Jan 2022 15:47:11 +0100 Subject: [PATCH 0301/1133] Move "show window" call on first frame Show the window only after the actual frame size is known (and if no error has occurred). This will allow to properly position and size the window when the size of the first frame is different from the size initially announced by the server. PR #2947 --- app/src/screen.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index a2796278..78ab91d9 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -693,6 +693,12 @@ sc_screen_update_frame(struct sc_screen *screen) { } update_texture(screen, frame); + if (!screen->has_frame) { + screen->has_frame = true; + // this is the very first frame, show the window + sc_screen_show_window(screen); + } + sc_screen_render(screen, false); return true; } @@ -763,17 +769,13 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { bool sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { switch (event->type) { - case EVENT_NEW_FRAME: - if (!screen->has_frame) { - screen->has_frame = true; - // this is the very first frame, show the window - sc_screen_show_window(screen); - } + case EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); if (!ok) { LOGW("Frame update failed\n"); } return true; + } case SDL_WINDOWEVENT: if (!screen->has_frame) { // Do nothing From 75c5dc6859d29e2fb4d273e6c261288b802e16c0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 16 Jan 2022 15:46:37 +0100 Subject: [PATCH 0302/1133] Position and size the window on first frame The optimal initial size was computed from the expected dimensions, sent immediately by the server before encoding any video frame. However, the actual frame size may be different, for example when the device encoder does not support the requested size. To always handle this case properly, position and size the window only once the first frame size is known. PR #2947 --- app/src/screen.c | 50 ++++++++++++++++++++++++++---------------------- app/src/screen.h | 9 +++++++++ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 78ab91d9..a357eecf 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -369,6 +369,12 @@ sc_screen_init(struct sc_screen *screen, screen->mouse_captured = false; screen->mouse_capture_key_pressed = 0; + screen->req.x = params->window_x; + screen->req.y = params->window_y; + screen->req.width = params->window_width; + screen->req.height = params->window_height; + screen->req.fullscreen = params->fullscreen; + static const struct sc_video_buffer_callbacks cbs = { .on_new_frame = sc_video_buffer_on_new_frame, }; @@ -397,9 +403,6 @@ sc_screen_init(struct sc_screen *screen, 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, - params->window_height); uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; @@ -410,13 +413,9 @@ sc_screen_init(struct sc_screen *screen, window_flags |= SDL_WINDOW_BORDERLESS; } - int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED - ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; - int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED - ? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; - screen->window = SDL_CreateWindow(params->window_title, x, y, - window_size.width, window_size.height, - window_flags); + // The window will be positioned and sized on first video frame + screen->window = + SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); if (!screen->window) { LOGC("Could not create window: %s", SDL_GetError()); goto error_destroy_fps_counter; @@ -498,17 +497,6 @@ sc_screen_init(struct sc_screen *screen, sc_input_manager_init(&screen->im, &im_params); - // Reset the window size to trigger a SIZE_CHANGED event, to workaround - // HiDPI issues with some SDL renderers when several displays having - // different HiDPI scaling are connected - SDL_SetWindowSize(screen->window, window_size.width, window_size.height); - - sc_screen_update_content_rect(screen); - - if (params->fullscreen) { - sc_screen_switch_fullscreen(screen); - } - #ifdef CONTINUOUS_RESIZING_WORKAROUND SDL_AddEventWatch(event_watcher, screen); #endif @@ -545,7 +533,23 @@ error_destroy_video_buffer: } static void -sc_screen_show_window(struct sc_screen *screen) { +sc_screen_show_initial_window(struct sc_screen *screen) { + int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED + ? screen->req.x : (int) SDL_WINDOWPOS_CENTERED; + int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED + ? screen->req.y : (int) SDL_WINDOWPOS_CENTERED; + + struct sc_size window_size = + get_initial_optimal_size(screen->content_size, screen->req.width, + screen->req.height); + + set_window_size(screen, window_size); + SDL_SetWindowPosition(screen->window, x, y); + + if (screen->req.fullscreen) { + sc_screen_switch_fullscreen(screen); + } + SDL_ShowWindow(screen->window); } @@ -696,7 +700,7 @@ sc_screen_update_frame(struct sc_screen *screen) { if (!screen->has_frame) { screen->has_frame = true; // this is the very first frame, show the window - sc_screen_show_window(screen); + sc_screen_show_initial_window(screen); } sc_screen_render(screen, false); diff --git a/app/src/screen.h b/app/src/screen.h index 9f65a5f6..3e6c1031 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -28,6 +28,15 @@ struct sc_screen { struct sc_video_buffer vb; struct fps_counter fps_counter; + // The initial requested window properties + struct { + int16_t x; + int16_t y; + uint16_t width; + uint16_t height; + bool fullscreen; + } req; + SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; From 3a0ba7d0a439c0933f74e2433122c15cb7ee7318 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 Jan 2022 20:16:03 +0100 Subject: [PATCH 0303/1133] Disable downsizing on error if V4L2 is enabled V4L2 device is created with the initial device size, it does not support resizing. PR #2947 --- app/src/cli.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 76feb130..69177aef 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1545,11 +1545,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } - if (opts->v4l2_device && opts->lock_video_orientation - == SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { - LOGI("Video orientation is locked for v4l2 sink. " - "See --lock-video-orientation."); - opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; + if (opts->v4l2_device) { + if (opts->lock_video_orientation == + SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { + LOGI("Video orientation is locked for v4l2 sink. " + "See --lock-video-orientation."); + opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; + } + + // V4L2 could not handle size change. + // Do not log because downsizing on error is the default behavior, + // not an explicit request from the user. + opts->downsize_on_error = false; } if (opts->v4l2_buffer && !opts->v4l2_device) { From 2eb6fe7d81c15912a2252f229d317ae03f618a33 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 17 Jan 2022 20:22:33 +0100 Subject: [PATCH 0304/1133] Downsize on error only before the first frame The purpose of automatic downscaling on error is to make mirroring work by just starting scrcpy without an explicit -m value, even if the encoder could not encode at the screen definition. It is only useful when we detect an encoding failure before the first frame. Downsizing later could be surprising, so disable it. PR #2947 --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 10ba9fac..06f06a9d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -41,6 +41,8 @@ public class ScreenEncoder implements Device.RotationListener { private final boolean downsizeOnError; private long ptsOrigin; + private boolean firstFrameSent; + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { this.sendFrameMeta = sendFrameMeta; @@ -99,7 +101,7 @@ public class ScreenEncoder implements Device.RotationListener { codec.stop(); } catch (Exception e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); - if (!downsizeOnError) { + if (!downsizeOnError || firstFrameSent) { // Fail immediately throw e; } @@ -157,6 +159,7 @@ public class ScreenEncoder implements Device.RotationListener { } IO.writeFully(fd, codecBuffer); + firstFrameSent = true; } } finally { if (outputBufferId >= 0) { From 262506c733ba5fb63ea08ef1a7cb7a667a741ccf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 21:50:31 +0100 Subject: [PATCH 0305/1133] Limit retry-on-error to IllegalStateException MediaCodec errors always trigger IllegalStateException or a subtype (like MediaCodec.CodecException). In practice, this avoids to retry if the error is caused by an IOException when writing the video packet to the socket. --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 06f06a9d..e4e87c72 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -99,7 +99,7 @@ public class ScreenEncoder implements Device.RotationListener { alive = encode(codec, fd); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); - } catch (Exception e) { + } catch (IllegalStateException e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); if (!downsizeOnError || firstFrameSent) { // Fail immediately From b066dc0bbfc608d52706dc59e826c75d7ad46fef Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 19:10:27 +0100 Subject: [PATCH 0306/1133] Rename file_handler to sc_file_pusher Rename handler to pusher ("handler" is too generic), and add sc_ prefix. --- app/meson.build | 2 +- app/src/file_handler.c | 178 ----------------------------------------- app/src/file_handler.h | 60 -------------- app/src/file_pusher.c | 178 +++++++++++++++++++++++++++++++++++++++++ app/src/file_pusher.h | 59 ++++++++++++++ app/src/scrcpy.c | 30 +++---- 6 files changed, 253 insertions(+), 254 deletions(-) delete mode 100644 app/src/file_handler.c delete mode 100644 app/src/file_handler.h create mode 100644 app/src/file_pusher.c create mode 100644 app/src/file_pusher.h diff --git a/app/meson.build b/app/meson.build index 319dd0b6..88b8ef8c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -11,7 +11,7 @@ src = [ 'src/decoder.c', 'src/device_msg.c', 'src/icon.c', - 'src/file_handler.c', + 'src/file_pusher.c', 'src/fps_counter.c', 'src/frame_buffer.c', 'src/input_manager.c', diff --git a/app/src/file_handler.c b/app/src/file_handler.c deleted file mode 100644 index 95d230ae..00000000 --- a/app/src/file_handler.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "file_handler.h" - -#include -#include - -#include "adb.h" -#include "util/log.h" -#include "util/process_intr.h" - -#define DEFAULT_PUSH_TARGET "/sdcard/Download/" - -static void -file_handler_request_destroy(struct file_handler_request *req) { - free(req->file); -} - -bool -file_handler_init(struct file_handler *file_handler, const char *serial, - const char *push_target) { - assert(serial); - - cbuf_init(&file_handler->queue); - - bool ok = sc_mutex_init(&file_handler->mutex); - if (!ok) { - return false; - } - - ok = sc_cond_init(&file_handler->event_cond); - if (!ok) { - sc_mutex_destroy(&file_handler->mutex); - return false; - } - - ok = sc_intr_init(&file_handler->intr); - if (!ok) { - sc_cond_destroy(&file_handler->event_cond); - sc_mutex_destroy(&file_handler->mutex); - return false; - } - - file_handler->serial = strdup(serial); - if (!file_handler->serial) { - LOG_OOM(); - sc_intr_destroy(&file_handler->intr); - sc_cond_destroy(&file_handler->event_cond); - sc_mutex_destroy(&file_handler->mutex); - return false; - } - - // lazy initialization - file_handler->initialized = false; - - file_handler->stopped = false; - - file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; - - return true; -} - -void -file_handler_destroy(struct file_handler *file_handler) { - sc_cond_destroy(&file_handler->event_cond); - sc_mutex_destroy(&file_handler->mutex); - sc_intr_destroy(&file_handler->intr); - free(file_handler->serial); - - struct file_handler_request req; - while (cbuf_take(&file_handler->queue, &req)) { - file_handler_request_destroy(&req); - } -} - -bool -file_handler_request(struct file_handler *file_handler, - file_handler_action_t action, char *file) { - // start file_handler if it's used for the first time - if (!file_handler->initialized) { - if (!file_handler_start(file_handler)) { - return false; - } - file_handler->initialized = true; - } - - LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", - file); - struct file_handler_request req = { - .action = action, - .file = file, - }; - - sc_mutex_lock(&file_handler->mutex); - bool was_empty = cbuf_is_empty(&file_handler->queue); - bool res = cbuf_push(&file_handler->queue, req); - if (was_empty) { - sc_cond_signal(&file_handler->event_cond); - } - sc_mutex_unlock(&file_handler->mutex); - return res; -} - -static int -run_file_handler(void *data) { - struct file_handler *file_handler = data; - struct sc_intr *intr = &file_handler->intr; - - const char *serial = file_handler->serial; - assert(serial); - - const char *push_target = file_handler->push_target; - assert(push_target); - - for (;;) { - sc_mutex_lock(&file_handler->mutex); - while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { - sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); - } - if (file_handler->stopped) { - // stop immediately, do not process further events - sc_mutex_unlock(&file_handler->mutex); - break; - } - struct file_handler_request req; - bool non_empty = cbuf_take(&file_handler->queue, &req); - assert(non_empty); - (void) non_empty; - sc_mutex_unlock(&file_handler->mutex); - - if (req.action == ACTION_INSTALL_APK) { - LOGI("Installing %s...", req.file); - bool ok = adb_install(intr, serial, req.file, 0); - if (ok) { - LOGI("%s successfully installed", req.file); - } else { - LOGE("Failed to install %s", req.file); - } - } else { - LOGI("Pushing %s...", req.file); - bool ok = adb_push(intr, serial, req.file, push_target, 0); - if (ok) { - LOGI("%s successfully pushed to %s", req.file, push_target); - } else { - LOGE("Failed to push %s to %s", req.file, push_target); - } - } - - file_handler_request_destroy(&req); - } - return 0; -} - -bool -file_handler_start(struct file_handler *file_handler) { - LOGD("Starting file_handler thread"); - - bool ok = sc_thread_create(&file_handler->thread, run_file_handler, - "scrcpy-file", file_handler); - if (!ok) { - LOGC("Could not start file_handler thread"); - return false; - } - - return true; -} - -void -file_handler_stop(struct file_handler *file_handler) { - sc_mutex_lock(&file_handler->mutex); - file_handler->stopped = true; - sc_cond_signal(&file_handler->event_cond); - sc_intr_interrupt(&file_handler->intr); - sc_mutex_unlock(&file_handler->mutex); -} - -void -file_handler_join(struct file_handler *file_handler) { - sc_thread_join(&file_handler->thread, NULL); -} diff --git a/app/src/file_handler.h b/app/src/file_handler.h deleted file mode 100644 index 4c0313cc..00000000 --- a/app/src/file_handler.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef FILE_HANDLER_H -#define FILE_HANDLER_H - -#include "common.h" - -#include - -#include "adb.h" -#include "util/cbuf.h" -#include "util/thread.h" -#include "util/intr.h" - -typedef enum { - ACTION_INSTALL_APK, - ACTION_PUSH_FILE, -} file_handler_action_t; - -struct file_handler_request { - file_handler_action_t action; - char *file; -}; - -struct file_handler_request_queue CBUF(struct file_handler_request, 16); - -struct file_handler { - char *serial; - const char *push_target; - sc_thread thread; - sc_mutex mutex; - sc_cond event_cond; - bool stopped; - bool initialized; - struct file_handler_request_queue queue; - - struct sc_intr intr; -}; - -bool -file_handler_init(struct file_handler *file_handler, const char *serial, - const char *push_target); - -void -file_handler_destroy(struct file_handler *file_handler); - -bool -file_handler_start(struct file_handler *file_handler); - -void -file_handler_stop(struct file_handler *file_handler); - -void -file_handler_join(struct file_handler *file_handler); - -// take ownership of file, and will free() it -bool -file_handler_request(struct file_handler *file_handler, - file_handler_action_t action, - char *file); - -#endif diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c new file mode 100644 index 00000000..738e3616 --- /dev/null +++ b/app/src/file_pusher.c @@ -0,0 +1,178 @@ +#include "file_pusher.h" + +#include +#include + +#include "adb.h" +#include "util/log.h" +#include "util/process_intr.h" + +#define DEFAULT_PUSH_TARGET "/sdcard/Download/" + +static void +sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) { + free(req->file); +} + +bool +sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial, + const char *push_target) { + assert(serial); + + cbuf_init(&fp->queue); + + bool ok = sc_mutex_init(&fp->mutex); + if (!ok) { + return false; + } + + ok = sc_cond_init(&fp->event_cond); + if (!ok) { + sc_mutex_destroy(&fp->mutex); + return false; + } + + ok = sc_intr_init(&fp->intr); + if (!ok) { + sc_cond_destroy(&fp->event_cond); + sc_mutex_destroy(&fp->mutex); + return false; + } + + fp->serial = strdup(serial); + if (!fp->serial) { + LOG_OOM(); + sc_intr_destroy(&fp->intr); + sc_cond_destroy(&fp->event_cond); + sc_mutex_destroy(&fp->mutex); + return false; + } + + // lazy initialization + fp->initialized = false; + + fp->stopped = false; + + fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; + + return true; +} + +void +sc_file_pusher_destroy(struct sc_file_pusher *fp) { + sc_cond_destroy(&fp->event_cond); + sc_mutex_destroy(&fp->mutex); + sc_intr_destroy(&fp->intr); + free(fp->serial); + + struct sc_file_pusher_request req; + while (cbuf_take(&fp->queue, &req)) { + sc_file_pusher_request_destroy(&req); + } +} + +bool +sc_file_pusher_request(struct sc_file_pusher *fp, + enum sc_file_pusher_action action, char *file) { + // start file_pusher if it's used for the first time + if (!fp->initialized) { + if (!sc_file_pusher_start(fp)) { + return false; + } + fp->initialized = true; + } + + LOGI("Request to %s %s", action == SC_FILE_PUSHER_ACTION_INSTALL_APK + ? "install" : "push", + file); + struct sc_file_pusher_request req = { + .action = action, + .file = file, + }; + + sc_mutex_lock(&fp->mutex); + bool was_empty = cbuf_is_empty(&fp->queue); + bool res = cbuf_push(&fp->queue, req); + if (was_empty) { + sc_cond_signal(&fp->event_cond); + } + sc_mutex_unlock(&fp->mutex); + return res; +} + +static int +run_file_pusher(void *data) { + struct sc_file_pusher *fp = data; + struct sc_intr *intr = &fp->intr; + + const char *serial = fp->serial; + assert(serial); + + const char *push_target = fp->push_target; + assert(push_target); + + for (;;) { + sc_mutex_lock(&fp->mutex); + while (!fp->stopped && cbuf_is_empty(&fp->queue)) { + sc_cond_wait(&fp->event_cond, &fp->mutex); + } + if (fp->stopped) { + // stop immediately, do not process further events + sc_mutex_unlock(&fp->mutex); + break; + } + struct sc_file_pusher_request req; + bool non_empty = cbuf_take(&fp->queue, &req); + assert(non_empty); + (void) non_empty; + sc_mutex_unlock(&fp->mutex); + + if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) { + LOGI("Installing %s...", req.file); + bool ok = adb_install(intr, serial, req.file, 0); + if (ok) { + LOGI("%s successfully installed", req.file); + } else { + LOGE("Failed to install %s", req.file); + } + } else { + LOGI("Pushing %s...", req.file); + bool ok = adb_push(intr, serial, req.file, push_target, 0); + if (ok) { + LOGI("%s successfully pushed to %s", req.file, push_target); + } else { + LOGE("Failed to push %s to %s", req.file, push_target); + } + } + + sc_file_pusher_request_destroy(&req); + } + return 0; +} + +bool +sc_file_pusher_start(struct sc_file_pusher *fp) { + LOGD("Starting file_pusher thread"); + + bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp); + if (!ok) { + LOGC("Could not start file_pusher thread"); + return false; + } + + return true; +} + +void +sc_file_pusher_stop(struct sc_file_pusher *fp) { + sc_mutex_lock(&fp->mutex); + fp->stopped = true; + sc_cond_signal(&fp->event_cond); + sc_intr_interrupt(&fp->intr); + sc_mutex_unlock(&fp->mutex); +} + +void +sc_file_pusher_join(struct sc_file_pusher *fp) { + sc_thread_join(&fp->thread, NULL); +} diff --git a/app/src/file_pusher.h b/app/src/file_pusher.h new file mode 100644 index 00000000..56b107a0 --- /dev/null +++ b/app/src/file_pusher.h @@ -0,0 +1,59 @@ +#ifndef SC_FILE_PUSHER_H +#define SC_FILE_PUSHER_H + +#include "common.h" + +#include + +#include "adb.h" +#include "util/cbuf.h" +#include "util/thread.h" +#include "util/intr.h" + +enum sc_file_pusher_action { + SC_FILE_PUSHER_ACTION_INSTALL_APK, + SC_FILE_PUSHER_ACTION_PUSH_FILE, +}; + +struct sc_file_pusher_request { + enum sc_file_pusher_action action; + char *file; +}; + +struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16); + +struct sc_file_pusher { + char *serial; + const char *push_target; + sc_thread thread; + sc_mutex mutex; + sc_cond event_cond; + bool stopped; + bool initialized; + struct sc_file_pusher_request_queue queue; + + struct sc_intr intr; +}; + +bool +sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial, + const char *push_target); + +void +sc_file_pusher_destroy(struct sc_file_pusher *fp); + +bool +sc_file_pusher_start(struct sc_file_pusher *fp); + +void +sc_file_pusher_stop(struct sc_file_pusher *fp); + +void +sc_file_pusher_join(struct sc_file_pusher *fp); + +// take ownership of file, and will free() it +bool +sc_file_pusher_request(struct sc_file_pusher *fp, + enum sc_file_pusher_action action, char *file); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e55fef80..4e7ccd6f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -16,7 +16,7 @@ #include "controller.h" #include "decoder.h" #include "events.h" -#include "file_handler.h" +#include "file_pusher.h" #ifdef HAVE_AOA_HID # include "hid_keyboard.h" # include "hid_mouse.h" @@ -44,7 +44,7 @@ struct scrcpy { struct sc_v4l2_sink v4l2_sink; #endif struct sc_controller controller; - struct file_handler file_handler; + struct sc_file_pusher file_pusher; #ifdef HAVE_AOA_HID struct sc_aoa aoa; // sequence/ack helper to synchronize clipboard and Ctrl+v via HID @@ -181,13 +181,13 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, break; } - file_handler_action_t action; + enum sc_file_pusher_action action; if (is_apk(file)) { - action = ACTION_INSTALL_APK; + action = SC_FILE_PUSHER_ACTION_INSTALL_APK; } else { - action = ACTION_PUSH_FILE; + action = SC_FILE_PUSHER_ACTION_PUSH_FILE; } - file_handler_request(&s->file_handler, action, file); + sc_file_pusher_request(&s->file_pusher, action, file); goto end; } } @@ -327,7 +327,7 @@ scrcpy(struct scrcpy_options *options) { bool ret = false; bool server_started = false; - bool file_handler_initialized = false; + bool file_pusher_initialized = false; bool recorder_initialized = false; #ifdef HAVE_V4L2 bool v4l2_sink_initialized = false; @@ -408,11 +408,11 @@ scrcpy(struct scrcpy_options *options) { assert(serial); if (options->display && options->control) { - if (!file_handler_init(&s->file_handler, serial, - options->push_target)) { + if (!sc_file_pusher_init(&s->file_pusher, serial, + options->push_target)) { goto end; } - file_handler_initialized = true; + file_pusher_initialized = true; } struct decoder *dec = NULL; @@ -649,8 +649,8 @@ end: if (controller_started) { sc_controller_stop(&s->controller); } - if (file_handler_initialized) { - file_handler_stop(&s->file_handler); + if (file_pusher_initialized) { + sc_file_pusher_stop(&s->file_pusher); } if (screen_initialized) { sc_screen_interrupt(&s->screen); @@ -698,9 +698,9 @@ end: recorder_destroy(&s->recorder); } - if (file_handler_initialized) { - file_handler_join(&s->file_handler); - file_handler_destroy(&s->file_handler); + if (file_pusher_initialized) { + sc_file_pusher_join(&s->file_pusher); + sc_file_pusher_destroy(&s->file_pusher); } sc_server_destroy(&s->server); From 8e4e7d42f1d776dc8a6e14322fdda494c973135e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 19:15:47 +0100 Subject: [PATCH 0307/1133] Fix leak on file pusher error If a file_push request fails, the allocated filename must be freed. --- app/src/scrcpy.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4e7ccd6f..d628df51 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -187,7 +187,10 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, } else { action = SC_FILE_PUSHER_ACTION_PUSH_FILE; } - sc_file_pusher_request(&s->file_pusher, action, file); + bool ok = sc_file_pusher_request(&s->file_pusher, action, file); + if (!ok) { + free(file); + } goto end; } } From ebef027c4f13e7cd30743525e845ad06b3660679 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 19:16:32 +0100 Subject: [PATCH 0308/1133] Do not return status for event handling It is never read. Simplify. --- app/src/input_manager.c | 18 ++++++++---------- app/src/input_manager.h | 2 +- app/src/scrcpy.c | 3 +-- app/src/screen.c | 20 ++++++++++---------- app/src/screen.h | 2 +- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 9eee6e6c..e1e8738c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -834,45 +834,43 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, im->mp->ops->process_mouse_scroll(im->mp, &evt); } -bool +void sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { switch (event->type) { case SDL_TEXTINPUT: if (!im->control) { - return true; + break; } sc_input_manager_process_text_input(im, &event->text); - return true; + break; case SDL_KEYDOWN: case SDL_KEYUP: // some key events do not interact with the device, so process the // event even if control is disabled sc_input_manager_process_key(im, &event->key); - return true; + break; case SDL_MOUSEMOTION: if (!im->control) { break; } sc_input_manager_process_mouse_motion(im, &event->motion); - return true; + break; case SDL_MOUSEWHEEL: if (!im->control) { break; } sc_input_manager_process_mouse_wheel(im, &event->wheel); - return true; + break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: // some mouse events do not interact with the device, so process // the event even if control is disabled sc_input_manager_process_mouse_button(im, &event->button); - return true; + break; case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: sc_input_manager_process_touch(im, &event->tfinger); - return true; + break; } - - return false; } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 5d552ee2..b28b110f 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -59,7 +59,7 @@ void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params); -bool +void sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d628df51..1c03b8c1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -195,8 +195,7 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, } } - bool consumed = sc_screen_handle_event(&s->screen, event); - (void) consumed; + sc_screen_handle_event(&s->screen, event); end: return EVENT_RESULT_CONTINUE; diff --git a/app/src/screen.c b/app/src/screen.c index a357eecf..b1abe985 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -770,7 +770,7 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI; } -bool +void sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { switch (event->type) { case EVENT_NEW_FRAME: { @@ -778,12 +778,12 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (!ok) { LOGW("Frame update failed\n"); } - return true; + return; } case SDL_WINDOWEVENT: if (!screen->has_frame) { // Do nothing - return true; + return; } switch (event->window.event) { case SDL_WINDOWEVENT_EXPOSED: @@ -814,14 +814,14 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } break; } - return true; + return; case SDL_KEYDOWN: if (screen->im.mp->relative_mode) { SDL_Keycode key = event->key.keysym.sym; if (sc_screen_is_mouse_capture_key(key)) { if (!screen->mouse_capture_key_pressed) { screen->mouse_capture_key_pressed = key; - return true; + return; } else { // Another mouse capture key has been pressed, cancel // mouse (un)capture @@ -841,7 +841,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode sc_screen_capture_mouse(screen, !screen->mouse_captured); - return true; + return; } // Do not return, the event must be forwarded to the input // manager @@ -853,7 +853,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (screen->im.mp->relative_mode && !screen->mouse_captured) { // Do not forward to input manager, the mouse will be captured // on SDL_MOUSEBUTTONUP - return true; + return; } break; case SDL_FINGERMOTION: @@ -862,17 +862,17 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (screen->im.mp->relative_mode) { // Touch events are not compatible with relative mode // (coordinates are not relative) - return true; + return; } break; case SDL_MOUSEBUTTONUP: if (screen->im.mp->relative_mode && !screen->mouse_captured) { sc_screen_capture_mouse(screen, true); - return true; + return; } } - return sc_input_manager_handle_event(&screen->im, event); + sc_input_manager_handle_event(&screen->im, event); } struct sc_point diff --git a/app/src/screen.h b/app/src/screen.h index 3e6c1031..8af53a5e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -139,7 +139,7 @@ void sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); // react to SDL events -bool +void sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event); // convert point from window coordinates to frame coordinates From 1ffe312369ee2b223c8a946e053ad70932bec173 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 19:26:36 +0100 Subject: [PATCH 0309/1133] Handle file drop from input_manager A file is pushed (or an apk is installed) to the device on file drop. This behavior is specific to the screen and its input_manager. --- app/src/input_manager.c | 35 ++++++++++++++++++++++++++++++++ app/src/input_manager.h | 3 +++ app/src/scrcpy.c | 44 ++++++++--------------------------------- app/src/screen.c | 1 + app/src/screen.h | 1 + 5 files changed, 48 insertions(+), 36 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e1e8738c..08531cac 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -128,6 +128,7 @@ sc_input_manager_init(struct sc_input_manager *im, assert(!params->control || (params->mp && params->mp->ops)); im->controller = params->controller; + im->fp = params->fp; im->screen = params->screen; im->kp = params->kp; im->mp = params->mp; @@ -834,6 +835,34 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, im->mp->ops->process_mouse_scroll(im->mp, &evt); } +static bool +is_apk(const char *file) { + const char *ext = strrchr(file, '.'); + return ext && !strcmp(ext, ".apk"); +} + +static void +sc_input_manager_process_file(struct sc_input_manager *im, + const SDL_DropEvent *event) { + char *file = strdup(event->file); + SDL_free(event->file); + if (!file) { + LOG_OOM(); + return; + } + + enum sc_file_pusher_action action; + if (is_apk(file)) { + action = SC_FILE_PUSHER_ACTION_INSTALL_APK; + } else { + action = SC_FILE_PUSHER_ACTION_PUSH_FILE; + } + bool ok = sc_file_pusher_request(im->fp, action, file); + if (!ok) { + free(file); + } +} + void sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { switch (event->type) { @@ -872,5 +901,11 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { case SDL_FINGERUP: sc_input_manager_process_touch(im, &event->tfinger); break; + case SDL_DROPFILE: { + if (!im->control) { + break; + } + sc_input_manager_process_file(im, &event->drop); + } } } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index b28b110f..377835bf 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -8,6 +8,7 @@ #include #include "controller.h" +#include "file_pusher.h" #include "fps_counter.h" #include "options.h" #include "trait/key_processor.h" @@ -15,6 +16,7 @@ struct sc_input_manager { struct sc_controller *controller; + struct sc_file_pusher *fp; struct sc_screen *screen; struct sc_key_processor *kp; @@ -44,6 +46,7 @@ struct sc_input_manager { struct sc_input_manager_params { struct sc_controller *controller; + struct sc_file_pusher *fp; struct sc_screen *screen; struct sc_key_processor *kp; struct sc_mouse_processor *mp; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1c03b8c1..0552a057 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -148,12 +148,6 @@ sdl_configure(bool display, bool disable_screensaver) { } } -static bool -is_apk(const char *file) { - const char *ext = strrchr(file, '.'); - return ext && !strcmp(ext, ".apk"); -} - enum event_result { EVENT_RESULT_CONTINUE, EVENT_RESULT_STOPPED_BY_USER, @@ -161,8 +155,7 @@ enum event_result { }; static enum event_result -handle_event(struct scrcpy *s, const struct scrcpy_options *options, - SDL_Event *event) { +handle_event(struct scrcpy *s, SDL_Event *event) { switch (event->type) { case EVENT_STREAM_STOPPED: LOGD("Video stream stopped"); @@ -170,42 +163,17 @@ handle_event(struct scrcpy *s, const struct scrcpy_options *options, case SDL_QUIT: LOGD("User requested to quit"); return EVENT_RESULT_STOPPED_BY_USER; - case SDL_DROPFILE: { - if (!options->control) { - break; - } - char *file = strdup(event->drop.file); - SDL_free(event->drop.file); - if (!file) { - LOGW("Could not strdup drop filename\n"); - break; - } - - enum sc_file_pusher_action action; - if (is_apk(file)) { - action = SC_FILE_PUSHER_ACTION_INSTALL_APK; - } else { - action = SC_FILE_PUSHER_ACTION_PUSH_FILE; - } - bool ok = sc_file_pusher_request(&s->file_pusher, action, file); - if (!ok) { - free(file); - } - goto end; - } } sc_screen_handle_event(&s->screen, event); - -end: return EVENT_RESULT_CONTINUE; } static bool -event_loop(struct scrcpy *s, const struct scrcpy_options *options) { +event_loop(struct scrcpy *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { - enum event_result result = handle_event(s, options, &event); + enum event_result result = handle_event(s, &event); switch (result) { case EVENT_RESULT_STOPPED_BY_USER: return true; @@ -409,11 +377,14 @@ scrcpy(struct scrcpy_options *options) { const char *serial = s->server.params.serial; assert(serial); + struct sc_file_pusher *fp = NULL; + if (options->display && options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { goto end; } + fp = &s->file_pusher; file_pusher_initialized = true; } @@ -578,6 +549,7 @@ aoa_hid_end: struct sc_screen_params screen_params = { .controller = &s->controller, + .fp = fp, .kp = kp, .mp = mp, .control = options->control, @@ -627,7 +599,7 @@ aoa_hid_end: } stream_started = true; - ret = event_loop(s, options); + ret = event_loop(s); LOGD("quit..."); // Close the window immediately on closing, because screen_destroy() may diff --git a/app/src/screen.c b/app/src/screen.c index b1abe985..92afafbb 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -485,6 +485,7 @@ sc_screen_init(struct sc_screen *screen, struct sc_input_manager_params im_params = { .controller = params->controller, + .fp = params->fp, .screen = screen, .kp = params->kp, .mp = params->mp, diff --git a/app/src/screen.h b/app/src/screen.h index 8af53a5e..677cf514 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -70,6 +70,7 @@ struct sc_screen { struct sc_screen_params { struct sc_controller *controller; + struct sc_file_pusher *fp; struct sc_key_processor *kp; struct sc_mouse_processor *mp; From 81ff7ebd065eaf6917905e64e4e7017e9f83fc3b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 21:43:49 +0100 Subject: [PATCH 0310/1133] Simplify event loop Merge single event handling with the event loop function. --- app/src/scrcpy.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0552a057..d7ba3670 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -148,39 +148,19 @@ sdl_configure(bool display, bool disable_screensaver) { } } -enum event_result { - EVENT_RESULT_CONTINUE, - EVENT_RESULT_STOPPED_BY_USER, - EVENT_RESULT_STOPPED_BY_EOS, -}; - -static enum event_result -handle_event(struct scrcpy *s, SDL_Event *event) { - switch (event->type) { - case EVENT_STREAM_STOPPED: - LOGD("Video stream stopped"); - return EVENT_RESULT_STOPPED_BY_EOS; - case SDL_QUIT: - LOGD("User requested to quit"); - return EVENT_RESULT_STOPPED_BY_USER; - } - - sc_screen_handle_event(&s->screen, event); - return EVENT_RESULT_CONTINUE; -} - static bool event_loop(struct scrcpy *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { - enum event_result result = handle_event(s, &event); - switch (result) { - case EVENT_RESULT_STOPPED_BY_USER: - return true; - case EVENT_RESULT_STOPPED_BY_EOS: + switch (event.type) { + case EVENT_STREAM_STOPPED: LOGW("Device disconnected"); return false; - case EVENT_RESULT_CONTINUE: + case SDL_QUIT: + LOGD("User requested to quit"); + return true; + default: + sc_screen_handle_event(&s->screen, &event); break; } } From 0ec3361bc9711332a30725b03b9592a4c68f0248 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 11:34:00 +0100 Subject: [PATCH 0311/1133] Fix crash on --no-control Relative mouse mode assumed that a mouse processor was always available, but this is not the case if --no-control is passed. --- app/src/screen.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 92afafbb..9393a92f 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -773,6 +773,9 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { void sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { + // screen->im.mp may be NULL if --no-control + bool relative_mode = screen->im.mp && screen->im.mp->relative_mode; + switch (event->type) { case EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); @@ -810,14 +813,14 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { sc_screen_render(screen, true); break; case SDL_WINDOWEVENT_FOCUS_LOST: - if (screen->im.mp->relative_mode) { + if (relative_mode) { sc_screen_capture_mouse(screen, false); } break; } return; case SDL_KEYDOWN: - if (screen->im.mp->relative_mode) { + if (relative_mode) { SDL_Keycode key = event->key.keysym.sym; if (sc_screen_is_mouse_capture_key(key)) { if (!screen->mouse_capture_key_pressed) { @@ -834,7 +837,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } break; case SDL_KEYUP: - if (screen->im.mp->relative_mode) { + if (relative_mode) { SDL_Keycode key = event->key.keysym.sym; SDL_Keycode cap = screen->mouse_capture_key_pressed; screen->mouse_capture_key_pressed = 0; @@ -851,7 +854,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { case SDL_MOUSEWHEEL: case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: - if (screen->im.mp->relative_mode && !screen->mouse_captured) { + if (relative_mode && !screen->mouse_captured) { // Do not forward to input manager, the mouse will be captured // on SDL_MOUSEBUTTONUP return; @@ -860,14 +863,14 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: - if (screen->im.mp->relative_mode) { + if (relative_mode) { // Touch events are not compatible with relative mode // (coordinates are not relative) return; } break; case SDL_MOUSEBUTTONUP: - if (screen->im.mp->relative_mode && !screen->mouse_captured) { + if (relative_mode && !screen->mouse_captured) { sc_screen_capture_mouse(screen, true); return; } From 0b8e9263305a388faabafac83565322e18da0537 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 12:02:35 +0100 Subject: [PATCH 0312/1133] Do not process finger events if no control If --no-control is passed, then im->mp is NULL, so processing touches would crash. --- app/src/input_manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 08531cac..c7290dd6 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -899,6 +899,9 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: + if (!im->control) { + break; + } sc_input_manager_process_touch(im, &event->tfinger); break; case SDL_DROPFILE: { From 557daf280e03932ab1289a140d7920f756b0ad1f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 11:43:09 +0100 Subject: [PATCH 0313/1133] Pass NULL controller if control is disabled If --no-control is requested, then the controller instance is not initialized. However, its reference was still passed to screen and input_manager. Instead, pass NULL if no controller is available. --- app/src/scrcpy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d7ba3670..5c2114eb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -405,6 +405,7 @@ scrcpy(struct scrcpy_options *options) { stream_add_sink(&s->stream, &rec->packet_sink); } + struct sc_controller *controller = NULL; struct sc_key_processor *kp = NULL; struct sc_mouse_processor *mp = NULL; @@ -510,6 +511,7 @@ aoa_hid_end: goto end; } controller_started = true; + controller = &s->controller; if (options->turn_screen_off) { struct sc_control_msg msg; @@ -528,7 +530,7 @@ aoa_hid_end: options->window_title ? options->window_title : info->device_name; struct sc_screen_params screen_params = { - .controller = &s->controller, + .controller = controller, .fp = fp, .kp = kp, .mp = mp, From 855819bbd83d9d8e06988357ac1b548b11a4a0db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 12:08:55 +0100 Subject: [PATCH 0314/1133] Remove redundant control boolean The controller is NULL if and only if control is disabled, so an additional control boolean is redundant. --- app/src/input_manager.c | 70 ++++++++++++++++++++--------------------- app/src/input_manager.h | 2 -- app/src/scrcpy.c | 4 ++- app/src/screen.c | 1 - app/src/screen.h | 1 - 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c7290dd6..c520b1aa 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -124,8 +124,8 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) { void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { - assert(!params->control || (params->kp && params->kp->ops)); - assert(!params->control || (params->mp && params->mp->ops)); + assert(!params->controller || (params->kp && params->kp->ops)); + assert(!params->controller || (params->mp && params->mp->ops)); im->controller = params->controller; im->fp = params->fp; @@ -133,7 +133,6 @@ sc_input_manager_init(struct sc_input_manager *im, im->kp = params->kp; im->mp = params->mp; - im->control = params->control; im->forward_all_clicks = params->forward_all_clicks; im->legacy_paste = params->legacy_paste; im->clipboard_autosync = params->clipboard_autosync; @@ -434,9 +433,7 @@ inverse_point(struct sc_point point, struct sc_size size) { static void sc_input_manager_process_key(struct sc_input_manager *im, const SDL_KeyboardEvent *event) { - // control: indicates the state of the command-line option --no-control - bool control = im->control; - + // controller is NULL if --no-control is requested struct sc_controller *controller = im->controller; SDL_Keycode keycode = event->keysym.sym; @@ -463,33 +460,33 @@ sc_input_manager_process_key(struct sc_input_manager *im, enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: - if (control && !shift && !repeat) { + if (controller && !shift && !repeat) { action_home(controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && !shift && !repeat) { + if (controller && !shift && !repeat) { action_back(controller, action); } return; case SDLK_s: - if (control && !shift && !repeat) { + if (controller && !shift && !repeat) { action_app_switch(controller, action); } return; case SDLK_m: - if (control && !shift && !repeat) { + if (controller && !shift && !repeat) { action_menu(controller, action); } return; case SDLK_p: - if (control && !shift && !repeat) { + if (controller && !shift && !repeat) { action_power(controller, action); } return; case SDLK_o: - if (control && !repeat && down) { + if (controller && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -497,13 +494,13 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_DOWN: - if (control && !shift) { + if (controller && !shift) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (control && !shift) { + if (controller && !shift) { // forward repeated events action_volume_up(controller, action); } @@ -519,19 +516,19 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_c: - if (control && !shift && !repeat && down) { + if (controller && !shift && !repeat && down) { get_device_clipboard(controller, GET_CLIPBOARD_COPY_KEY_COPY); } return; case SDLK_x: - if (control && !shift && !repeat && down) { + if (controller && !shift && !repeat && down) { get_device_clipboard(controller, GET_CLIPBOARD_COPY_KEY_CUT); } return; case SDLK_v: - if (control && !repeat && down) { + if (controller && !repeat && down) { if (shift || im->legacy_paste) { // inject the text as input events clipboard_paste(controller); @@ -564,7 +561,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_n: - if (control && !repeat && down) { + if (controller && !repeat && down) { if (shift) { collapse_panels(controller); } else if (im->key_repeat == 0) { @@ -575,7 +572,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_r: - if (control && !shift && !repeat && down) { + if (controller && !shift && !repeat && down) { rotate_device(controller); } return; @@ -584,7 +581,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; } - if (!control) { + if (!controller) { return; } @@ -701,7 +698,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im, static void sc_input_manager_process_mouse_button(struct sc_input_manager *im, const SDL_MouseButtonEvent *event) { - bool control = im->control; + struct sc_controller *controller = im->controller; if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate @@ -712,24 +709,24 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, if (!im->forward_all_clicks) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; - if (control && event->button == SDL_BUTTON_X1) { - action_app_switch(im->controller, action); + if (controller && event->button == SDL_BUTTON_X1) { + action_app_switch(controller, action); return; } - if (control && event->button == SDL_BUTTON_X2 && down) { + if (controller && event->button == SDL_BUTTON_X2 && down) { if (event->clicks < 2) { - expand_notification_panel(im->controller); + expand_notification_panel(controller); } else { - expand_settings_panel(im->controller); + expand_settings_panel(controller); } return; } - if (control && event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(im->controller, action); + if (controller && event->button == SDL_BUTTON_RIGHT) { + press_back_or_turn_screen_on(controller, action); return; } - if (control && event->button == SDL_BUTTON_MIDDLE) { - action_home(im->controller, action); + if (controller && event->button == SDL_BUTTON_MIDDLE) { + action_home(controller, action); return; } @@ -751,7 +748,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, // otherwise, send the click event to the device } - if (!control) { + if (!controller) { return; } @@ -865,9 +862,10 @@ sc_input_manager_process_file(struct sc_input_manager *im, void sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { + bool control = im->controller; switch (event->type) { case SDL_TEXTINPUT: - if (!im->control) { + if (!control) { break; } sc_input_manager_process_text_input(im, &event->text); @@ -879,13 +877,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { sc_input_manager_process_key(im, &event->key); break; case SDL_MOUSEMOTION: - if (!im->control) { + if (!control) { break; } sc_input_manager_process_mouse_motion(im, &event->motion); break; case SDL_MOUSEWHEEL: - if (!im->control) { + if (!control) { break; } sc_input_manager_process_mouse_wheel(im, &event->wheel); @@ -899,13 +897,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: - if (!im->control) { + if (!control) { break; } sc_input_manager_process_touch(im, &event->tfinger); break; case SDL_DROPFILE: { - if (!im->control) { + if (!control) { break; } sc_input_manager_process_file(im, &event->drop); diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 377835bf..f6c210e3 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -22,7 +22,6 @@ struct sc_input_manager { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool control; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; @@ -51,7 +50,6 @@ struct sc_input_manager_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool control; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5c2114eb..92521f45 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -525,6 +525,9 @@ aoa_hid_end: } + // There is a controller if and only if control is enabled + assert(options->control == !!controller); + if (options->display) { const char *window_title = options->window_title ? options->window_title : info->device_name; @@ -534,7 +537,6 @@ aoa_hid_end: .fp = fp, .kp = kp, .mp = mp, - .control = options->control, .forward_all_clicks = options->forward_all_clicks, .legacy_paste = options->legacy_paste, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/screen.c b/app/src/screen.c index 9393a92f..52dfae04 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -489,7 +489,6 @@ sc_screen_init(struct sc_screen *screen, .screen = screen, .kp = params->kp, .mp = params->mp, - .control = params->control, .forward_all_clicks = params->forward_all_clicks, .legacy_paste = params->legacy_paste, .clipboard_autosync = params->clipboard_autosync, diff --git a/app/src/screen.h b/app/src/screen.h index 677cf514..13bd4d99 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -74,7 +74,6 @@ struct sc_screen_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool control; bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; From 7e35bfe382da9f66aefdd46f0610a420a0815f2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 12:11:53 +0100 Subject: [PATCH 0315/1133] Refactor if-blocks Group all conditions requiring a controller in a single if-block. --- app/src/input_manager.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c520b1aa..28c74791 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -707,27 +707,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, bool down = event->type == SDL_MOUSEBUTTONDOWN; if (!im->forward_all_clicks) { - enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; + if (controller) { + enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; - if (controller && event->button == SDL_BUTTON_X1) { - action_app_switch(controller, action); - return; - } - if (controller && event->button == SDL_BUTTON_X2 && down) { - if (event->clicks < 2) { - expand_notification_panel(controller); - } else { - expand_settings_panel(controller); + if (event->button == SDL_BUTTON_X1) { + action_app_switch(controller, action); + return; + } + if (event->button == SDL_BUTTON_X2 && down) { + if (event->clicks < 2) { + expand_notification_panel(controller); + } else { + expand_settings_panel(controller); + } + return; + } + if (event->button == SDL_BUTTON_RIGHT) { + press_back_or_turn_screen_on(controller, action); + return; + } + if (event->button == SDL_BUTTON_MIDDLE) { + action_home(controller, action); + return; } - return; - } - if (controller && event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(controller, action); - return; - } - if (controller && event->button == SDL_BUTTON_MIDDLE) { - action_home(controller, action); - return; } // double-click on black borders resize to fit the device screen From 241a587e61706321a7f8767d90aaf9876037c2eb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 12:31:05 +0100 Subject: [PATCH 0316/1133] Fix missing HID mouse destructor call The destructor unregisters the HID mouse, so it was not reported as a leak, but it must still be called. --- app/src/scrcpy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 92521f45..5f4455ba 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -598,6 +598,9 @@ end: if (hid_keyboard_initialized) { sc_hid_keyboard_destroy(&s->keyboard_hid); } + if (hid_mouse_initialized) { + sc_hid_mouse_destroy(&s->mouse_hid); + } sc_aoa_stop(&s->aoa); } if (acksync) { From 308a1f8192be145242919b153577d319497cd2fd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 21:55:41 +0100 Subject: [PATCH 0317/1133] Simplify error handling in sc_aoa_init() Use goto to avoid many repetitions. --- app/src/aoa_hid.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index 9de918bc..25dbcc8e 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -131,31 +131,22 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial, } if (!sc_cond_init(&aoa->event_cond)) { - sc_mutex_destroy(&aoa->mutex); - return false; + goto error_destroy_mutex; } if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) { - sc_cond_destroy(&aoa->event_cond); - sc_mutex_destroy(&aoa->mutex); - return false; + goto error_destroy_cond; } aoa->usb_device = sc_aoa_find_usb_device(serial); if (!aoa->usb_device) { LOGW("USB device of serial %s not found", serial); - libusb_exit(aoa->usb_context); - sc_mutex_destroy(&aoa->mutex); - sc_cond_destroy(&aoa->event_cond); - return false; + goto error_exit_libusb; } if (sc_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); - sc_cond_destroy(&aoa->event_cond); - sc_mutex_destroy(&aoa->mutex); + goto error_unref_device; return false; } @@ -163,6 +154,16 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial, aoa->acksync = acksync; return true; + +error_unref_device: + libusb_unref_device(aoa->usb_device); +error_exit_libusb: + libusb_exit(aoa->usb_context); +error_destroy_cond: + sc_cond_destroy(&aoa->event_cond); +error_destroy_mutex: + sc_mutex_destroy(&aoa->mutex); + return false; } void From d41a46dc95d13922d7b1166d8454842402e67a92 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jan 2022 21:56:15 +0100 Subject: [PATCH 0318/1133] Handle libusb_get_device_descriptor() error The function libusb_get_device_descriptor() might return an error. Handle it. --- app/src/aoa_hid.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index 25dbcc8e..a9460c3b 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -56,14 +56,13 @@ accept_device(libusb_device *device, const char *serial) { // devices available on the computer have permission restrictions struct libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); - - if (!desc.iSerialNumber) { + int result = libusb_get_device_descriptor(device, &desc); + if (result < 0 || !desc.iSerialNumber) { return false; } libusb_device_handle *handle; - int result = libusb_open(device, &handle); + result = libusb_open(device, &handle); if (result < 0) { return false; } From 1f65b1bf87d835bdd96475b27c1712cd7c63b00b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 14:48:06 +0100 Subject: [PATCH 0319/1133] Remove inline hint There is no reason to request inlining here. --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index 52dfae04..c2dbb9f4 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -156,7 +156,7 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, return window_size; } -static inline void +static void sc_screen_capture_mouse(struct sc_screen *screen, bool capture) { if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", From ac038f276eb5e7dcc6fac7fa7120da23a38bc0ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 14:57:12 +0100 Subject: [PATCH 0320/1133] Add missing break statement This was harmless because this is the last "case" of the switch, but for consistency, add the missing break. --- app/src/screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/screen.c b/app/src/screen.c index c2dbb9f4..cdd84367 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -873,6 +873,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { sc_screen_capture_mouse(screen, true); return; } + break; } sc_input_manager_handle_event(&screen->im, event); From 8c7f0ed5ead5dc15dc10fb1b521b7ec91523c50e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 16:07:15 +0100 Subject: [PATCH 0321/1133] Fix warning message Make the message consistent for HID keyboard and HID mouse. --- app/src/hid_mouse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/hid_mouse.c b/app/src/hid_mouse.c index 0e26c7c3..c24b4a4a 100644 --- a/app/src/hid_mouse.c +++ b/app/src/hid_mouse.c @@ -262,6 +262,6 @@ void sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) { bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); if (!ok) { - LOGW("Could not unregister HID"); + LOGW("Could not unregister HID mouse"); } } From 17c97820b2030759e8b7e431e7d353c30bcff3dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 15:12:03 +0100 Subject: [PATCH 0322/1133] Never forward capture keys In relative mode, Alt and Super are "capture keys". Never forward them to the input manager, to avoid inconsistencies between UP and DOWN events. --- app/src/screen.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index cdd84367..af433648 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -824,14 +824,13 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (sc_screen_is_mouse_capture_key(key)) { if (!screen->mouse_capture_key_pressed) { screen->mouse_capture_key_pressed = key; - return; } else { // Another mouse capture key has been pressed, cancel // mouse (un)capture screen->mouse_capture_key_pressed = 0; - // Do not return, the event must be forwarded to the - // input manager } + // Mouse capture keys are never forwarded to the device + return; } } break; @@ -840,14 +839,16 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { SDL_Keycode key = event->key.keysym.sym; SDL_Keycode cap = screen->mouse_capture_key_pressed; screen->mouse_capture_key_pressed = 0; - if (key == cap) { - // A mouse capture key has been pressed then released: - // toggle the capture mouse mode - sc_screen_capture_mouse(screen, !screen->mouse_captured); + if (sc_screen_is_mouse_capture_key(key)) { + if (key == cap) { + // A mouse capture key has been pressed then released: + // toggle the capture mouse mode + sc_screen_capture_mouse(screen, + !screen->mouse_captured); + } + // Mouse capture keys are never forwarded to the device return; } - // Do not return, the event must be forwarded to the input - // manager } break; case SDL_MOUSEWHEEL: From 4bf9c057fe00c397982bc610998f7f1d1fd39017 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 21:23:31 +0100 Subject: [PATCH 0323/1133] Extract relative mode check to an inline function This will allow to reuse the condition in another function. --- app/src/screen.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index af433648..31c63092 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -156,6 +156,12 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, return window_size; } +static inline bool +sc_screen_is_relative_mode(struct sc_screen *screen) { + // screen->im.mp may be NULL if --no-control + return screen->im.mp && screen->im.mp->relative_mode; +} + static void sc_screen_capture_mouse(struct sc_screen *screen, bool capture) { if (SDL_SetRelativeMouseMode(capture)) { @@ -772,8 +778,7 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { void sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { - // screen->im.mp may be NULL if --no-control - bool relative_mode = screen->im.mp && screen->im.mp->relative_mode; + bool relative_mode = sc_screen_is_relative_mode(screen); switch (event->type) { case EVENT_NEW_FRAME: { From 063d103dd6f959f6f1619f20e4e964a042c700ad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 21:30:30 +0100 Subject: [PATCH 0324/1133] Capture mouse on start for --hid-mouse If relative mode is enabled, capture the mouse immediately. --- app/src/screen.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 31c63092..c62920bc 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -707,6 +707,11 @@ sc_screen_update_frame(struct sc_screen *screen) { screen->has_frame = true; // this is the very first frame, show the window sc_screen_show_initial_window(screen); + + if (sc_screen_is_relative_mode(screen)) { + // Capture mouse on start + sc_screen_capture_mouse(screen, true); + } } sc_screen_render(screen, false); From a9429efa34b3cbdc57847cb37d3621b889522c7f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 21:46:57 +0100 Subject: [PATCH 0325/1133] Fix downsize on error before first frame Retry with a lower definition if MediaCodec fails before the first frame, not the first packet. In practice, the first packet is a config packet without any frame, and MediaCodec might fail just after. Refs 2eb6fe7d81c15912a2252f229d317ae03f618a33 Refs #2963 --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index e4e87c72..4c23dd92 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -159,7 +159,10 @@ public class ScreenEncoder implements Device.RotationListener { } IO.writeFully(fd, codecBuffer); - firstFrameSent = true; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { + // If this is not a config packet, then it contains a frame + firstFrameSent = true; + } } } finally { if (outputBufferId >= 0) { From 1ff69e21c28ad69f6a39219c5da4c5f77dbaf9f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 08:54:09 +0100 Subject: [PATCH 0326/1133] Update error messages in FAQ With the recent versions, scrcpy first executes "adb get-serialno", so the adb error messages are not exactly the same. --- FAQ.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FAQ.md b/FAQ.md index 399f3504..edcea3b0 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,7 +12,7 @@ Here are the common reported problems and their status. In that case, it will print this error: -> ERROR: "adb push" returned with value 1 +> ERROR: "adb get-serialno" returned with value 1 This is typically not a bug in _scrcpy_, but a problem in your environment. @@ -39,7 +39,7 @@ Check [stackoverflow][device-unauthorized]. ### Device not detected -> adb: error: failed to get feature set: no devices/emulators found +> error: no devices/emulators found Check that you correctly enabled [adb debugging][enable-adb]. @@ -54,7 +54,7 @@ If your device is not detected, you may need some [drivers] (on Windows). There If several devices are connected, you will encounter this error: -> adb: error: failed to get feature set: more than one device/emulator +> error: more than one device/emulator the identifier of the device you want to mirror must be provided: From ae8fdda09ec5730bf84e80261b7e09fe44680b8d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 08:55:27 +0100 Subject: [PATCH 0327/1133] Improve FAQ explanations --- FAQ.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/FAQ.md b/FAQ.md index edcea3b0..270fcaf3 100644 --- a/FAQ.md +++ b/FAQ.md @@ -32,7 +32,16 @@ in the release, so it should work out-of-the-box. ### Device unauthorized -Check [stackoverflow][device-unauthorized]. + +> error: device unauthorized. +> This adb server's $ADB_VENDOR_KEYS is not set +> Try 'adb kill-server' if that seems wrong. +> Otherwise check for a confirmation dialog on your device. + +When connecting, a popup should open on the device. You must authorize USB +debugging. + +If it does not open, check [stackoverflow][device-unauthorized]. [device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized @@ -62,7 +71,7 @@ the identifier of the device you want to mirror must be provided: scrcpy -s 01234567890abcdef ``` -Note that if your device is connected over TCP/IP, you'll get this message: +Note that if your device is connected over TCP/IP, you might get this message: > adb: error: more than one device/emulator > ERROR: "adb reverse" returned with value 1 From e0bce1725bdb6b5f114051b9611d90f57050a77c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 21:37:40 +0100 Subject: [PATCH 0328/1133] Fix header guard prefix --- app/src/hid_mouse.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/hid_mouse.h b/app/src/hid_mouse.h index 2819b1ff..b89f7795 100644 --- a/app/src/hid_mouse.h +++ b/app/src/hid_mouse.h @@ -1,5 +1,5 @@ -#ifndef HID_MOUSE_H -#define HID_MOUSE_H +#ifndef SC_HID_MOUSE_H +#define SC_HID_MOUSE_H #include "common.h" From 5d6076bffd9ffe03ce7299f7f3459f6d018fb8c7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 21:38:30 +0100 Subject: [PATCH 0329/1133] Move misplaced break statements With ifdefs, the resulting code could contain both a return statement and a break. --- app/src/cli.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 69177aef..21d13b44 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1320,12 +1320,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'K': #ifdef HAVE_AOA_HID opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; + break; #else LOGE("HID over AOA (-K/--hid-keyboard) is not supported on " "this platform. It is only available on Linux."); return false; #endif - break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; @@ -1339,12 +1339,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'M': #ifdef HAVE_AOA_HID opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; + break; #else LOGE("HID over AOA (-M/--hid-mouse) is not supported on this" "platform. It is only available on Linux."); return false; #endif - break; case OPT_LOCK_VIDEO_ORIENTATION: if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { @@ -1503,21 +1503,21 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_V4L2_SINK: #ifdef HAVE_V4L2 opts->v4l2_device = optarg; + break; #else LOGE("V4L2 (--v4l2-sink) is only available on Linux."); return false; #endif - break; case OPT_V4L2_BUFFER: #ifdef HAVE_V4L2 if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) { return false; } + break; #else LOGE("V4L2 (--v4l2-buffer) is only available on Linux."); return false; #endif - break; default: // getopt prints the error message on stderr return false; From ca516f4318100e93d8fa4a88e05f004bafd1dc91 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 21:44:28 +0100 Subject: [PATCH 0330/1133] Refactor if-block in cli Several tests must be performed if opts->control is false. --- app/src/cli.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 21d13b44..57e02df8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1591,14 +1591,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } - if (!opts->control && opts->turn_screen_off) { - LOGE("Could not request to turn screen off if control is disabled"); - return false; - } - - if (!opts->control && opts->stay_awake) { - LOGE("Could not request to stay awake if control is disabled"); - return false; + if (!opts->control) { + if (opts->turn_screen_off) { + LOGE("Could not request to turn screen off if control is disabled"); + return false; + } + if (opts->stay_awake) { + LOGE("Could not request to stay awake if control is disabled"); + return false; + } } return true; From f289d206ea0e10cc052137781c39da862ebe2f73 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 21:45:44 +0100 Subject: [PATCH 0331/1133] Disable more actions if --no-control If control is disabled, then do not enable "show touches" or automatically power off the device on close. --- app/src/cli.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 57e02df8..60492730 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1600,6 +1600,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("Could not request to stay awake if control is disabled"); return false; } + if (opts->show_touches) { + LOGE("Could not request to show touches if control is disabled"); + return false; + } + if (opts->power_off_on_close) { + LOGE("Could not request power off on close if control is disabled"); + return false; + } } return true; From 31a5d0c2bfab4d8f74b116d634f63a0f3a79889a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 10:23:09 +0100 Subject: [PATCH 0332/1133] Move call to send device name and size This will allow to optionally disable it. PR #2971 --- .../java/com/genymobile/scrcpy/DesktopConnection.java | 9 +++------ server/src/main/java/com/genymobile/scrcpy/Server.java | 4 +++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 40cb088c..ca20c1d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -46,7 +46,7 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(Device device, boolean tunnelForward, boolean control) throws IOException { + public static DesktopConnection open(boolean tunnelForward, boolean control) throws IOException { LocalSocket videoSocket; LocalSocket controlSocket = null; if (tunnelForward) { @@ -78,10 +78,7 @@ public final class DesktopConnection implements Closeable { } } - DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket); - Size videoSize = device.getScreenInfo().getVideoSize(); - connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); - return connection; + return new DesktopConnection(videoSocket, controlSocket); } public void close() throws IOException { @@ -95,7 +92,7 @@ public final class DesktopConnection implements Closeable { } } - private void send(String deviceName, int width, int height) throws IOException { + public void sendDeviceMeta(String deviceName, int width, int height) throws IOException { byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9d7a62e3..6dc0b2c6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -67,7 +67,9 @@ public final class Server { boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); - try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) { + try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control)) { + Size videoSize = device.getScreenInfo().getVideoSize(); + connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); From 6b21f4ae13e0644632d2bef4f14ff590b1fa5e18 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 10:29:06 +0100 Subject: [PATCH 0333/1133] Reorder scrcpy-server options Move the options unused by the scrcpy client at the end. These options may be useful to use scrcpy-server directly (to get a raw H.264 stream for example). PR #2971 --- .../java/com/genymobile/scrcpy/Options.java | 20 ++++++++++--------- .../java/com/genymobile/scrcpy/Server.java | 8 ++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index ef7d8572..236a93d7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,7 +12,6 @@ public class Options { private int lockVideoOrientation = -1; private boolean tunnelForward; private Rect crop; - private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean control = true; private int displayId; private boolean showTouches; @@ -23,6 +22,9 @@ public class Options { private boolean clipboardAutosync = true; private boolean downsizeOnError = true; + // Options not used by the scrcpy client, but useful to use scrcpy-server directly + private boolean sendFrameMeta = true; // send PTS so that the client may record properly + public Ln.Level getLogLevel() { return logLevel; } @@ -79,14 +81,6 @@ public class Options { this.crop = crop; } - public boolean getSendFrameMeta() { - return sendFrameMeta; - } - - public void setSendFrameMeta(boolean sendFrameMeta) { - this.sendFrameMeta = sendFrameMeta; - } - public boolean getControl() { return control; } @@ -158,4 +152,12 @@ public class Options { public void setDownsizeOnError(boolean downsizeOnError) { this.downsizeOnError = downsizeOnError; } + + public boolean getSendFrameMeta() { + return sendFrameMeta; + } + + public void setSendFrameMeta(boolean sendFrameMeta) { + this.sendFrameMeta = sendFrameMeta; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 6dc0b2c6..e690a45b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -201,10 +201,6 @@ public final class Server { Rect crop = parseCrop(value); options.setCrop(crop); break; - case "send_frame_meta": - boolean sendFrameMeta = Boolean.parseBoolean(value); - options.setSendFrameMeta(sendFrameMeta); - break; case "control": boolean control = Boolean.parseBoolean(value); options.setControl(control); @@ -242,6 +238,10 @@ public final class Server { boolean downsizeOnError = Boolean.parseBoolean(value); options.setDownsizeOnError(downsizeOnError); break; + case "send_frame_meta": + boolean sendFrameMeta = Boolean.parseBoolean(value); + options.setSendFrameMeta(sendFrameMeta); + break; default: Ln.w("Unknown server option: " + key); break; From 3ba32c2a0d57b708980df121caad84252f720699 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 10:24:41 +0100 Subject: [PATCH 0334/1133] Add server option send_device_meta Similar to send_device_frame, this option allows to disable sending the device name and size on start. This is only useful when using the scrcpy-server alone to get a raw H.264 stream, without using the scrcpy client. PR #2971 --- .../src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 236a93d7..9210b318 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -23,6 +23,7 @@ public class Options { private boolean downsizeOnError = true; // Options not used by the scrcpy client, but useful to use scrcpy-server directly + private boolean sendDeviceMeta = true; // send device name and size private boolean sendFrameMeta = true; // send PTS so that the client may record properly public Ln.Level getLogLevel() { @@ -153,6 +154,14 @@ public class Options { this.downsizeOnError = downsizeOnError; } + public boolean getSendDeviceMeta() { + return sendDeviceMeta; + } + + public void setSendDeviceMeta(boolean sendDeviceMeta) { + this.sendDeviceMeta = sendDeviceMeta; + } + public boolean getSendFrameMeta() { return sendFrameMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index e690a45b..20fd1c1b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -68,8 +68,10 @@ public final class Server { boolean control = options.getControl(); try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control)) { - Size videoSize = device.getScreenInfo().getVideoSize(); - connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); + if (options.getSendDeviceMeta()) { + Size videoSize = device.getScreenInfo().getVideoSize(); + connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); + } ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); @@ -238,6 +240,10 @@ public final class Server { boolean downsizeOnError = Boolean.parseBoolean(value); options.setDownsizeOnError(downsizeOnError); break; + case "send_device_meta": + boolean sendDeviceMeta = Boolean.parseBoolean(value); + options.setSendDeviceMeta(sendDeviceMeta); + break; case "send_frame_meta": boolean sendFrameMeta = Boolean.parseBoolean(value); options.setSendFrameMeta(sendFrameMeta); From 45a5e560dfeabed8b32482caad857424ff472518 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 10:36:18 +0100 Subject: [PATCH 0335/1133] Add server option send_dummy_byte If set to false, no dummy byte is written to detect a connection error. PR #2971 --- .../java/com/genymobile/scrcpy/DesktopConnection.java | 8 +++++--- server/src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 7 ++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index ca20c1d8..78728d81 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -46,15 +46,17 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(boolean tunnelForward, boolean control) throws IOException { + public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { LocalSocket videoSocket; LocalSocket controlSocket = null; if (tunnelForward) { LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME); try { videoSocket = localServerSocket.accept(); - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + } if (control) { try { controlSocket = localServerSocket.accept(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 9210b318..92cf1e7a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -25,6 +25,7 @@ public class Options { // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size private boolean sendFrameMeta = true; // send PTS so that the client may record properly + private boolean sendDummyByte = true; // write a byte on start to detect connection issues public Ln.Level getLogLevel() { return logLevel; @@ -169,4 +170,12 @@ public class Options { public void setSendFrameMeta(boolean sendFrameMeta) { this.sendFrameMeta = sendFrameMeta; } + + public boolean getSendDummyByte() { + return sendDummyByte; + } + + public void setSendDummyByte(boolean sendDummyByte) { + this.sendDummyByte = sendDummyByte; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 20fd1c1b..d9c22771 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -66,8 +66,9 @@ public final class Server { boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); + boolean sendDummyByte = options.getSendDummyByte(); - try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control)) { + try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) { if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); @@ -248,6 +249,10 @@ public final class Server { boolean sendFrameMeta = Boolean.parseBoolean(value); options.setSendFrameMeta(sendFrameMeta); break; + case "send_dummy_byte": + boolean sendDummyByte = Boolean.parseBoolean(value); + options.setSendDummyByte(sendDummyByte); + break; default: Ln.w("Unknown server option: " + key); break; From 2faf9715bebe720ed75ce98f5f8c2b2b1532880a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 10:47:08 +0100 Subject: [PATCH 0336/1133] Add server option raw_video_stream For convenience, this new option forces the 3 following options: - send_device_meta=false - send_frame_meta=false - send_dummy_byte=false This allows to send a raw H.264 stream on the video socket. Concretely: adb push scrcpy-server /data/local/tmp/scrcpy-server.jar adb forward tcp:1234 localabstract:scrcpy adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar \ app_process / com.genymobile.scrcpy.Server 1.21 \ raw_video_stream=true tunnel_forward=true control=false As soon as a client connects via TCP to localhost:1234, it will receive the raw H.264 stream. Refs #1419 comment PR #2971 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d9c22771..68c6c02c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -253,6 +253,14 @@ public final class Server { boolean sendDummyByte = Boolean.parseBoolean(value); options.setSendDummyByte(sendDummyByte); break; + case "raw_video_stream": + boolean rawVideoStream = Boolean.parseBoolean(value); + if (rawVideoStream) { + options.setSendDeviceMeta(false); + options.setSendFrameMeta(false); + options.setSendDummyByte(false); + } + break; default: Ln.w("Unknown server option: " + key); break; From 9d2e00697e5b6c27def3791e55cee67b8dd395b5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 21:31:30 +0100 Subject: [PATCH 0337/1133] Use sc_ prefix for control_msg enums Refs afa4a1b728d6f130acff23c0e3fb4609bcb8a6b2 --- app/src/control_msg.c | 57 ++++++------ app/src/control_msg.h | 48 +++++----- app/src/controller.c | 2 +- app/src/input_manager.c | 38 ++++---- app/src/keyboard_inject.c | 4 +- app/src/mouse_inject.c | 8 +- app/src/scrcpy.c | 4 +- app/tests/test_control_msg_serialize.c | 124 ++++++++++++------------- 8 files changed, 141 insertions(+), 144 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index d093183a..e8ae4681 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -92,19 +92,19 @@ size_t sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { buf[0] = msg->type; switch (msg->type) { - case CONTROL_MSG_TYPE_INJECT_KEYCODE: + case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: buf[1] = msg->inject_keycode.action; buffer_write32be(&buf[2], msg->inject_keycode.keycode); buffer_write32be(&buf[6], msg->inject_keycode.repeat); buffer_write32be(&buf[10], msg->inject_keycode.metastate); return 14; - case CONTROL_MSG_TYPE_INJECT_TEXT: { + case SC_CONTROL_MSG_TYPE_INJECT_TEXT: { size_t len = write_string(msg->inject_text.text, - CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]); + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]); return 1 + len; } - case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: + case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: buf[1] = msg->inject_touch_event.action; buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id); write_position(&buf[10], &msg->inject_touch_event.position); @@ -113,7 +113,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { buffer_write16be(&buf[22], pressure); buffer_write32be(&buf[24], msg->inject_touch_event.buttons); return 28; - case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: + case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: write_position(&buf[1], &msg->inject_scroll_event.position); buffer_write32be(&buf[13], (uint32_t) msg->inject_scroll_event.hscroll); @@ -121,27 +121,26 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { (uint32_t) msg->inject_scroll_event.vscroll); buffer_write32be(&buf[21], msg->inject_scroll_event.buttons); return 25; - case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: + case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: buf[1] = msg->inject_keycode.action; return 2; - case CONTROL_MSG_TYPE_GET_CLIPBOARD: + case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD: buf[1] = msg->get_clipboard.copy_key; return 2; - case CONTROL_MSG_TYPE_SET_CLIPBOARD: { + case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: buffer_write64be(&buf[1], msg->set_clipboard.sequence); buf[9] = !!msg->set_clipboard.paste; size_t len = write_string(msg->set_clipboard.text, - CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, + SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, &buf[10]); return 10 + len; - } - case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: + case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: buf[1] = msg->set_screen_power_mode.mode; return 2; - case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: - case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: - case CONTROL_MSG_TYPE_COLLAPSE_PANELS: - case CONTROL_MSG_TYPE_ROTATE_DEVICE: + case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: + case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: + case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: + case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: // no additional data return 1; default: @@ -154,17 +153,17 @@ void sc_control_msg_log(const struct sc_control_msg *msg) { #define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__) switch (msg->type) { - case CONTROL_MSG_TYPE_INJECT_KEYCODE: + case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx", KEYEVENT_ACTION_LABEL(msg->inject_keycode.action), (int) msg->inject_keycode.keycode, msg->inject_keycode.repeat, (long) msg->inject_keycode.metastate); break; - case CONTROL_MSG_TYPE_INJECT_TEXT: + case SC_CONTROL_MSG_TYPE_INJECT_TEXT: LOG_CMSG("text \"%s\"", msg->inject_text.text); break; - case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: { + case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: { int action = msg->inject_touch_event.action & AMOTION_EVENT_ACTION_MASK; uint64_t id = msg->inject_touch_event.pointer_id; @@ -191,7 +190,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) { } break; } - case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: + case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 " vscroll=%" PRIi32 " buttons=%06lx", msg->inject_scroll_event.position.point.x, @@ -200,34 +199,34 @@ sc_control_msg_log(const struct sc_control_msg *msg) { msg->inject_scroll_event.vscroll, (long) msg->inject_scroll_event.buttons); break; - case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: + case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: LOG_CMSG("back-or-screen-on %s", KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); break; - case CONTROL_MSG_TYPE_GET_CLIPBOARD: + case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD: LOG_CMSG("get clipboard copy_key=%s", copy_key_labels[msg->get_clipboard.copy_key]); break; - case CONTROL_MSG_TYPE_SET_CLIPBOARD: + case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"", msg->set_clipboard.sequence, msg->set_clipboard.paste ? "paste" : "nopaste", msg->set_clipboard.text); break; - case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: + case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: LOG_CMSG("power mode %s", SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode)); break; - case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: + case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: LOG_CMSG("expand notification panel"); break; - case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: + case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: LOG_CMSG("expand settings panel"); break; - case CONTROL_MSG_TYPE_COLLAPSE_PANELS: + case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: LOG_CMSG("collapse panels"); break; - case CONTROL_MSG_TYPE_ROTATE_DEVICE: + case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: LOG_CMSG("rotate device"); break; default: @@ -239,10 +238,10 @@ sc_control_msg_log(const struct sc_control_msg *msg) { void sc_control_msg_destroy(struct sc_control_msg *msg) { switch (msg->type) { - case CONTROL_MSG_TYPE_INJECT_TEXT: + case SC_CONTROL_MSG_TYPE_INJECT_TEXT: free(msg->inject_text.text); break; - case CONTROL_MSG_TYPE_SET_CLIPBOARD: + case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: free(msg->set_clipboard.text); break; default: diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 2ac96064..fbe203d4 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -11,40 +11,40 @@ #include "android/keycodes.h" #include "coords.h" -#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k +#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k -#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 +#define SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes -#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14) +#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14) #define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) enum sc_control_msg_type { - CONTROL_MSG_TYPE_INJECT_KEYCODE, - CONTROL_MSG_TYPE_INJECT_TEXT, - CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, - CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, - CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, - CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, - CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, - CONTROL_MSG_TYPE_COLLAPSE_PANELS, - CONTROL_MSG_TYPE_GET_CLIPBOARD, - CONTROL_MSG_TYPE_SET_CLIPBOARD, - CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, - CONTROL_MSG_TYPE_ROTATE_DEVICE, + SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, + SC_CONTROL_MSG_TYPE_INJECT_TEXT, + SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, + SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, + SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, + SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, + SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, + SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, + SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, + SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, + SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, }; -enum screen_power_mode { +enum sc_screen_power_mode { // see - SCREEN_POWER_MODE_OFF = 0, - SCREEN_POWER_MODE_NORMAL = 2, + SC_SCREEN_POWER_MODE_OFF = 0, + SC_SCREEN_POWER_MODE_NORMAL = 2, }; -enum get_clipboard_copy_key { - GET_CLIPBOARD_COPY_KEY_NONE, - GET_CLIPBOARD_COPY_KEY_COPY, - GET_CLIPBOARD_COPY_KEY_CUT, +enum sc_copy_key { + SC_COPY_KEY_NONE, + SC_COPY_KEY_COPY, + SC_COPY_KEY_CUT, }; struct sc_control_msg { @@ -77,7 +77,7 @@ struct sc_control_msg { // screen may only be turned on on ACTION_DOWN } back_or_screen_on; struct { - enum get_clipboard_copy_key copy_key; + enum sc_copy_key copy_key; } get_clipboard; struct { uint64_t sequence; @@ -85,7 +85,7 @@ struct sc_control_msg { bool paste; } set_clipboard; struct { - enum screen_power_mode mode; + enum sc_screen_power_mode mode; } set_screen_power_mode; }; }; diff --git a/app/src/controller.c b/app/src/controller.c index 9135d967..626a5e30 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -66,7 +66,7 @@ sc_controller_push_msg(struct sc_controller *controller, static bool process_msg(struct sc_controller *controller, const struct sc_control_msg *msg) { - static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; + static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE]; size_t length = sc_control_msg_serialize(msg, serialized_msg); if (!length) { return false; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 28c74791..d623c5ae 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -161,7 +161,7 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode, enum sc_action action, const char *name) { // send DOWN event struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE; msg.inject_keycode.action = action == SC_ACTION_DOWN ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP; @@ -215,7 +215,7 @@ static void press_back_or_turn_screen_on(struct sc_controller *controller, enum sc_action action) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; + msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.back_or_screen_on.action = action == SC_ACTION_DOWN ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP; @@ -228,7 +228,7 @@ press_back_or_turn_screen_on(struct sc_controller *controller, static void expand_notification_panel(struct sc_controller *controller) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; + msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'expand notification panel'"); @@ -238,7 +238,7 @@ expand_notification_panel(struct sc_controller *controller) { static void expand_settings_panel(struct sc_controller *controller) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; + msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'expand settings panel'"); @@ -248,7 +248,7 @@ expand_settings_panel(struct sc_controller *controller) { static void collapse_panels(struct sc_controller *controller) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS; + msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS; if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request 'collapse notification panel'"); @@ -257,9 +257,9 @@ collapse_panels(struct sc_controller *controller) { static bool get_device_clipboard(struct sc_controller *controller, - enum get_clipboard_copy_key copy_key) { + enum sc_copy_key copy_key) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD; + msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.get_clipboard.copy_key = copy_key; if (!sc_controller_push_msg(controller, &msg)) { @@ -287,7 +287,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste, } struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; + msg.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.set_clipboard.sequence = sequence; msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; @@ -303,9 +303,9 @@ set_device_clipboard(struct sc_controller *controller, bool paste, static void set_screen_power_mode(struct sc_controller *controller, - enum screen_power_mode mode) { + enum sc_screen_power_mode mode) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; + msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = mode; if (!sc_controller_push_msg(controller, &msg)) { @@ -350,7 +350,7 @@ clipboard_paste(struct sc_controller *controller) { } struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = text_dup; if (!sc_controller_push_msg(controller, &msg)) { free(text_dup); @@ -361,7 +361,7 @@ clipboard_paste(struct sc_controller *controller) { static void rotate_device(struct sc_controller *controller) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE; + msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE; if (!sc_controller_push_msg(controller, &msg)) { LOGW("Could not request device rotation"); @@ -407,7 +407,7 @@ simulate_virtual_finger(struct sc_input_manager *im, bool up = action == AMOTION_EVENT_ACTION_UP; struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; msg.inject_touch_event.action = action; msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.point = point; @@ -487,9 +487,9 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_o: if (controller && !repeat && down) { - enum screen_power_mode mode = shift - ? SCREEN_POWER_MODE_NORMAL - : SCREEN_POWER_MODE_OFF; + enum sc_screen_power_mode mode = shift + ? SC_SCREEN_POWER_MODE_NORMAL + : SC_SCREEN_POWER_MODE_OFF; set_screen_power_mode(controller, mode); } return; @@ -517,14 +517,12 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_c: if (controller && !shift && !repeat && down) { - get_device_clipboard(controller, - GET_CLIPBOARD_COPY_KEY_COPY); + get_device_clipboard(controller, SC_COPY_KEY_COPY); } return; case SDLK_x: if (controller && !shift && !repeat && down) { - get_device_clipboard(controller, - GET_CLIPBOARD_COPY_KEY_CUT); + get_device_clipboard(controller, SC_COPY_KEY_CUT); } return; case SDLK_v: diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_inject.c index 7276d325..fe297310 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_inject.c @@ -248,7 +248,7 @@ convert_meta_state(uint16_t mod) { static bool convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg, enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { - msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; + msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE; if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode, event->mods_state, key_inject_mode)) { @@ -310,7 +310,7 @@ sc_key_processor_process_text(struct sc_key_processor *kp, } struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = strdup(event->text); if (!msg.inject_text.text) { LOGW("Could not strdup input text"); diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 855aaa9f..2e89de9a 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -66,7 +66,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_MOVE, .pointer_id = POINTER_ID_MOUSE, @@ -87,7 +87,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = convert_mouse_action(event->action), .pointer_id = POINTER_ID_MOUSE, @@ -108,7 +108,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = event->position, .hscroll = event->hscroll, @@ -128,7 +128,7 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = convert_touch_action(event->action), .pointer_id = event->pointer_id, diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5f4455ba..1534c772 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -515,8 +515,8 @@ aoa_hid_end: if (options->turn_screen_off) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; - msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF; + msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; + msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF; if (!sc_controller_push_msg(&s->controller, &msg)) { LOGW("Could not request 'set screen power mode'"); diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 95a54a98..72e3d87a 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -7,7 +7,7 @@ static void test_serialize_inject_keycode(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_KEYCODE, + .type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, .inject_keycode = { .action = AKEY_EVENT_ACTION_UP, .keycode = AKEYCODE_ENTER, @@ -16,12 +16,12 @@ static void test_serialize_inject_keycode(void) { }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 14); const unsigned char expected[] = { - CONTROL_MSG_TYPE_INJECT_KEYCODE, + SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, 0x01, // AKEY_EVENT_ACTION_UP 0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER 0x00, 0x00, 0x00, 0X05, // repeat @@ -32,18 +32,18 @@ static void test_serialize_inject_keycode(void) { static void test_serialize_inject_text(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TEXT, + .type = SC_CONTROL_MSG_TYPE_INJECT_TEXT, .inject_text = { .text = "hello, world!", }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 18); const unsigned char expected[] = { - CONTROL_MSG_TYPE_INJECT_TEXT, + SC_CONTROL_MSG_TYPE_INJECT_TEXT, 0x00, 0x00, 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text }; @@ -52,30 +52,30 @@ static void test_serialize_inject_text(void) { static void test_serialize_inject_text_long(void) { struct sc_control_msg msg; - msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; - char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; - memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); - text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; + msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT; + char text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; + memset(text, 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); + text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; msg.inject_text.text = text; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); - assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); + assert(size == 5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); - unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; - expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT; + unsigned char expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; + expected[0] = SC_CONTROL_MSG_TYPE_INJECT_TEXT; expected[1] = 0x00; expected[2] = 0x00; expected[3] = 0x01; expected[4] = 0x2c; // text length (32 bits) - memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); + memset(&expected[5], 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_inject_touch_event(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_DOWN, .pointer_id = UINT64_C(0x1234567887654321), @@ -94,12 +94,12 @@ static void test_serialize_inject_touch_event(void) { }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 28); const unsigned char expected[] = { - CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, + SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, 0x00, // AKEY_EVENT_ACTION_DOWN 0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200 @@ -112,7 +112,7 @@ static void test_serialize_inject_touch_event(void) { static void test_serialize_inject_scroll_event(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, + .type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = { .point = { @@ -130,12 +130,12 @@ static void test_serialize_inject_scroll_event(void) { }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 25); const unsigned char expected[] = { - CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, + SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x04, 0x38, 0x07, 0x80, // 1080 1920 0x00, 0x00, 0x00, 0x01, // 1 @@ -147,18 +147,18 @@ static void test_serialize_inject_scroll_event(void) { static void test_serialize_back_or_screen_on(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, + .type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, .back_or_screen_on = { .action = AKEY_EVENT_ACTION_UP, }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { - CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, + SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, 0x01, // AKEY_EVENT_ACTION_UP }; assert(!memcmp(buf, expected, sizeof(expected))); @@ -166,71 +166,71 @@ static void test_serialize_back_or_screen_on(void) { static void test_serialize_expand_notification_panel(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, + .type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { - CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, + SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_expand_settings_panel(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, + .type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { - CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, + SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, }; assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_collapse_panels(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_COLLAPSE_PANELS, + .type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { - CONTROL_MSG_TYPE_COLLAPSE_PANELS, + SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_get_clipboard(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_GET_CLIPBOARD, + .type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, .get_clipboard = { - .copy_key = GET_CLIPBOARD_COPY_KEY_COPY, + .copy_key = SC_COPY_KEY_COPY, }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { - CONTROL_MSG_TYPE_GET_CLIPBOARD, - GET_CLIPBOARD_COPY_KEY_COPY, + SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, + SC_COPY_KEY_COPY, }; assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_set_clipboard(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, + .type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { .sequence = UINT64_C(0x0102030405060708), .paste = true, @@ -238,12 +238,12 @@ static void test_serialize_set_clipboard(void) { }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 27); const unsigned char expected[] = { - CONTROL_MSG_TYPE_SET_CLIPBOARD, + SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence 1, // paste 0x00, 0x00, 0x00, 0x0d, // text length @@ -254,7 +254,7 @@ static void test_serialize_set_clipboard(void) { static void test_serialize_set_clipboard_long(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_SET_CLIPBOARD, + .type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, .set_clipboard = { .sequence = UINT64_C(0x0102030405060708), .paste = true, @@ -262,60 +262,60 @@ static void test_serialize_set_clipboard_long(void) { }, }; - char text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1]; - memset(text, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); - text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0'; + char text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1]; + memset(text, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); + text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0'; msg.set_clipboard.text = text; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); - assert(size == CONTROL_MSG_MAX_SIZE); + assert(size == SC_CONTROL_MSG_MAX_SIZE); - unsigned char expected[CONTROL_MSG_MAX_SIZE] = { - CONTROL_MSG_TYPE_SET_CLIPBOARD, + unsigned char expected[SC_CONTROL_MSG_MAX_SIZE] = { + SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence 1, // paste // text length - CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24, - (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff, - (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff, - CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff, + SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24, + (SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff, + (SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff, + SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff, }; - memset(expected + 14, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); + memset(expected + 14, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_set_screen_power_mode(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, + .type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, .set_screen_power_mode = { - .mode = SCREEN_POWER_MODE_NORMAL, + .mode = SC_SCREEN_POWER_MODE_NORMAL, }, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); const unsigned char expected[] = { - CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, - 0x02, // SCREEN_POWER_MODE_NORMAL + SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, + 0x02, // SC_SCREEN_POWER_MODE_NORMAL }; assert(!memcmp(buf, expected, sizeof(expected))); } static void test_serialize_rotate_device(void) { struct sc_control_msg msg = { - .type = CONTROL_MSG_TYPE_ROTATE_DEVICE, + .type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, }; - unsigned char buf[CONTROL_MSG_MAX_SIZE]; + unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); const unsigned char expected[] = { - CONTROL_MSG_TYPE_ROTATE_DEVICE, + SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, }; assert(!memcmp(buf, expected, sizeof(expected))); } From c8dc1917f47a3f9b4ef6026b912c2792b69e676a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 08:10:04 +0100 Subject: [PATCH 0338/1133] Do not restore power mode if --no-control This totally disables deferred cleanup when --no-control is passed. Refs f289d206ea0e10cc052137781c39da862ebe2f73 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 68c6c02c..8cf289cd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -19,6 +19,7 @@ public final class Server { private static void initAndCleanUp(Options options) { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; + boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled if (options.getShowTouches() || options.getStayAwake()) { Settings settings = Device.getSettings(); if (options.getShowTouches()) { @@ -51,7 +52,8 @@ public final class Server { } try { - CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); + CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, + options.getPowerOffScreenOnClose()); } catch (IOException e) { Ln.e("Could not configure cleanup", e); } From 50f4f1639c552f2195f1b99f67aeac884c0337a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 09:57:24 +0100 Subject: [PATCH 0339/1133] Add a shorcut "open a terminal here" on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, the file explorer does not provide any "open a terminal here" shortcut. Add a bat file which just runs `cmd` to easily open a terminal directly in the scrcpy directory (so there is no need to `cd C:\path\…`). PR #2970 --- FAQ.md | 11 +++++++++-- data/open_a_terminal_here.bat | 1 + release.mk | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 data/open_a_terminal_here.bat diff --git a/FAQ.md b/FAQ.md index 43ba39af..6468b32a 100644 --- a/FAQ.md +++ b/FAQ.md @@ -248,8 +248,15 @@ Caused by: java.lang.IllegalArgumentException: displayToken must not be null ## Command line on Windows -Some Windows users are not familiar with the command line. Here is how to open a -terminal and run `scrcpy` with arguments: +Since v1.22, a "shortcut" has been added to directly open a terminal in the +scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your +command. For example: + +``` +scrcpy --record file.mkv +``` + +You could also open a terminal and go to the scrcpy folder manually: 1. Press Windows+r, this opens a dialog box. 2. Type `cmd` and press Enter, this opens a terminal. diff --git a/data/open_a_terminal_here.bat b/data/open_a_terminal_here.bat new file mode 100644 index 00000000..24d557f3 --- /dev/null +++ b/data/open_a_terminal_here.bat @@ -0,0 +1 @@ +@cmd diff --git a/release.mk b/release.mk index aa26f02d..6b361e5b 100644 --- a/release.mk +++ b/release.mk @@ -93,6 +93,7 @@ dist-win32: build-server build-win32 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 data/open_a_terminal_here.bat "$(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)/" @@ -110,6 +111,7 @@ dist-win64: build-server build-win64 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 data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 8e4d3beb01ba4623acc91deaaed2d10aef19f718 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 12:26:43 +0100 Subject: [PATCH 0340/1133] Fix return value on adb commands error --- app/src/adb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 598b331f..5bc4e4cc 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -425,7 +425,7 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) { } if (r == -1) { - return false; + return NULL; } sc_str_truncate(buf, r, " \r\n"); @@ -455,7 +455,7 @@ adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { } if (r == -1) { - return false; + return NULL; } assert((size_t) r <= sizeof(buf)); From 02b5e87802aa2dce4b8dbacf47dfded55688944b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 15:30:09 +0100 Subject: [PATCH 0341/1133] Slightly reduce lock usage Locking the frame_buffer mutex to reference the input frame into the tmp_frame is unnecessary. This also fixes the missing unlock on error. --- app/src/frame_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/frame_buffer.c b/app/src/frame_buffer.c index d177d4fa..fc5e7084 100644 --- a/app/src/frame_buffer.c +++ b/app/src/frame_buffer.c @@ -51,8 +51,6 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) { bool sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, bool *previous_frame_skipped) { - sc_mutex_lock(&fb->mutex); - // Use a temporary frame to preserve pending_frame in case of error. // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand. int r = av_frame_ref(fb->tmp_frame, frame); @@ -61,6 +59,8 @@ sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, return false; } + sc_mutex_lock(&fb->mutex); + // Now that av_frame_ref() succeeded, we can replace the previous // pending_frame swap_frames(&fb->pending_frame, &fb->tmp_frame); From 4817cadd09aa391746343f9daa2a028a2fa19b6a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 18:59:41 +0100 Subject: [PATCH 0342/1133] Fix code style Align function parameters. --- app/src/frame_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/frame_buffer.c b/app/src/frame_buffer.c index fc5e7084..5699b58f 100644 --- a/app/src/frame_buffer.c +++ b/app/src/frame_buffer.c @@ -50,7 +50,7 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) { bool sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, - bool *previous_frame_skipped) { + bool *previous_frame_skipped) { // Use a temporary frame to preserve pending_frame in case of error. // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand. int r = av_frame_ref(fb->tmp_frame, frame); From 34e19dcc57f35d8f152b1f5b0988800581af15df Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 19:00:03 +0100 Subject: [PATCH 0343/1133] Upgrade SDL (2.0.20) for Windows Include the latest version of SDL in Windows releases. --- cross_win32.txt | 2 +- cross_win64.txt | 2 +- prebuilt-deps/Makefile | 6 +++--- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index 826c044f..9bf974e3 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -20,4 +20,4 @@ ffmpeg_avcodec = 'avcodec-58' ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared' -prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 2e049fdb..763e12e9 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -20,4 +20,4 @@ ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared' -prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 93e8c48d..b40321b6 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -23,9 +23,9 @@ prepare-ffmpeg-win64: ffmpeg-5.0-full_build-shared prepare-sdl2: - @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \ - bbad7c6947f6ca3e05292f065852ed8b62f319fc5533047e7708769c4dbae394 \ - SDL2-2.0.18 + @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.20-mingw.tar.gz \ + 38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 \ + SDL2-2.0.20 prepare-adb: @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ diff --git a/release.mk b/release.mk index 6b361e5b..2f2fe9e1 100644 --- a/release.mk +++ b/release.mk @@ -102,7 +102,7 @@ dist-win32: build-server build-win32 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.18/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -120,7 +120,7 @@ dist-win64: build-server build-win64 cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.18/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From c0de365f672110959cec8fca936bd6aeaa01b59d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 19:01:30 +0100 Subject: [PATCH 0344/1133] Upgrade platform-tools (32.0.0) for Windows Include the latest version of adb in Windows releases. --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index b40321b6..44ea6e1e 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -28,6 +28,6 @@ prepare-sdl2: SDL2-2.0.20 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ - 0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r32.0.0-windows.zip \ + 41f4c7512b32cbb3f8c624c20b56326abb692a6f169b03b4b63b6c5a6fdbb08c \ platform-tools From f51c53e913d4614a677f656d973fa69c496d585a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 17:08:14 +0100 Subject: [PATCH 0345/1133] Mention "screen copy" in README Help users make sense of the app name :) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edf48cfd..9eb4bd21 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ scrcpy +_pronounced "**scr**een **c**o**py**"_ + [Read in another language](#translations) This application provides display and control of Android devices connected via From 86158130051d450a449a2e7bb20b0fcef1b62e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Raiti?= <46955459+Secreto31126@users.noreply.github.com> Date: Sun, 23 Jan 2022 06:37:00 -0300 Subject: [PATCH 0346/1133] Update README.sp.md to v1.21 PR #2962 Signed-off-by: Romain Vimont --- README.md | 2 +- README.sp.md | 369 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 301 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index c4b25f25..db0e2244 100644 --- a/README.md +++ b/README.md @@ -1051,7 +1051,7 @@ This README is available in other languages: - [日本語 (Japanese, `jp`) - v1.19](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) -- [Español (Spanish, `sp`) - v1.17](README.sp.md) +- [Español (Spanish, `sp`) - v1.21](README.sp.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md) diff --git a/README.sp.md b/README.sp.md index 6f76a7be..4ab5ae25 100644 --- a/README.sp.md +++ b/README.sp.md @@ -1,24 +1,36 @@ Solo se garantiza que el archivo [README](README.md) original esté actualizado. -# scrcpy (v1.17) +# scrcpy (v1.21) -Esta aplicación proporciona imagen y control de un dispositivo Android conectado -por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_. +scrcpy + +Esta aplicación proporciona control e imagen de un dispositivo Android conectado +por USB (o [por TCP/IP](#conexión)). No requiere acceso _root_. Compatible con _GNU/Linux_, _Windows_ y _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) -Sus características principales son: - - - **ligero** (nativo, solo muestra la imagen del dispositivo) - - **desempeño** (30~60fps) - - **calidad** (1920×1080 o superior) - - **baja latencia** ([35~70ms][lowlatency]) - - **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen) - - **no intrusivo** (no se deja nada instalado en el dispositivo) +Se enfoca en: + - **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo + - **rendimiento**: 30~120fps, dependiendo del dispositivo + - **calidad**: 1920×1080 o superior + - **baja latencia**: [35~70ms][lowlatency] + - **inicio rápido**: ~1 segundo para mostrar la primera imagen + - **no intrusivo**: no deja nada instalado en el dispositivo + - **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet + - **libertad**: software gratis y de código abierto [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 +Con la aplicación puede: + - [grabar la pantalla](#capturas-y-grabaciones) + - duplicar la imagen con [la pantalla apagada](#apagar-la-pantalla) + - [copiar y pegar](#copiar-y-pegar) en ambos sentidos + - [configurar la calidad](#configuración-de-captura) + - usar la pantalla del dispositivo [como webcam (V4L2)](#v4l2loopback) (solo en Linux) + - [emular un teclado físico (HID)](#emular-teclado-físico-hid) + (solo en Linux) + - y mucho más… ## Requisitos @@ -51,7 +63,7 @@ Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple]) ### Linux -En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04): +En Debian y Ubuntu: ``` apt install scrcpy @@ -125,7 +137,7 @@ Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes: brew install android-platform-tools ``` -También está disponible en [MacPorts], que configurará el adb automáticamente: +También está disponible en [MacPorts], que configura el adb automáticamente: ```bash sudo port install scrcpy @@ -153,7 +165,7 @@ scrcpy --help ## Características -### Capturar configuración +### Configuración de captura #### Reducir la definición @@ -208,10 +220,11 @@ Si `--max-size` también está especificado, el cambio de tamaño es aplicado de Para fijar la rotación de la transmisión: ```bash -scrcpy --lock-video-orientation 0 # orientación normal -scrcpy --lock-video-orientation 1 # 90° contrarreloj -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj +scrcpy --lock-video-orientation # orientación inicial +scrcpy --lock-video-orientation=0 # orientación normal +scrcpy --lock-video-orientation=1 # 90° contrarreloj +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj ``` Esto afecta la rotación de la grabación. @@ -233,7 +246,10 @@ Para listar los codificadores disponibles, puedes pasar un nombre de codificador scrcpy --encoder _ ``` -### Grabación +### Capturas y grabaciones + + +#### Grabación Es posible grabar la pantalla mientras se transmite: @@ -250,17 +266,117 @@ scrcpy -Nr file.mkv # interrumpe la grabación con Ctrl+C ``` -"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay +Los "skipped frames" son grabados, incluso si no se mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay variation]" no impacta el archivo grabado. [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation +#### v4l2loopback + +En Linux se puede mandar el stream del video a un dispositivo loopback v4l2, por +lo que se puede abrir el dispositivo Android como una webcam con cualquier +programa compatible con v4l2. + +Se debe instalar el modulo `v4l2loopback`: + +```bash +sudo apt install v4l2loopback-dkms +``` + +Para crear un dispositivo v4l2: + +```bash +sudo modprobe v4l2loopback +``` + +Esto va a crear un nuevo dispositivo de video en `/dev/videoN`, donde `N` es un número +(hay más [opciones](https://github.com/umlaeute/v4l2loopback#options) disponibles +para crear múltiples dispositivos o usar un ID en específico). + +Para ver los dispositivos disponibles: + +```bash +# requiere el paquete v4l-utils +v4l2-ctl --list-devices +# simple pero generalmente suficiente +ls /dev/video* +``` + +Para iniciar scrcpy usando una fuente v4l2: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # deshabilita la transmisión de imagen +scrcpy --v4l2-sink=/dev/videoN -N # más corto +``` + +(reemplace `N` con el ID del dispositivo, compruebe con `ls /dev/video*`) + +Una vez habilitado, podés abrir el stream del video con una herramienta compatible con v4l2: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC puede agregar un delay por buffering +``` + +Por ejemplo, podrías capturar el video usando [OBS]. + +[OBS]: https://obsproject.com/ + + +#### Buffering + +Es posible agregar buffering al video. Esto reduce el ruido en la imagen ("jitter") +pero aumenta la latencia (vea [#2464]). + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +La opción de buffering está disponible para la transmisión de imagen: + +```bash +scrcpy --display-buffer=50 # agrega 50 ms de buffering a la imagen +``` + +y las fuentes V4L2: + +```bash +scrcpy --v4l2-buffer=500 # agrega 500 ms de buffering a la fuente v4l2 +``` + + ### Conexión -#### Inalámbrica +#### TCP/IP (Inalámbrica) + +_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP. +El dispositivo debe estar conectado a la misma red que la computadora: + +##### Automático + +La opción `--tcpip` permite configurar la conexión automáticamente. Hay 2 variables. + +Si el dispositivo (accesible en 192.168.1.1 para este ejemplo) ya está escuchando +en un puerto (generalmente 5555) esperando una conexión adb entrante, entonces corré: + +```bash +scrcpy --tcpip=192.168.1.1 # el puerto default es 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +Si el dispositivo no tiene habilitado el modo adb TCP/IP (o si no sabés la dirección IP), +entonces conectá el dispositivo por USB y corré: + +```bash +scrcpy --tcpip # sin argumentos +``` + +El programa buscará automáticamente la IP del dispositivo, habilitará el modo TCP/IP, y +se conectará al dispositivo antes de comenzar a transmitir la imagen. -_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP: +##### Manual + +Como alternativa, se puede habilitar la conexión TCP/IP manualmente usando `adb`: 1. Conecta el dispositivo al mismo Wi-Fi que tu computadora. 2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando: @@ -302,7 +418,7 @@ scrcpy -s 192.168.0.1:5555 # versión breve Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos. -#### Autoiniciar al detectar dispositivo +#### Iniciar automáticamente al detectar dispositivo Puedes utilizar [AutoAdb]: @@ -312,37 +428,82 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -#### Túnel SSH +#### Túneles + +Para conectarse a un dispositivo remoto, es posible conectar un cliente local `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_). -Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_): +##### Servidor ADB remoto + +Para conectarse a un servidor ADB remoto, haz que el servidor escuche en todas las interfaces: ```bash -adb kill-server # cierra el servidor local adb en 5037 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +adb kill-server +adb -a nodaemon server start # conserva este servidor abierto ``` -Desde otra terminal: +**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.** + +Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra +terminal, corré scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +Por default, scrcpy usa el puerto local que se usó para establecer el tunel +`adb forward` (típicamente `27183`, vea `--port`). También es posible forzar un +puerto diferente (puede resultar útil en situaciones más complejas, donde haya +múltiples redirecciones): + +``` +scrcpy --tunnel-port=1234 +``` + + +##### Túnel SSH + +Para comunicarse con un servidor ADB remoto de forma segura, es preferible usar un túnel SSH. + +Primero, asegurate que el servidor ADB está corriendo en la computadora remota: + +```bash +adb start-server +``` + +Después, establecé el túnel SSH: + +```bash +# local 5038 --> remoto 5037 +# local 27183 <-- remoto 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer +# conserva este servidor abierto +``` + +Desde otra terminal, corré scrcpy: + +```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy ``` Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`): ```bash -adb kill-server # cierra el servidor local adb en 5037 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# local 5038 --> remoto 5037 +# local 27183 --> remoto 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer # conserva este servidor abierto ``` -Desde otra terminal: +Desde otra terminal, corré scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy --force-adb-forward ``` - Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad: ``` @@ -402,7 +563,7 @@ Se puede rotar la ventana: scrcpy --rotation 1 ``` -Los valores posibles son: +Los posibles valores son: - `0`: sin rotación - `1`: 90 grados contrarreloj - `2`: 180 grados @@ -416,7 +577,7 @@ Nótese que _scrcpy_ maneja 3 diferentes rotaciones: - `--rotation` (o MOD+/MOD+) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación. -### Otras opciones menores +### Otras opciones #### Solo lectura ("Read-only") @@ -479,14 +640,12 @@ scrcpy -Sw # versión breve ``` -#### Renderizar frames vencidos - -Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior. +#### Apagar al cerrar la aplicación -Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use: +Para apagar la pantalla del dispositivo al cerrar scrcpy: ```bash -scrcpy --render-expired-frames +scrcpy --power-off-on-close ``` #### Mostrar clicks @@ -548,6 +707,8 @@ Además, MOD+Shift+v permite inyectar el texto Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de Ctrl+v y MOD+v para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que MOD+Shift+v). +Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`. + #### Pellizcar para zoom Para simular "pinch-to-zoom": Ctrl+_click-y-mover_. @@ -556,6 +717,48 @@ Más precisamente, mantén Ctrl mientras presionas botón izquierdo. Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla. +#### Emular teclado físico (HID) + +Por default, scrcpy usa el sistema de Android para la injección de teclas o texto: +funciona en todas partes, pero está limitado a ASCII. + +En Linux, scrcpy puede emular un teclado USB físico en Android para proveer +una mejor experiencia al enviar _inputs_ (usando [USB HID vía AOAv2][hid-aoav2]): +deshabilita el teclado virtual y funciona para todos los caracteres y IME. + +[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support + +Sin embargo, solo funciona si el dispositivo está conectado por USB, y por ahora +solo funciona en Linux. + +Para habilitar este modo: + +```bash +scrcpy --hid-keyboard +scrcpy -K # más corto +``` + +Si por alguna razón falla (por ejemplo si el dispositivo no está conectado vía +USB), automáticamente vuelve al modo default (un mensaje se escribirá en la consola). +Se puede usar los mismos argumentos en la línea de comandos tanto si se conecta con +USB o vía TCP/IP. + +En este modo, los _raw key events_ (_scancodes_) se envían al dispositivo, independientemente +del mapeo del teclado en el host. Por eso, si el diseño de tu teclado no concuerda, debe ser +configurado en el dispositivo Android, en Ajustes → Sistema → Idioma y Entrada de Texto +→ [Teclado Físico]. + +Se puede iniciar automáticamente en esta página de ajustes: + +```bash +adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS +``` + +Sin embargo, la opción solo está disponible cuando el teclado HID está activo +(o cuando se conecta un teclado físico). + +[Teclado Físico]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 + #### Preferencias de inyección de texto @@ -573,13 +776,23 @@ scrcpy --prefer-text (Pero esto romperá el comportamiento del teclado en los juegos) +Por el contrario, se puede forzar scrcpy para siempre injectar _raw key events_: + +```bash +scrcpy --raw-key-events +``` + +Estas opciones no tienen efecto en los teclados HID (todos los _key events_ son enviados como +_scancodes_ en este modo). + [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 #### Repetir tecla -Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos. +Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede +causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos. Para evitar enviar _key events_ repetidos: @@ -587,6 +800,9 @@ Para evitar enviar _key events_ repetidos: scrcpy --no-key-repeat ``` +Estas opciones no tienen efecto en los teclados HID (Android maneja directamente +las repeticiones de teclas en este modo) + #### Botón derecho y botón del medio @@ -608,14 +824,15 @@ No hay respuesta visual, un mensaje se escribirá en la consola. #### Enviar archivos al dispositivo -Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_. +Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte +un archivo (no APK) a la ventana de _scrcpy_. -No hay respuesta visual, un mensaje se escribirá en la consola. +No hay ninguna respuesta visual, un mensaje se escribirá en la consola. El directorio de destino puede ser modificado al iniciar: ```bash -scrcpy --push-target=/sdcard/Download/ +scrcpy --push-target=/sdcard/Movies/ ``` @@ -647,36 +864,48 @@ _[Super] es generalmente la tecla Windows o Cmd [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - | Acción | Atajo - | ------------------------------------------- |:----------------------------- - | Alterne entre pantalla compelta | MOD+f - | Rotar pantalla hacia la izquierda | MOD+ _(izquierda)_ - | Rotar pantalla hacia la derecha | MOD+ _(derecha)_ - | Ajustar ventana a 1:1 ("pixel-perfect") | MOD+g - | Ajustar ventana para quitar los bordes negros| MOD+w \| _Doble click¹_ - | Click en `INICIO` | MOD+h \| _Botón del medio_ - | Click en `RETROCEDER` | MOD+b \| _Botón derecho²_ - | Click en `CAMBIAR APLICACIÓN` | MOD+s - | Click en `MENÚ` (desbloquear pantalla) | MOD+m - | Click en `SUBIR VOLUMEN` | MOD+ _(arriba)_ - | Click en `BAJAR VOLUME` | MOD+ _(abajo)_ - | Click en `ENCENDIDO` | MOD+p - | Encendido | _Botón derecho²_ - | Apagar pantalla (manteniendo la transmisión)| MOD+o - | Encender pantalla | MOD+Shift+o - | Rotar pantalla del dispositivo | MOD+r - | Abrir panel de notificaciones | MOD+n - | Cerrar panel de notificaciones | MOD+Shift+n - | Copiar al portapapeles³ | MOD+c - | Cortar al portapapeles³ | MOD+x - | Synchronizar portapapeles y pegar³ | MOD+v - | inyectar texto del portapapeles de la PC | MOD+Shift+v + | Acción | Atajo + | ------------------------------------------- |:----------------------------- + | Alterne entre pantalla compelta | MOD+f + | Rotar pantalla hacia la izquierda | MOD+ _(izquierda)_ + | Rotar pantalla hacia la derecha | MOD+ _(derecha)_ + | Ajustar ventana a 1:1 ("pixel-perfect") | MOD+g + | Ajustar ventana para quitar los bordes negros| MOD+w \| _Doble click izquierdo¹_ + | Click en `INICIO` | MOD+h \| _Click medio_ + | Click en `RETROCEDER` | MOD+b \| _Click derecho²_ + | Click en `CAMBIAR APLICACIÓN` | MOD+s \| _Cuarto botón³_ + | Click en `MENÚ` (desbloquear pantalla)⁴ | MOD+m + | Click en `SUBIR VOLUMEN` | MOD+ _(arriba)_ + | Click en `BAJAR VOLUME` | MOD+ _(abajo)_ + | Click en `ENCENDIDO` | MOD+p + | Encendido | _Botón derecho²_ + | Apagar pantalla (manteniendo la transmisión) | MOD+o + | Encender pantalla | MOD+Shift+o + | Rotar pantalla del dispositivo | MOD+r + | Abrir panel de notificaciones | MOD+n \| _Quinto botón³_ + | Abrir panel de configuración | MOD+n+n \| _Doble quinto botón³_ + | Cerrar paneles | MOD+Shift+n + | Copiar al portapapeles⁵ | MOD+c + | Cortar al portapapeles⁵ | MOD+x + | Synchronizar portapapeles y pegar⁵ | MOD+v + | Inyectar texto del portapapeles de la PC | MOD+Shift+v | Habilitar/Deshabilitar contador de FPS (en stdout) | MOD+i - | Pellizcar para zoom | Ctrl+_click-y-mover_ + | Pellizcar para zoom | Ctrl+_click-y-mover_ + | Arrastrar y soltar un archivo (APK) | Instalar APK desde la computadora + | Arrastrar y soltar un archivo (no APK) | [Mover archivo al dispositivo](#enviar-archivos-al-dispositivo) _¹Doble click en los bordes negros para eliminarlos._ _²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._ -_³Solo en Android >= 7._ +_³Cuarto y quinto botón del mouse, si tu mouse los tiene._ +_⁴Para las apps react-native en desarrollo, `MENU` activa el menú de desarrollo._ +_⁵Solo en Android >= 7._ + +Los shortcuts con teclas repetidas se ejecutan soltando y volviendo a apretar la tecla +por segunda vez. Por ejemplo, para ejecutar "Abrir panel de configuración": + + 1. Apretá y mantené apretado MOD. + 2. Después apretá dos veces la tecla n. + 3. Por último, soltá la tecla MOD. Todos los atajos Ctrl+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa. @@ -691,6 +920,8 @@ ADB=/path/to/adb scrcpy Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`. +Para sobreescribir el ícono, configure el path en `SCRCPY_ICON_PATH`. + ## ¿Por qué _scrcpy_? From b546c33eff4daa9a9b69a2c98de39e70fd08109e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 21:12:46 +0100 Subject: [PATCH 0347/1133] Do not print scrcpy version twice on --version Refs 6da6d905c2aeac17255347037d44c32a62c4c504 --- app/src/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index 690e4070..2fdde8e0 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -17,9 +17,7 @@ static void print_version(void) { - fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION); - - fprintf(stderr, "dependencies:\n"); + fprintf(stderr, "\ndependencies:\n"); fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, From 8ea6fb1f0f4e2c06ecf51fa184d08a71663c0e7b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 21:14:33 +0100 Subject: [PATCH 0348/1133] Print version on stdout Refs b25404ee4b6f2cbdd41992fa3e087dd8a73412c9 --- app/src/main.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index 2fdde8e0..cbcef4a7 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -17,22 +17,22 @@ static void print_version(void) { - fprintf(stderr, "\ndependencies:\n"); - fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, - SDL_PATCHLEVEL); - fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, - LIBAVCODEC_VERSION_MINOR, - LIBAVCODEC_VERSION_MICRO); - fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, - LIBAVFORMAT_VERSION_MINOR, - LIBAVFORMAT_VERSION_MICRO); - fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, - LIBAVUTIL_VERSION_MINOR, - LIBAVUTIL_VERSION_MICRO); + printf("\ndependencies:\n"); + printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, + SDL_PATCHLEVEL); + printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, + LIBAVCODEC_VERSION_MINOR, + LIBAVCODEC_VERSION_MICRO); + printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, + LIBAVFORMAT_VERSION_MINOR, + LIBAVFORMAT_VERSION_MICRO); + printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, + LIBAVUTIL_VERSION_MINOR, + LIBAVUTIL_VERSION_MICRO); #ifdef HAVE_V4L2 - fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, - LIBAVDEVICE_VERSION_MINOR, - LIBAVDEVICE_VERSION_MICRO); + printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, + LIBAVDEVICE_VERSION_MINOR, + LIBAVDEVICE_VERSION_MICRO); #endif } From c996a6d462d7499b1421eabcfcadf0a58baae60e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 23:22:43 +0100 Subject: [PATCH 0349/1133] Fix socket close race condition The server needs to interrupt the sockets on stop, but it must not close them while other threads may attempt to read from or write to them. In particular, the video_socket is read by the stream thread, and the control_socket is written by the controller and read by receiver. Therefore, close the socket only on sc_server_destroy(), which is called after all other threads are joined. Reported by TSAN on close: WARNING: ThreadSanitizer: data race (pid=3287612) Write of size 8 at 0x7ba000000080 by thread T1: #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8) #1 net_close ../app/src/util/net.c:280 (scrcpy+0x23643) #2 run_server ../app/src/server.c:772 (scrcpy+0x20047) #3 (libSDL2-2.0.so.0+0x905a0) Previous read of size 8 at 0x7ba000000080 by thread T16: #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6) #1 net_recv_all ../app/src/util/net.c:228 (scrcpy+0x234a9) #2 stream_recv_packet ../app/src/stream.c:33 (scrcpy+0x2045c) #3 run_stream ../app/src/stream.c:228 (scrcpy+0x21169) #4 (libSDL2-2.0.so.0+0x905a0) Refs ddb9396743072f97628fab168ef7fcd45a597b03 --- app/src/server.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index b62a74f1..e5acef95 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -769,12 +769,10 @@ run_server(void *data) { // Interrupt sockets to wake up socket blocking calls on the server assert(server->video_socket != SC_SOCKET_NONE); net_interrupt(server->video_socket); - net_close(server->video_socket); if (server->control_socket != SC_SOCKET_NONE) { // There is no control_socket if --no-control is set net_interrupt(server->control_socket); - net_close(server->control_socket); } // Give some delay for the server to terminate properly @@ -830,6 +828,13 @@ sc_server_stop(struct sc_server *server) { void sc_server_destroy(struct sc_server *server) { + if (server->video_socket != SC_SOCKET_NONE) { + net_close(server->video_socket); + } + if (server->control_socket != SC_SOCKET_NONE) { + net_close(server->control_socket); + } + sc_server_params_destroy(&server->params); sc_intr_destroy(&server->intr); sc_cond_destroy(&server->cond_stopped); From 2762f5d18351d5e610a110d731867bd1c27248cf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 22:27:15 +0100 Subject: [PATCH 0350/1133] Move AOA/HID code to usb/ PR #2974 --- app/meson.build | 6 +++--- app/src/scrcpy.c | 9 +++++---- app/src/{ => usb}/aoa_hid.c | 0 app/src/{ => usb}/aoa_hid.h | 0 app/src/{ => usb}/hid_keyboard.c | 0 app/src/{ => usb}/hid_keyboard.h | 0 app/src/{ => usb}/hid_mouse.c | 0 app/src/{ => usb}/hid_mouse.h | 0 8 files changed, 8 insertions(+), 7 deletions(-) rename app/src/{ => usb}/aoa_hid.c (100%) rename app/src/{ => usb}/aoa_hid.h (100%) rename app/src/{ => usb}/hid_keyboard.c (100%) rename app/src/{ => usb}/hid_keyboard.h (100%) rename app/src/{ => usb}/hid_mouse.c (100%) rename app/src/{ => usb}/hid_mouse.h (100%) diff --git a/app/meson.build b/app/meson.build index 88b8ef8c..60888bb3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -75,9 +75,9 @@ endif aoa_hid_support = host_machine.system() == 'linux' if aoa_hid_support src += [ - 'src/aoa_hid.c', - 'src/hid_keyboard.c', - 'src/hid_mouse.c', + 'src/usb/aoa_hid.c', + 'src/usb/hid_keyboard.c', + 'src/usb/hid_mouse.c', ] endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1534c772..c1e20edb 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -17,16 +17,17 @@ #include "decoder.h" #include "events.h" #include "file_pusher.h" -#ifdef HAVE_AOA_HID -# include "hid_keyboard.h" -# include "hid_mouse.h" -#endif #include "keyboard_inject.h" #include "mouse_inject.h" #include "recorder.h" #include "screen.h" #include "server.h" #include "stream.h" +#ifdef HAVE_AOA_HID +# include "usb/aoa_hid.h" +# include "usb/hid_keyboard.h" +# include "usb/hid_mouse.h" +#endif #include "util/acksync.h" #include "util/log.h" #include "util/net.h" diff --git a/app/src/aoa_hid.c b/app/src/usb/aoa_hid.c similarity index 100% rename from app/src/aoa_hid.c rename to app/src/usb/aoa_hid.c diff --git a/app/src/aoa_hid.h b/app/src/usb/aoa_hid.h similarity index 100% rename from app/src/aoa_hid.h rename to app/src/usb/aoa_hid.h diff --git a/app/src/hid_keyboard.c b/app/src/usb/hid_keyboard.c similarity index 100% rename from app/src/hid_keyboard.c rename to app/src/usb/hid_keyboard.c diff --git a/app/src/hid_keyboard.h b/app/src/usb/hid_keyboard.h similarity index 100% rename from app/src/hid_keyboard.h rename to app/src/usb/hid_keyboard.h diff --git a/app/src/hid_mouse.c b/app/src/usb/hid_mouse.c similarity index 100% rename from app/src/hid_mouse.c rename to app/src/usb/hid_mouse.c diff --git a/app/src/hid_mouse.h b/app/src/usb/hid_mouse.h similarity index 100% rename from app/src/hid_mouse.h rename to app/src/usb/hid_mouse.h From d48d191262957402fa73e94d6eda15afce528a13 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 22:29:07 +0100 Subject: [PATCH 0351/1133] Rename HAVE_AOA_HID to HAVE_USB The condition actually determines whether scrcpy can use libusb or not. PR #2974 --- app/meson.build | 8 ++++---- app/src/cli.c | 4 ++-- app/src/scrcpy.c | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/meson.build b/app/meson.build index 60888bb3..c26332e0 100644 --- a/app/meson.build +++ b/app/meson.build @@ -72,8 +72,8 @@ if v4l2_support src += [ 'src/v4l2_sink.c' ] endif -aoa_hid_support = host_machine.system() == 'linux' -if aoa_hid_support +usb_support = host_machine.system() == 'linux' +if usb_support src += [ 'src/usb/aoa_hid.c', 'src/usb/hid_keyboard.c', @@ -99,7 +99,7 @@ if not crossbuild_windows dependencies += dependency('libavdevice') endif - if aoa_hid_support + if usb_support dependencies += dependency('libusb-1.0') endif @@ -193,7 +193,7 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == ' conf.set('HAVE_V4L2', v4l2_support) # enable HID over AOA support (linux only) -conf.set('HAVE_AOA_HID', aoa_hid_support) +conf.set('HAVE_USB', usb_support) configure_file(configuration: conf, output: 'config.h') diff --git a/app/src/cli.c b/app/src/cli.c index 60492730..34c3103a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1318,7 +1318,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], args->help = true; break; case 'K': -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; break; #else @@ -1337,7 +1337,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } break; case 'M': -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; break; #else diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c1e20edb..9321cf47 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -23,7 +23,7 @@ #include "screen.h" #include "server.h" #include "stream.h" -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/hid_keyboard.h" # include "usb/hid_mouse.h" @@ -46,20 +46,20 @@ struct scrcpy { #endif struct sc_controller controller; struct sc_file_pusher file_pusher; -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB struct sc_aoa aoa; // sequence/ack helper to synchronize clipboard and Ctrl+v via HID struct sc_acksync acksync; #endif union { struct sc_keyboard_inject keyboard_inject; -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB struct sc_hid_keyboard keyboard_hid; #endif }; union { struct sc_mouse_inject mouse_inject; -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB struct sc_hid_mouse mouse_hid; #endif }; @@ -284,7 +284,7 @@ scrcpy(struct scrcpy_options *options) { bool v4l2_sink_initialized = false; #endif bool stream_started = false; -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; bool hid_mouse_initialized = false; @@ -411,7 +411,7 @@ scrcpy(struct scrcpy_options *options) { struct sc_mouse_processor *mp = NULL; if (options->control) { -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB bool use_hid_keyboard = options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; bool use_hid_mouse = @@ -594,7 +594,7 @@ aoa_hid_end: end: // The stream is not stopped explicitly, because it will stop by itself on // end-of-stream -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB if (aoa_hid_initialized) { if (hid_keyboard_initialized) { sc_hid_keyboard_destroy(&s->keyboard_hid); @@ -635,7 +635,7 @@ end: } #endif -#ifdef HAVE_AOA_HID +#ifdef HAVE_USB if (aoa_hid_initialized) { sc_aoa_join(&s->aoa); sc_aoa_destroy(&s->aoa); From 1d6f9952ee290564bce5876183e691dbc7b01055 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 22:56:12 +0100 Subject: [PATCH 0352/1133] Extract USB handling from AOA The AOA code handled both USB initialization and AOA commands/events. Extract USB-related code to a separate file and structure. PR #2974 --- app/meson.build | 1 + app/src/usb/aoa_hid.c | 99 +++---------------------------------- app/src/usb/aoa_hid.h | 5 +- app/src/usb/usb.c | 112 ++++++++++++++++++++++++++++++++++++++++++ app/src/usb/usb.h | 21 ++++++++ 5 files changed, 143 insertions(+), 95 deletions(-) create mode 100644 app/src/usb/usb.c create mode 100644 app/src/usb/usb.h diff --git a/app/meson.build b/app/meson.build index c26332e0..5497281a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -78,6 +78,7 @@ if usb_support 'src/usb/aoa_hid.c', 'src/usb/hid_keyboard.c', 'src/usb/hid_mouse.c', + 'src/usb/usb.c', ] endif diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index a9460c3b..b03808ae 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -50,74 +50,6 @@ log_libusb_error(enum libusb_error errcode) { LOGW("libusb error: %s", libusb_strerror(errcode)); } -static bool -accept_device(libusb_device *device, const char *serial) { - // do not log any USB error in this function, it is expected that many USB - // devices available on the computer have permission restrictions - - struct libusb_device_descriptor desc; - int result = libusb_get_device_descriptor(device, &desc); - if (result < 0 || !desc.iSerialNumber) { - return false; - } - - libusb_device_handle *handle; - result = libusb_open(device, &handle); - if (result < 0) { - return false; - } - - char buffer[128]; - result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, - (unsigned char *) buffer, - sizeof(buffer)); - libusb_close(handle); - if (result < 0) { - return false; - } - - buffer[sizeof(buffer) - 1] = '\0'; // just in case - - // accept the device if its serial matches - return !strcmp(buffer, serial); -} - -static libusb_device * -sc_aoa_find_usb_device(const char *serial) { - if (!serial) { - return NULL; - } - - libusb_device **list; - libusb_device *result = NULL; - ssize_t count = libusb_get_device_list(NULL, &list); - if (count < 0) { - log_libusb_error((enum libusb_error) count); - return NULL; - } - - for (size_t i = 0; i < (size_t) count; ++i) { - libusb_device *device = list[i]; - - if (accept_device(device, serial)) { - result = libusb_ref_device(device); - break; - } - } - libusb_free_device_list(list, 1); - return result; -} - -static int -sc_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); - return result; - } - return 0; -} - bool sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync) { @@ -133,31 +65,16 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial, goto error_destroy_mutex; } - if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) { + bool ok = sc_usb_init(&aoa->usb, serial); + if (!ok) { goto error_destroy_cond; } - aoa->usb_device = sc_aoa_find_usb_device(serial); - if (!aoa->usb_device) { - LOGW("USB device of serial %s not found", serial); - goto error_exit_libusb; - } - - if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) { - LOGW("Open USB handle failed"); - goto error_unref_device; - return false; - } - aoa->stopped = false; aoa->acksync = acksync; return true; -error_unref_device: - libusb_unref_device(aoa->usb_device); -error_exit_libusb: - libusb_exit(aoa->usb_context); error_destroy_cond: sc_cond_destroy(&aoa->event_cond); error_destroy_mutex: @@ -173,9 +90,7 @@ sc_aoa_destroy(struct sc_aoa *aoa) { sc_hid_event_destroy(&event); } - libusb_close(aoa->usb_handle); - libusb_unref_device(aoa->usb_device); - libusb_exit(aoa->usb_context); + sc_usb_destroy(&aoa->usb); sc_cond_destroy(&aoa->event_cond); sc_mutex_destroy(&aoa->mutex); } @@ -192,7 +107,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, uint16_t index = report_desc_size; unsigned char *buffer = NULL; uint16_t length = 0; - int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + int result = libusb_control_transfer(aoa->usb.handle, request_type, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { @@ -228,7 +143,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, // libusb_control_transfer expects a pointer to non-const unsigned char *buffer = (unsigned char *) report_desc; uint16_t length = report_desc_size; - int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + int result = libusb_control_transfer(aoa->usb.handle, request_type, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { @@ -270,7 +185,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { uint16_t index = 0; unsigned char *buffer = event->buffer; uint16_t length = event->size; - int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + int result = libusb_control_transfer(aoa->usb.handle, request_type, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { @@ -292,7 +207,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { uint16_t index = 0; unsigned char *buffer = NULL; uint16_t length = 0; - int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + int result = libusb_control_transfer(aoa->usb.handle, request_type, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index e8fb9708..0ce78626 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -6,6 +6,7 @@ #include +#include "usb.h" #include "util/acksync.h" #include "util/cbuf.h" #include "util/thread.h" @@ -29,9 +30,7 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event); struct sc_hid_event_queue CBUF(struct sc_hid_event, 64); struct sc_aoa { - libusb_context *usb_context; - libusb_device *usb_device; - libusb_device_handle *usb_handle; + struct sc_usb usb; sc_thread thread; sc_mutex mutex; sc_cond event_cond; diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c new file mode 100644 index 00000000..e933fdbc --- /dev/null +++ b/app/src/usb/usb.c @@ -0,0 +1,112 @@ +#include "usb.h" + +#include + +#include "util/log.h" + +static inline void +log_libusb_error(enum libusb_error errcode) { + LOGW("libusb error: %s", libusb_strerror(errcode)); +} + +static bool +accept_device(libusb_device *device, const char *serial) { + // Do not log any USB error in this function, it is expected that many USB + // devices available on the computer have permission restrictions + + struct libusb_device_descriptor desc; + int result = libusb_get_device_descriptor(device, &desc); + if (result < 0 || !desc.iSerialNumber) { + return false; + } + + libusb_device_handle *handle; + result = libusb_open(device, &handle); + if (result < 0) { + return false; + } + + char buffer[128]; + result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, + (unsigned char *) buffer, + sizeof(buffer)); + libusb_close(handle); + if (result < 0) { + return false; + } + + buffer[sizeof(buffer) - 1] = '\0'; // just in case + + // Accept the device if its serial matches + return !strcmp(buffer, serial); +} + +static libusb_device * +sc_usb_find_device(const char *serial) { + if (!serial) { + return NULL; + } + + libusb_device **list; + libusb_device *result = NULL; + ssize_t count = libusb_get_device_list(NULL, &list); + if (count < 0) { + log_libusb_error((enum libusb_error) count); + return NULL; + } + + for (size_t i = 0; i < (size_t) count; ++i) { + libusb_device *device = list[i]; + + if (accept_device(device, serial)) { + result = libusb_ref_device(device); + break; + } + } + + libusb_free_device_list(list, 1); + return result; +} + +static libusb_device_handle * +sc_usb_open_handle(libusb_device *device) { + libusb_device_handle *handle; + int result = libusb_open(device, &handle); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + return NULL; + } + return handle; + } + +bool +sc_usb_init(struct sc_usb *usb, const char *serial) { + // There is only one device, initialize the context here + if (libusb_init(&usb->context) != LIBUSB_SUCCESS) { + return false; + } + + usb->device = sc_usb_find_device(serial); + if (!usb->device) { + LOGW("USB device %s not found", serial); + libusb_exit(usb->context); + return false; + } + + usb->handle = sc_usb_open_handle(usb->device); + if (!usb->handle) { + LOGW("Could not open USB device %s", serial); + libusb_unref_device(usb->device); + libusb_exit(usb->context); + return false; + } + + return true; +} + +void +sc_usb_destroy(struct sc_usb *usb) { + libusb_close(usb->handle); + libusb_unref_device(usb->device); + libusb_exit(usb->context); +} diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h new file mode 100644 index 00000000..5743626d --- /dev/null +++ b/app/src/usb/usb.h @@ -0,0 +1,21 @@ +#ifndef SC_USB_H +#define SC_USB_H + +#include "common.h" + +#include +#include + +struct sc_usb { + libusb_context *context; + libusb_device *device; + libusb_device_handle *handle; +}; + +bool +sc_usb_init(struct sc_usb *usb, const char *serial); + +void +sc_usb_destroy(struct sc_usb *usb); + +#endif From 48e3ff284f538c470e0bf15cc91b8c3430304bf4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 21:38:28 +0100 Subject: [PATCH 0353/1133] Make serial mandatory for sc_usb In practice, it is already mandatory. PR #2974 --- app/src/usb/usb.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index e933fdbc..ebd218dc 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -43,10 +43,6 @@ accept_device(libusb_device *device, const char *serial) { static libusb_device * sc_usb_find_device(const char *serial) { - if (!serial) { - return NULL; - } - libusb_device **list; libusb_device *result = NULL; ssize_t count = libusb_get_device_list(NULL, &list); @@ -81,6 +77,8 @@ sc_usb_open_handle(libusb_device *device) { bool sc_usb_init(struct sc_usb *usb, const char *serial) { + assert(serial); + // There is only one device, initialize the context here if (libusb_init(&usb->context) != LIBUSB_SUCCESS) { return false; From adda47b0f72eb18fa0abc68c67acd435eccdd310 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 23:11:42 +0100 Subject: [PATCH 0354/1133] Move sc_usb out of sc_aoa This will allow to initialize a USB device separately and pass it to sc_aoa. PR #2974 --- app/src/scrcpy.c | 14 +++++++++++++- app/src/usb/aoa_hid.c | 34 ++++++++++++---------------------- app/src/usb/aoa_hid.h | 4 ++-- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9321cf47..0c7966ac 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -27,6 +27,7 @@ # include "usb/aoa_hid.h" # include "usb/hid_keyboard.h" # include "usb/hid_mouse.h" +# include "usb/usb.h" #endif #include "util/acksync.h" #include "util/log.h" @@ -47,6 +48,7 @@ struct scrcpy { struct sc_controller controller; struct sc_file_pusher file_pusher; #ifdef HAVE_USB + struct sc_usb usb; struct sc_aoa aoa; // sequence/ack helper to synchronize clipboard and Ctrl+v via HID struct sc_acksync acksync; @@ -422,9 +424,17 @@ scrcpy(struct scrcpy_options *options) { goto end; } - ok = sc_aoa_init(&s->aoa, serial, &s->acksync); + ok = sc_usb_init(&s->usb, serial); + if (!ok) { + LOGE("Failed to initialized USB device"); + sc_acksync_destroy(&s->acksync); + goto aoa_hid_end; + } + + ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync); if (!ok) { LOGE("Failed to enable HID over AOA"); + sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } @@ -451,6 +461,7 @@ scrcpy(struct scrcpy_options *options) { if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); + sc_usb_destroy(&s->usb); sc_aoa_destroy(&s->aoa); goto aoa_hid_end; } @@ -639,6 +650,7 @@ end: if (aoa_hid_initialized) { sc_aoa_join(&s->aoa); sc_aoa_destroy(&s->aoa); + sc_usb_destroy(&s->usb); } #endif diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index b03808ae..0925d9e4 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -51,7 +51,7 @@ log_libusb_error(enum libusb_error errcode) { } bool -sc_aoa_init(struct sc_aoa *aoa, const char *serial, +sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { assert(acksync); @@ -62,24 +62,15 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial, } if (!sc_cond_init(&aoa->event_cond)) { - goto error_destroy_mutex; - } - - bool ok = sc_usb_init(&aoa->usb, serial); - if (!ok) { - goto error_destroy_cond; + sc_mutex_destroy(&aoa->mutex); + return false; } aoa->stopped = false; aoa->acksync = acksync; + aoa->usb = usb; return true; - -error_destroy_cond: - sc_cond_destroy(&aoa->event_cond); -error_destroy_mutex: - sc_mutex_destroy(&aoa->mutex); - return false; } void @@ -90,7 +81,6 @@ sc_aoa_destroy(struct sc_aoa *aoa) { sc_hid_event_destroy(&event); } - sc_usb_destroy(&aoa->usb); sc_cond_destroy(&aoa->event_cond); sc_mutex_destroy(&aoa->mutex); } @@ -107,8 +97,8 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, uint16_t index = report_desc_size; unsigned char *buffer = NULL; uint16_t length = 0; - int result = libusb_control_transfer(aoa->usb.handle, request_type, request, - value, index, buffer, length, + int result = libusb_control_transfer(aoa->usb->handle, request_type, + request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { log_libusb_error((enum libusb_error) result); @@ -143,8 +133,8 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, // libusb_control_transfer expects a pointer to non-const unsigned char *buffer = (unsigned char *) report_desc; uint16_t length = report_desc_size; - int result = libusb_control_transfer(aoa->usb.handle, request_type, request, - value, index, buffer, length, + int result = libusb_control_transfer(aoa->usb->handle, request_type, + request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { log_libusb_error((enum libusb_error) result); @@ -185,8 +175,8 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { uint16_t index = 0; unsigned char *buffer = event->buffer; uint16_t length = event->size; - int result = libusb_control_transfer(aoa->usb.handle, request_type, request, - value, index, buffer, length, + int result = libusb_control_transfer(aoa->usb->handle, request_type, + request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { log_libusb_error((enum libusb_error) result); @@ -207,8 +197,8 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { uint16_t index = 0; unsigned char *buffer = NULL; uint16_t length = 0; - int result = libusb_control_transfer(aoa->usb.handle, request_type, request, - value, index, buffer, length, + int result = libusb_control_transfer(aoa->usb->handle, request_type, + request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { log_libusb_error((enum libusb_error) result); diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 0ce78626..d785a0e9 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -30,7 +30,7 @@ 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 sc_usb usb; + struct sc_usb *usb; sc_thread thread; sc_mutex mutex; sc_cond event_cond; @@ -41,7 +41,7 @@ struct sc_aoa { }; bool -sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync); +sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync); void sc_aoa_destroy(struct sc_aoa *aoa); From b779eca8d37896072ddc5017f1c63a3056515869 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 21:40:46 +0100 Subject: [PATCH 0355/1133] Remove libusb_device field It is possible to retrieve the device instance from the handle via libusb_get_device(), so we don't need to reference the device one more time. PR #2974 --- app/src/usb/usb.c | 9 ++++----- app/src/usb/usb.h | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index ebd218dc..44d1d489 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -84,17 +84,17 @@ sc_usb_init(struct sc_usb *usb, const char *serial) { return false; } - usb->device = sc_usb_find_device(serial); - if (!usb->device) { + libusb_device *device = sc_usb_find_device(serial); + if (!device) { LOGW("USB device %s not found", serial); libusb_exit(usb->context); return false; } - usb->handle = sc_usb_open_handle(usb->device); + usb->handle = sc_usb_open_handle(device); + libusb_unref_device(device); if (!usb->handle) { LOGW("Could not open USB device %s", serial); - libusb_unref_device(usb->device); libusb_exit(usb->context); return false; } @@ -105,6 +105,5 @@ sc_usb_init(struct sc_usb *usb, const char *serial) { void sc_usb_destroy(struct sc_usb *usb) { libusb_close(usb->handle); - libusb_unref_device(usb->device); libusb_exit(usb->context); } diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index 5743626d..8ee3eb9f 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -8,7 +8,6 @@ struct sc_usb { libusb_context *context; - libusb_device *device; libusb_device_handle *handle; }; From 2114f48185ff69e8b2849c75d4b77c9352a3b6f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 21:42:26 +0100 Subject: [PATCH 0356/1133] Find device with USB context An explicit context was used everywhere except for listing the devices. PR #2974 --- app/src/usb/usb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 44d1d489..321d745c 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -42,10 +42,10 @@ accept_device(libusb_device *device, const char *serial) { } static libusb_device * -sc_usb_find_device(const char *serial) { +sc_usb_find_device(struct sc_usb *usb, const char *serial) { libusb_device **list; libusb_device *result = NULL; - ssize_t count = libusb_get_device_list(NULL, &list); + ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { log_libusb_error((enum libusb_error) count); return NULL; @@ -84,7 +84,7 @@ sc_usb_init(struct sc_usb *usb, const char *serial) { return false; } - libusb_device *device = sc_usb_find_device(serial); + libusb_device *device = sc_usb_find_device(usb, serial); if (!device) { LOGW("USB device %s not found", serial); libusb_exit(usb->context); From bbef426a4bb69dc96fbd8c1075c0e569d71fdff4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 25 Jan 2022 19:10:23 +0100 Subject: [PATCH 0357/1133] Split USB initialization and connection This will allow to execute other USB calls (retrieving the device list for example) before connecting to the selected device. PR #2974 --- app/src/scrcpy.c | 15 +++++++++++++-- app/src/usb/usb.c | 23 +++++++++++++---------- app/src/usb/usb.h | 8 +++++++- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0c7966ac..3de683db 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -424,9 +424,17 @@ scrcpy(struct scrcpy_options *options) { goto end; } - ok = sc_usb_init(&s->usb, serial); + ok = sc_usb_init(&s->usb); if (!ok) { - LOGE("Failed to initialized USB device"); + LOGE("Failed to initialize USB"); + sc_acksync_destroy(&s->acksync); + goto aoa_hid_end; + } + + ok = sc_usb_connect(&s->usb, serial); + if (!ok) { + LOGE("Failed to connect to USB device %s", serial); + sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } @@ -434,6 +442,7 @@ scrcpy(struct scrcpy_options *options) { ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync); if (!ok) { LOGE("Failed to enable HID over AOA"); + sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; @@ -461,6 +470,7 @@ scrcpy(struct scrcpy_options *options) { if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); + sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); sc_aoa_destroy(&s->aoa); goto aoa_hid_end; @@ -650,6 +660,7 @@ end: if (aoa_hid_initialized) { sc_aoa_join(&s->aoa); sc_aoa_destroy(&s->aoa); + sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); } #endif diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 321d745c..64f30353 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -76,18 +76,23 @@ sc_usb_open_handle(libusb_device *device) { } bool -sc_usb_init(struct sc_usb *usb, const char *serial) { - assert(serial); +sc_usb_init(struct sc_usb *usb) { + usb->handle = NULL; + return libusb_init(&usb->context) == LIBUSB_SUCCESS; +} - // There is only one device, initialize the context here - if (libusb_init(&usb->context) != LIBUSB_SUCCESS) { - return false; - } +void +sc_usb_destroy(struct sc_usb *usb) { + libusb_exit(usb->context); +} + +bool +sc_usb_connect(struct sc_usb *usb, const char *serial) { + assert(serial); libusb_device *device = sc_usb_find_device(usb, serial); if (!device) { LOGW("USB device %s not found", serial); - libusb_exit(usb->context); return false; } @@ -95,7 +100,6 @@ sc_usb_init(struct sc_usb *usb, const char *serial) { libusb_unref_device(device); if (!usb->handle) { LOGW("Could not open USB device %s", serial); - libusb_exit(usb->context); return false; } @@ -103,7 +107,6 @@ sc_usb_init(struct sc_usb *usb, const char *serial) { } void -sc_usb_destroy(struct sc_usb *usb) { +sc_usb_disconnect(struct sc_usb *usb) { libusb_close(usb->handle); - libusb_exit(usb->context); } diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index 8ee3eb9f..e487a32c 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -12,9 +12,15 @@ struct sc_usb { }; bool -sc_usb_init(struct sc_usb *usb, const char *serial); +sc_usb_init(struct sc_usb *usb); void sc_usb_destroy(struct sc_usb *usb); +bool +sc_usb_connect(struct sc_usb *usb, const char *serial); + +void +sc_usb_disconnect(struct sc_usb *usb); + #endif From 1ab3692f3dec0f28e2f228d604a037f1a801d337 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 25 Jan 2022 19:40:51 +0100 Subject: [PATCH 0358/1133] Add util function to read USB descriptor string Use it from accept_device() to simplify (at the cost an additional allocation for each serial, but it is not important). It will also be useful in other functions in further commits. PR #2974 --- app/src/usb/usb.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 64f30353..e2bbaee1 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -9,6 +9,26 @@ log_libusb_error(enum libusb_error errcode) { LOGW("libusb error: %s", libusb_strerror(errcode)); } +static char * +read_string(libusb_device_handle *handle, uint8_t desc_index) { + char buffer[128]; + int result = + libusb_get_string_descriptor_ascii(handle, desc_index, + (unsigned char *) buffer, + sizeof(buffer)); + if (result < 0) { + return NULL; + } + + assert((size_t) result <= sizeof(buffer)); + + // When non-negative, 'result' contains the number of bytes written + char *s = malloc(result + 1); + memcpy(s, buffer, result); + s[result] = '\0'; + return s; +} + static bool accept_device(libusb_device *device, const char *serial) { // Do not log any USB error in this function, it is expected that many USB @@ -26,19 +46,15 @@ accept_device(libusb_device *device, const char *serial) { return false; } - char buffer[128]; - result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, - (unsigned char *) buffer, - sizeof(buffer)); + char *device_serial = read_string(handle, desc.iSerialNumber); libusb_close(handle); - if (result < 0) { + if (!device_serial) { return false; } - buffer[sizeof(buffer) - 1] = '\0'; // just in case - - // Accept the device if its serial matches - return !strcmp(buffer, serial); + bool matches = !strcmp(serial, device_serial); + free(device_serial); + return matches; } static libusb_device * From 0ee9e2ff51baa4826283b521053a383550b93ea1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 25 Jan 2022 21:11:32 +0100 Subject: [PATCH 0359/1133] Expose function to find a USB device The device was automatically found by sc_usb_connect(). Instead, expose a function to find a device from a serial, and let the caller connect to the device found (if any). This will allow to list all devices first, then select one device to connect to. PR #2974 --- app/src/scrcpy.c | 12 +++++++++++- app/src/usb/usb.c | 16 ++++------------ app/src/usb/usb.h | 5 ++++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3de683db..9039eede 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -431,7 +431,17 @@ scrcpy(struct scrcpy_options *options) { goto aoa_hid_end; } - ok = sc_usb_connect(&s->usb, serial); + assert(serial); + libusb_device *device = sc_usb_find_device(&s->usb, serial); + if (!device) { + LOGE("Could not find USB device %s", serial); + sc_usb_destroy(&s->usb); + sc_acksync_destroy(&s->acksync); + goto aoa_hid_end; + } + + ok = sc_usb_connect(&s->usb, device); + libusb_unref_device(device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); sc_usb_destroy(&s->usb); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index e2bbaee1..459aefc0 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -57,8 +57,10 @@ accept_device(libusb_device *device, const char *serial) { return matches; } -static libusb_device * +libusb_device * sc_usb_find_device(struct sc_usb *usb, const char *serial) { + assert(serial); + libusb_device **list; libusb_device *result = NULL; ssize_t count = libusb_get_device_list(usb->context, &list); @@ -103,19 +105,9 @@ sc_usb_destroy(struct sc_usb *usb) { } bool -sc_usb_connect(struct sc_usb *usb, const char *serial) { - assert(serial); - - libusb_device *device = sc_usb_find_device(usb, serial); - if (!device) { - LOGW("USB device %s not found", serial); - return false; - } - +sc_usb_connect(struct sc_usb *usb, libusb_device *device) { usb->handle = sc_usb_open_handle(device); - libusb_unref_device(device); if (!usb->handle) { - LOGW("Could not open USB device %s", serial); return false; } diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index e487a32c..0ab7ce90 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -17,8 +17,11 @@ sc_usb_init(struct sc_usb *usb); void sc_usb_destroy(struct sc_usb *usb); +libusb_device * +sc_usb_find_device(struct sc_usb *usb, const char *serial); + bool -sc_usb_connect(struct sc_usb *usb, const char *serial); +sc_usb_connect(struct sc_usb *usb, libusb_device *device); void sc_usb_disconnect(struct sc_usb *usb); From d8b37fe189d5cacf9bac8162d25c58c1a6890a6c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 22:02:24 +0100 Subject: [PATCH 0360/1133] Wrap libusb_device Introduce a structure to wrap a libusb_device and expose its descriptor data read during discovery. PR #2974 --- app/src/scrcpy.c | 13 +++++++++---- app/src/usb/usb.c | 47 +++++++++++++++++++++++++++++++++++------------ app/src/usb/usb.h | 17 +++++++++++++++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9039eede..7d58b0f1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -432,16 +432,21 @@ scrcpy(struct scrcpy_options *options) { } assert(serial); - libusb_device *device = sc_usb_find_device(&s->usb, serial); - if (!device) { + struct sc_usb_device usb_device; + ok = sc_usb_find_device(&s->usb, serial, &usb_device); + if (!ok) { LOGE("Could not find USB device %s", serial); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } - ok = sc_usb_connect(&s->usb, device); - libusb_unref_device(device); + LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", + usb_device.serial, usb_device.vid, usb_device.pid, + usb_device.manufacturer, usb_device.product); + + ok = sc_usb_connect(&s->usb, usb_device.device); + sc_usb_device_destroy(&usb_device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); sc_usb_destroy(&s->usb); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 459aefc0..9d5f35be 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -30,7 +30,8 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) { } static bool -accept_device(libusb_device *device, const char *serial) { +accept_device(libusb_device *device, const char *serial, + struct sc_usb_device *out) { // Do not log any USB error in this function, it is expected that many USB // devices available on the computer have permission restrictions @@ -47,39 +48,61 @@ accept_device(libusb_device *device, const char *serial) { } char *device_serial = read_string(handle, desc.iSerialNumber); - libusb_close(handle); if (!device_serial) { + libusb_close(handle); return false; } bool matches = !strcmp(serial, device_serial); - free(device_serial); - return matches; + if (!matches) { + free(device_serial); + libusb_close(handle); + return false; + } + + out->device = libusb_ref_device(device); + out->serial = device_serial; + out->vid = desc.idVendor; + out->pid = desc.idProduct; + out->manufacturer = read_string(handle, desc.iManufacturer); + out->product = read_string(handle, desc.iProduct); + + libusb_close(handle); + + return true; +} + +void +sc_usb_device_destroy(struct sc_usb_device *usb_device) { + libusb_unref_device(usb_device->device); + free(usb_device->serial); + free(usb_device->manufacturer); + free(usb_device->product); } -libusb_device * -sc_usb_find_device(struct sc_usb *usb, const char *serial) { +bool +sc_usb_find_device(struct sc_usb *usb, const char *serial, + struct sc_usb_device *out) { assert(serial); libusb_device **list; - libusb_device *result = NULL; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { log_libusb_error((enum libusb_error) count); - return NULL; + return false; } for (size_t i = 0; i < (size_t) count; ++i) { libusb_device *device = list[i]; - if (accept_device(device, serial)) { - result = libusb_ref_device(device); - break; + if (accept_device(device, serial, out)) { + libusb_free_device_list(list, 1); + return true; } } libusb_free_device_list(list, 1); - return result; + return false; } static libusb_device_handle * diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index 0ab7ce90..ea6e5514 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -11,14 +11,27 @@ struct sc_usb { libusb_device_handle *handle; }; +struct sc_usb_device { + libusb_device *device; + char *serial; + char *manufacturer; + char *product; + uint16_t vid; + uint16_t pid; +}; + +void +sc_usb_device_destroy(struct sc_usb_device *usb_device); + bool sc_usb_init(struct sc_usb *usb); void sc_usb_destroy(struct sc_usb *usb); -libusb_device * -sc_usb_find_device(struct sc_usb *usb, const char *serial); +bool +sc_usb_find_device(struct sc_usb *usb, const char *serial, + struct sc_usb_device *out); bool sc_usb_connect(struct sc_usb *usb, libusb_device *device); From 1c17f57c101fc08fd54c99b5c6c947414f5f35be Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 22:08:55 +0100 Subject: [PATCH 0361/1133] Find a list of devices instead of a single one Several devices may match the requested serial, but above all, this paves the way to list all devices (when no serial is provided). PR #2974 --- app/src/scrcpy.c | 25 ++++++++++++++++++------- app/src/usb/usb.c | 25 ++++++++++++++++--------- app/src/usb/usb.h | 9 ++++++--- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7d58b0f1..1c84b428 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -432,21 +432,32 @@ scrcpy(struct scrcpy_options *options) { } assert(serial); - struct sc_usb_device usb_device; - ok = sc_usb_find_device(&s->usb, serial, &usb_device); - if (!ok) { + struct sc_usb_device usb_devices[16]; + ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, + ARRAY_LEN(usb_devices)); + if (count <= 0) { LOGE("Could not find USB device %s", serial); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } + if (count > 1) { + LOGE("Multiple (%d) devices with serial %s", (int) count, serial); + sc_usb_device_destroy_all(usb_devices, count); + sc_usb_destroy(&s->usb); + sc_acksync_destroy(&s->acksync); + goto aoa_hid_end; + } + + struct sc_usb_device *usb_device = &usb_devices[0]; + LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - usb_device.serial, usb_device.vid, usb_device.pid, - usb_device.manufacturer, usb_device.product); + usb_device->serial, usb_device->vid, usb_device->pid, + usb_device->manufacturer, usb_device->product); - ok = sc_usb_connect(&s->usb, usb_device.device); - sc_usb_device_destroy(&usb_device); + ok = sc_usb_connect(&s->usb, usb_device->device); + sc_usb_device_destroy(usb_device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); sc_usb_destroy(&s->usb); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 9d5f35be..6add7b2e 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -80,29 +80,36 @@ sc_usb_device_destroy(struct sc_usb_device *usb_device) { free(usb_device->product); } -bool -sc_usb_find_device(struct sc_usb *usb, const char *serial, - struct sc_usb_device *out) { +void +sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) { + for (size_t i = 0; i < count; ++i) { + sc_usb_device_destroy(&usb_devices[i]); + } +} + +ssize_t +sc_usb_find_devices(struct sc_usb *usb, const char *serial, + struct sc_usb_device *devices, size_t len) { assert(serial); libusb_device **list; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { log_libusb_error((enum libusb_error) count); - return false; + return -1; } - for (size_t i = 0; i < (size_t) count; ++i) { + size_t idx = 0; + for (size_t i = 0; i < (size_t) count && idx < len; ++i) { libusb_device *device = list[i]; - if (accept_device(device, serial, out)) { - libusb_free_device_list(list, 1); - return true; + if (accept_device(device, serial, &devices[idx])) { + ++idx; } } libusb_free_device_list(list, 1); - return false; + return idx; } static libusb_device_handle * diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index ea6e5514..a271d034 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -23,15 +23,18 @@ struct sc_usb_device { void sc_usb_device_destroy(struct sc_usb_device *usb_device); +void +sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count); + bool sc_usb_init(struct sc_usb *usb); void sc_usb_destroy(struct sc_usb *usb); -bool -sc_usb_find_device(struct sc_usb *usb, const char *serial, - struct sc_usb_device *out); +ssize_t +sc_usb_find_devices(struct sc_usb *usb, const char *serial, + struct sc_usb_device *devices, size_t len); bool sc_usb_connect(struct sc_usb *usb, libusb_device *device); From 8fc9dca8cb4d35fb329826fffa00e1403eb7ed43 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 22:15:23 +0100 Subject: [PATCH 0362/1133] Make serial optional to find USB devices If no serial is provided, then list all available USB devices (which can be open and having a serial). PR #2974 --- app/src/usb/usb.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 6add7b2e..8140b674 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -53,11 +53,14 @@ accept_device(libusb_device *device, const char *serial, return false; } - bool matches = !strcmp(serial, device_serial); - if (!matches) { - free(device_serial); - libusb_close(handle); - return false; + if (serial) { + // Filter by serial + bool matches = !strcmp(serial, device_serial); + if (!matches) { + free(device_serial); + libusb_close(handle); + return false; + } } out->device = libusb_ref_device(device); @@ -90,8 +93,6 @@ sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) { ssize_t sc_usb_find_devices(struct sc_usb *usb, const char *serial, struct sc_usb_device *devices, size_t len) { - assert(serial); - libusb_device **list; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { From 37987b822e8f0f57f27c867ef6c573f98791b5bd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 22 Jan 2022 10:52:13 +0100 Subject: [PATCH 0363/1133] Make acksync optional for AOA initialization Acksync is used to delay HID events until some request (in practice, device clipboard synchronization) is acknowledged by the device. This mechanism will not be necessary for OTG mode. PR #2974 --- app/src/usb/aoa_hid.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 0925d9e4..6e3bd2e7 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -53,8 +53,6 @@ log_libusb_error(enum libusb_error errcode) { bool sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { - assert(acksync); - cbuf_init(&aoa->queue); if (!sc_mutex_init(&aoa->mutex)) { @@ -248,6 +246,11 @@ run_aoa_thread(void *data) { if (ack_to_wait != SC_SEQUENCE_INVALID) { LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); + + // If some events have ack_to_wait set, then sc_aoa must have been + // initialized with a non NULL acksync + assert(aoa->acksync); + // Do not block the loop indefinitely if the ack never comes (it // should never happen) sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500); @@ -294,7 +297,9 @@ sc_aoa_stop(struct sc_aoa *aoa) { sc_cond_signal(&aoa->event_cond); sc_mutex_unlock(&aoa->mutex); - sc_acksync_interrupt(aoa->acksync); + if (aoa->acksync) { + sc_acksync_interrupt(aoa->acksync); + } } void From 1a03206e36b6065b421b3e7635a48211cdf0ce4a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jan 2022 20:02:05 +0100 Subject: [PATCH 0364/1133] Detect USB device disconnection The device disconnection is detected when the video socket closes. In order to introduce an OTG mode (HID events) without mirroring (and without server), we must be able to detect USB device disconnection. This feature will only be used in OTG mode. PR #2974 --- app/src/scrcpy.c | 4 +- app/src/usb/usb.c | 115 +++++++++++++++++++++++++++++++++++++++++++++- app/src/usb/usb.h | 26 ++++++++++- 3 files changed, 142 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1c84b428..5f67f7f8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -456,7 +456,7 @@ scrcpy(struct scrcpy_options *options) { usb_device->serial, usb_device->vid, usb_device->pid, usb_device->manufacturer, usb_device->product); - ok = sc_usb_connect(&s->usb, usb_device->device); + ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL); sc_usb_device_destroy(usb_device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); @@ -650,6 +650,7 @@ end: sc_hid_mouse_destroy(&s->mouse_hid); } sc_aoa_stop(&s->aoa); + sc_usb_stop(&s->usb); } if (acksync) { sc_acksync_destroy(acksync); @@ -686,6 +687,7 @@ end: if (aoa_hid_initialized) { sc_aoa_join(&s->aoa); sc_aoa_destroy(&s->aoa); + sc_usb_join(&s->usb); sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); } diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 8140b674..729df7f0 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -135,13 +135,111 @@ sc_usb_destroy(struct sc_usb *usb) { libusb_exit(usb->context); } +static int +sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, + libusb_hotplug_event event, void *userdata) { + (void) ctx; + (void) device; + (void) event; + + struct sc_usb *usb = userdata; + + libusb_device *dev = libusb_get_device(usb->handle); + assert(dev); + if (dev != device) { + // Not the connected device + return 0; + } + + assert(usb->cbs && usb->cbs->on_disconnected); + usb->cbs->on_disconnected(usb, usb->cbs_userdata); + + // Do not automatically deregister the callback by returning 1. Instead, + // manually deregister to interrupt libusb_handle_events() from the libusb + // event thread: + return 0; +} + +static int +run_libusb_event_handler(void *data) { + struct sc_usb *usb = data; + while (!atomic_load(&usb->stopped)) { + // Interrupted by events or by libusb_hotplug_deregister_callback() + libusb_handle_events(usb->context); + } + return 0; +} + +static bool +sc_usb_register_callback(struct sc_usb *usb) { + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + LOGW("libusb does not have hotplug capability"); + return false; + } + + libusb_device *device = libusb_get_device(usb->handle); + assert(device); + + struct libusb_device_descriptor desc; + int result = libusb_get_device_descriptor(device, &desc); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + LOGW("Could not read USB device descriptor"); + return false; + } + + int events = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT; + int flags = LIBUSB_HOTPLUG_NO_FLAGS; + int vendor_id = desc.idVendor; + int product_id = desc.idProduct; + int dev_class = LIBUSB_HOTPLUG_MATCH_ANY; + result = libusb_hotplug_register_callback(usb->context, events, flags, + vendor_id, product_id, dev_class, + sc_usb_libusb_callback, usb, + &usb->callback_handle); + if (result < 0) { + log_libusb_error((enum libusb_error) result); + LOGW("Could not register USB callback"); + return false; + } + + usb->has_callback_handle = true; + return true; +} + bool -sc_usb_connect(struct sc_usb *usb, libusb_device *device) { +sc_usb_connect(struct sc_usb *usb, libusb_device *device, + const struct sc_usb_callbacks *cbs, void *cbs_userdata) { usb->handle = sc_usb_open_handle(device); if (!usb->handle) { return false; } + usb->has_callback_handle = false; + usb->has_libusb_event_thread = false; + + // If cbs is set, then cbs->on_disconnected must be set + assert(!cbs || cbs->on_disconnected); + usb->cbs = cbs; + usb->cbs_userdata = cbs_userdata; + + if (cbs) { + atomic_init(&usb->stopped, false); + if (sc_usb_register_callback(usb)) { + // Create a thread to process libusb events, so that device + // disconnection could be detected immediately + usb->has_libusb_event_thread = + sc_thread_create(&usb->libusb_event_thread, + run_libusb_event_handler, "scrcpy-usbev", usb); + if (!usb->has_libusb_event_thread) { + LOGW("Libusb event thread handler could not be created, USB " + "device disconnection might not be detected immediately"); + } + } else { + LOGW("Could not register USB device disconnection callback"); + } + } + return true; } @@ -149,3 +247,18 @@ void sc_usb_disconnect(struct sc_usb *usb) { libusb_close(usb->handle); } + +void +sc_usb_stop(struct sc_usb *usb) { + if (usb->has_callback_handle) { + atomic_store(&usb->stopped, true); + libusb_hotplug_deregister_callback(usb->context, usb->callback_handle); + } +} + +void +sc_usb_join(struct sc_usb *usb) { + if (usb->has_libusb_event_thread) { + sc_thread_join(&usb->libusb_event_thread, NULL); + } +} diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index a271d034..eda7c2f9 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -6,9 +6,26 @@ #include #include +#include "util/thread.h" + struct sc_usb { libusb_context *context; libusb_device_handle *handle; + + const struct sc_usb_callbacks *cbs; + void *cbs_userdata; + + bool has_callback_handle; + libusb_hotplug_callback_handle callback_handle; + + bool has_libusb_event_thread; + sc_thread libusb_event_thread; + + atomic_bool stopped; // only used if cbs != NULL +}; + +struct sc_usb_callbacks { + void (*on_disconnected)(struct sc_usb *usb, void *userdata); }; struct sc_usb_device { @@ -37,9 +54,16 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, struct sc_usb_device *devices, size_t len); bool -sc_usb_connect(struct sc_usb *usb, libusb_device *device); +sc_usb_connect(struct sc_usb *usb, libusb_device *device, + const struct sc_usb_callbacks *cbs, void *cbs_userdata); void sc_usb_disconnect(struct sc_usb *usb); +void +sc_usb_stop(struct sc_usb *usb); + +void +sc_usb_join(struct sc_usb *usb); + #endif From 36aaf702791b77d65b7b1bf87ee58a808cdf70d0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Jan 2022 15:35:46 +0100 Subject: [PATCH 0365/1133] Move input event helpers Input events helpers to convert from SDL events to scrcpy events were implemented in input_manager. To reuse them for OTG mode, move them to input_events.h. PR #2974 --- app/src/input_events.h | 72 +++++++++++++++++++++++++++++++++++++++++ app/src/input_manager.c | 72 ----------------------------------------- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/app/src/input_events.h b/app/src/input_events.h index 4a4cf356..9bf3c421 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -377,4 +377,76 @@ struct sc_touch_event { float pressure; }; +static inline uint16_t +sc_mods_state_from_sdl(uint16_t mods_state) { + return mods_state; +} + +static inline enum sc_keycode +sc_keycode_from_sdl(SDL_Keycode keycode) { + return (enum sc_keycode) keycode; +} + +static inline enum sc_scancode +sc_scancode_from_sdl(SDL_Scancode scancode) { + return (enum sc_scancode) scancode; +} + +static inline enum sc_action +sc_action_from_sdl_keyboard_type(uint32_t type) { + assert(type == SDL_KEYDOWN || type == SDL_KEYUP); + if (type == SDL_KEYDOWN) { + return SC_ACTION_DOWN; + } + return SC_ACTION_UP; +} + +static inline enum sc_action +sc_action_from_sdl_mousebutton_type(uint32_t type) { + assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP); + if (type == SDL_MOUSEBUTTONDOWN) { + return SC_ACTION_DOWN; + } + return SC_ACTION_UP; +} + +static inline enum sc_touch_action +sc_touch_action_from_sdl(uint32_t type) { + assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN || + type == SDL_FINGERUP); + if (type == SDL_FINGERMOTION) { + return SC_TOUCH_ACTION_MOVE; + } + if (type == SDL_FINGERDOWN) { + return SC_TOUCH_ACTION_DOWN; + } + return SC_TOUCH_ACTION_UP; +} + +static inline enum sc_mouse_button +sc_mouse_button_from_sdl(uint8_t button) { + if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) { + // SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index) + return SDL_BUTTON(button); + } + + return SC_MOUSE_BUTTON_UNKNOWN; +} + +static inline uint8_t +sc_mouse_buttons_state_from_sdl(uint32_t buttons_state, + bool forward_all_clicks) { + assert(buttons_state < 0x100); // fits in uint8_t + + uint8_t mask = SC_MOUSE_BUTTON_LEFT; + if (forward_all_clicks) { + mask |= SC_MOUSE_BUTTON_RIGHT + | SC_MOUSE_BUTTON_MIDDLE + | SC_MOUSE_BUTTON_X1 + | SC_MOUSE_BUTTON_X2; + } + + return buttons_state & mask; +} + #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index d623c5ae..63842d35 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -7,78 +7,6 @@ #include "screen.h" #include "util/log.h" -static inline uint16_t -sc_mods_state_from_sdl(uint16_t mods_state) { - return mods_state; -} - -static inline enum sc_keycode -sc_keycode_from_sdl(SDL_Keycode keycode) { - return (enum sc_keycode) keycode; -} - -static inline enum sc_scancode -sc_scancode_from_sdl(SDL_Scancode scancode) { - return (enum sc_scancode) scancode; -} - -static inline enum sc_action -sc_action_from_sdl_keyboard_type(uint32_t type) { - assert(type == SDL_KEYDOWN || type == SDL_KEYUP); - if (type == SDL_KEYDOWN) { - return SC_ACTION_DOWN; - } - return SC_ACTION_UP; -} - -static inline enum sc_action -sc_action_from_sdl_mousebutton_type(uint32_t type) { - assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP); - if (type == SDL_MOUSEBUTTONDOWN) { - return SC_ACTION_DOWN; - } - return SC_ACTION_UP; -} - -static inline enum sc_touch_action -sc_touch_action_from_sdl(uint32_t type) { - assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN || - type == SDL_FINGERUP); - if (type == SDL_FINGERMOTION) { - return SC_TOUCH_ACTION_MOVE; - } - if (type == SDL_FINGERDOWN) { - return SC_TOUCH_ACTION_DOWN; - } - return SC_TOUCH_ACTION_UP; -} - -static inline enum sc_mouse_button -sc_mouse_button_from_sdl(uint8_t button) { - if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) { - // SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index) - return SDL_BUTTON(button); - } - - return SC_MOUSE_BUTTON_UNKNOWN; -} - -static inline uint8_t -sc_mouse_buttons_state_from_sdl(uint32_t buttons_state, - bool forward_all_clicks) { - assert(buttons_state < 0x100); // fits in uint8_t - - uint8_t mask = SC_MOUSE_BUTTON_LEFT; - if (forward_all_clicks) { - mask |= SC_MOUSE_BUTTON_RIGHT - | SC_MOUSE_BUTTON_MIDDLE - | SC_MOUSE_BUTTON_X1 - | SC_MOUSE_BUTTON_X2; - } - - return buttons_state & mask; -} - #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t From 91418c79ab3b7056c21ef6c64f903704c8f00a52 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 22 Jan 2022 11:09:41 +0100 Subject: [PATCH 0366/1133] Add OTG mode Add an option --otg to run scrcpy with only physical keyboard and mouse simulation (HID over AOA), without mirroring and without requiring adb. To avoid adding complexity into the scrcpy initialization and screen implementation, OTG mode is implemented totally separately, with a separate window. PR #2974 --- app/meson.build | 2 + app/scrcpy.1 | 12 ++ app/src/cli.c | 61 ++++++++++ app/src/events.h | 1 + app/src/main.c | 10 +- app/src/options.c | 3 + app/src/options.h | 3 + app/src/usb/scrcpy_otg.c | 213 ++++++++++++++++++++++++++++++++ app/src/usb/scrcpy_otg.h | 12 ++ app/src/usb/screen_otg.c | 254 +++++++++++++++++++++++++++++++++++++++ app/src/usb/screen_otg.h | 46 +++++++ 11 files changed, 615 insertions(+), 2 deletions(-) create mode 100644 app/src/usb/scrcpy_otg.c create mode 100644 app/src/usb/scrcpy_otg.h create mode 100644 app/src/usb/screen_otg.c create mode 100644 app/src/usb/screen_otg.h diff --git a/app/meson.build b/app/meson.build index 5497281a..80a0d9d2 100644 --- a/app/meson.build +++ b/app/meson.build @@ -78,6 +78,8 @@ if usb_support 'src/usb/aoa_hid.c', 'src/usb/hid_keyboard.c', 'src/usb/hid_mouse.c', + 'src/usb/scrcpy_otg.c', + 'src/usb/screen_otg.c', 'src/usb/usb.c', ] endif diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 37431a91..e8e74f98 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -162,6 +162,18 @@ Do not forward repeated key events when a key is held down. .B \-\-no\-mipmaps If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. +.TP +.B \-\-otg +Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. + +In this mode, adb (USB debugging) is not necessary, and mirroring is disabled. + +LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mouse back to the computer. + +It may only work over USB, and is currently only supported on Linux. + +See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR. + .TP .BI "\-p, \-\-port " port[:port] Set the TCP port (range) used by the client to listen. diff --git a/app/src/cli.c b/app/src/cli.c index 34c3103a..28462efd 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -53,6 +53,7 @@ #define OPT_TCPIP 1033 #define OPT_RAW_KEY_EVENTS 1034 #define OPT_NO_DOWNSIZE_ON_ERROR 1035 +#define OPT_OTG 1036 struct sc_option { char shortopt; @@ -276,6 +277,20 @@ static const struct sc_option options[] = { "mipmaps are automatically generated to improve downscaling " "quality. This option disables the generation of mipmaps.", }, + { + .longopt_id = OPT_OTG, + .longopt = "otg", + .text = "Run in OTG mode: simulate physical keyboard and mouse, " + "as if the computer keyboard and mouse were plugged directly " + "to the device via an OTG cable.\n" + "In this mode, adb (USB debugging) is not necessary, and " + "mirroring is disabled.\n" + "LAlt, LSuper or RSuper toggle the mouse capture mode, to give " + "control of the mouse back to the computer.\n" + "It may only work over USB, and is currently only supported " + "on Linux.\n" + "See --hid-keyboard and --hid-mouse.", + }, { .shortopt = 'p', .longopt = "port", @@ -1500,6 +1515,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_DOWNSIZE_ON_ERROR: opts->downsize_on_error = false; break; + case OPT_OTG: +#ifdef HAVE_USB + opts->otg = true; + break; +#else + LOGE("OTG mode (--otg) is not supported on this platform. It " + "is only available on Linux."); + return false; +#endif case OPT_V4L2_SINK: #ifdef HAVE_V4L2 opts->v4l2_device = optarg; @@ -1610,6 +1634,43 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } +#ifdef HAVE_USB + if (opts->otg) { + // OTG mode is compatible with only very few options. + // Only report obvious errors. + if (opts->record_filename) { + LOGE("OTG mode: could not record"); + return false; + } + if (opts->turn_screen_off) { + LOGE("OTG mode: could not turn screen off"); + return false; + } + if (opts->stay_awake) { + LOGE("OTG mode: could not stay awake"); + return false; + } + if (opts->show_touches) { + LOGE("OTG mode: could not request to show touches"); + return false; + } + if (opts->power_off_on_close) { + LOGE("OTG mode: could not request power off on close"); + return false; + } + if (opts->display_id) { + LOGE("OTG mode: could not select display"); + return false; + } +#ifdef HAVE_V4L2 + if (opts->v4l2_device) { + LOGE("OTG mode: could not sink to V4L2 device"); + return false; + } +#endif + } +#endif + return true; } diff --git a/app/src/events.h b/app/src/events.h index abe1a72c..3c14f96e 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -2,3 +2,4 @@ #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2) #define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) +#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) diff --git a/app/src/main.c b/app/src/main.c index cbcef4a7..8a8c029c 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -13,6 +13,7 @@ #include "cli.h" #include "options.h" #include "scrcpy.h" +#include "usb/scrcpy_otg.h" #include "util/log.h" static void @@ -88,9 +89,14 @@ main(int argc, char *argv[]) { return 1; } - int res = scrcpy(&args.opts) ? 0 : 1; +#ifdef HAVE_USB + bool ok = args.opts.otg ? scrcpy_otg(&args.opts) + : scrcpy(&args.opts); +#else + bool ok = scrcpy(&args.opts); +#endif avformat_network_deinit(); // ignore failure - return res; + return ok ? 0 : 1; } diff --git a/app/src/options.c b/app/src/options.c index b8560406..c94f798d 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -37,6 +37,9 @@ const struct scrcpy_options scrcpy_options_default = { .display_id = 0, .display_buffer = 0, .v4l2_buffer = 0, +#ifdef HAVE_USB + .otg = false, +#endif .show_touches = false, .fullscreen = false, .always_on_top = false, diff --git a/app/src/options.h b/app/src/options.h index 99f03d40..1591a065 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -112,6 +112,9 @@ struct scrcpy_options { uint32_t display_id; sc_tick display_buffer; sc_tick v4l2_buffer; +#ifdef HAVE_USB + bool otg; +#endif bool show_touches; bool fullscreen; bool always_on_top; diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c new file mode 100644 index 00000000..0a28eda4 --- /dev/null +++ b/app/src/usb/scrcpy_otg.c @@ -0,0 +1,213 @@ +#include "scrcpy_otg.h" + +#include + +#include "events.h" +#include "screen_otg.h" +#include "util/log.h" + +struct scrcpy_otg { + struct sc_usb usb; + struct sc_aoa aoa; + struct sc_hid_keyboard keyboard; + struct sc_hid_mouse mouse; + + struct sc_screen_otg screen_otg; +}; + +static void +sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) { + (void) usb; + (void) userdata; + + SDL_Event event; + event.type = EVENT_USB_DEVICE_DISCONNECTED; + int ret = SDL_PushEvent(&event); + if (ret < 0) { + LOGE("Could not post USB disconnection event: %s", SDL_GetError()); + } +} + +static bool +event_loop(struct scrcpy_otg *s) { + SDL_Event event; + while (SDL_WaitEvent(&event)) { + switch (event.type) { + case EVENT_USB_DEVICE_DISCONNECTED: + LOGW("Device disconnected"); + return false; + case SDL_QUIT: + LOGD("User requested to quit"); + return true; + default: + sc_screen_otg_handle_event(&s->screen_otg, &event); + break; + } + } + return false; +} + +bool +scrcpy_otg(struct scrcpy_options *options) { + static struct scrcpy_otg scrcpy_otg; + struct scrcpy_otg *s = &scrcpy_otg; + + const char *serial = options->serial; + + // Minimal SDL initialization + if (SDL_Init(SDL_INIT_EVENTS)) { + LOGC("Could not initialize SDL: %s", SDL_GetError()); + return false; + } + + atexit(SDL_Quit); + + bool ret = false; + + struct sc_hid_keyboard *keyboard = NULL; + struct sc_hid_mouse *mouse = NULL; + bool usb_device_initialized = false; + bool usb_connected = false; + bool aoa_started = false; + bool aoa_initialized = false; + + static const struct sc_usb_callbacks cbs = { + .on_disconnected = sc_usb_on_disconnected, + }; + bool ok = sc_usb_init(&s->usb); + if (!ok) { + return false; + } + + struct sc_usb_device usb_devices[16]; + ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, + ARRAY_LEN(usb_devices)); + if (count < 0) { + LOGE("Could not list USB devices"); + goto end; + } + + if (count == 0) { + if (serial) { + LOGE("Could not find USB device %s", serial); + } else { + LOGE("Could not find any USB device"); + } + goto end; + } + + if (count > 1) { + if (serial) { + LOGE("Multiple (%d) USB devices with serial %s:", (int) count, + serial); + } else { + LOGE("Multiple (%d) USB devices:", (int) count); + } + for (size_t i = 0; i < (size_t) count; ++i) { + struct sc_usb_device *d = &usb_devices[i]; + LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", + d->serial, d->vid, d->pid, d->manufacturer, d->product); + } + if (!serial) { + LOGE("Specify the device via -s or --serial"); + } + sc_usb_device_destroy_all(usb_devices, count); + goto end; + } + usb_device_initialized = true; + + struct sc_usb_device *usb_device = &usb_devices[0]; + + LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", + usb_device->serial, usb_device->vid, usb_device->pid, + usb_device->manufacturer, usb_device->product); + + ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL); + if (!ok) { + goto end; + } + usb_connected = true; + + ok = sc_aoa_init(&s->aoa, &s->usb, NULL); + if (!ok) { + goto end; + } + aoa_initialized = true; + + ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); + if (!ok) { + goto end; + } + keyboard = &s->keyboard; + + ok = sc_hid_mouse_init(&s->mouse, &s->aoa); + if (!ok) { + goto end; + } + mouse = &s->mouse; + + ok = sc_aoa_start(&s->aoa); + if (!ok) { + goto end; + } + aoa_started = true; + + const char *window_title = options->window_title; + if (!window_title) { + window_title = usb_device->product ? usb_device->product : "scrcpy"; + } + + struct sc_screen_otg_params params = { + .keyboard = keyboard, + .mouse = mouse, + .window_title = window_title, + .always_on_top = options->always_on_top, + .window_x = options->window_x, + .window_y = options->window_y, + .window_borderless = options->window_borderless, + }; + + ok = sc_screen_otg_init(&s->screen_otg, ¶ms); + if (!ok) { + goto end; + } + + // usb_device not needed anymore + sc_usb_device_destroy(usb_device); + usb_device_initialized = false; + + ret = event_loop(s); + LOGD("quit..."); + +end: + if (aoa_started) { + sc_aoa_stop(&s->aoa); + } + sc_usb_stop(&s->usb); + + if (mouse) { + sc_hid_mouse_destroy(&s->mouse); + } + if (keyboard) { + sc_hid_keyboard_destroy(&s->keyboard); + } + + if (aoa_initialized) { + sc_aoa_join(&s->aoa); + sc_aoa_destroy(&s->aoa); + } + + sc_usb_join(&s->usb); + + if (usb_connected) { + sc_usb_disconnect(&s->usb); + } + + if (usb_device_initialized) { + sc_usb_device_destroy(usb_device); + } + + sc_usb_destroy(&s->usb); + + return ret; +} diff --git a/app/src/usb/scrcpy_otg.h b/app/src/usb/scrcpy_otg.h new file mode 100644 index 00000000..24b9cde8 --- /dev/null +++ b/app/src/usb/scrcpy_otg.h @@ -0,0 +1,12 @@ +#ifndef SCRCPY_OTG_H +#define SCRCPY_OTG_H + +#include "common.h" + +#include +#include "options.h" + +bool +scrcpy_otg(struct scrcpy_options *options); + +#endif diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c new file mode 100644 index 00000000..a4b37264 --- /dev/null +++ b/app/src/usb/screen_otg.c @@ -0,0 +1,254 @@ +#include "screen_otg.h" + +#include "icon.h" +#include "options.h" +#include "util/log.h" + +static void +sc_screen_otg_capture_mouse(struct sc_screen_otg *screen, bool capture) { + if (SDL_SetRelativeMouseMode(capture)) { + LOGE("Could not set relative mouse mode to %s: %s", + capture ? "true" : "false", SDL_GetError()); + return; + } + + screen->mouse_captured = capture; +} + +static void +sc_screen_otg_render(struct sc_screen_otg *screen) { + SDL_RenderClear(screen->renderer); + if (screen->texture) { + SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); + } + SDL_RenderPresent(screen->renderer); +} + +bool +sc_screen_otg_init(struct sc_screen_otg *screen, + const struct sc_screen_otg_params *params) { + screen->keyboard = params->keyboard; + screen->mouse = params->mouse; + + screen->mouse_captured = false; + screen->mouse_capture_key_pressed = 0; + + const char *title = params->window_title; + assert(title); + + int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED + ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; + int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED + ? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; + int width = 256; + int height = 256; + + uint32_t window_flags = 0; + if (params->always_on_top) { + window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; + } + if (params->window_borderless) { + window_flags |= SDL_WINDOW_BORDERLESS; + } + + screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags); + if (!screen->window) { + LOGE("Could not create window: %s", SDL_GetError()); + return false; + } + + screen->renderer = SDL_CreateRenderer(screen->window, -1, 0); + if (!screen->renderer) { + LOGE("Could not create renderer: %s", SDL_GetError()); + goto error_destroy_window; + } + + SDL_Surface *icon = scrcpy_icon_load(); + + if (icon) { + SDL_SetWindowIcon(screen->window, icon); + + screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon); + scrcpy_icon_destroy(icon); + if (!screen->texture) { + goto error_destroy_renderer; + } + } else { + screen->texture = NULL; + LOGW("Could not load icon"); + } + + // Capture mouse on start + sc_screen_otg_capture_mouse(screen, true); + + return true; + +error_destroy_window: + SDL_DestroyWindow(screen->window); +error_destroy_renderer: + SDL_DestroyRenderer(screen->renderer); + + return false; +} + +void +sc_screen_otg_destroy(struct sc_screen_otg *screen) { + if (screen->texture) { + SDL_DestroyTexture(screen->texture); + } + SDL_DestroyRenderer(screen->renderer); + SDL_DestroyWindow(screen->window); +} + +static inline bool +sc_screen_otg_is_mouse_capture_key(SDL_Keycode key) { + return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI; +} + +static void +sc_screen_otg_process_key(struct sc_screen_otg *screen, + const SDL_KeyboardEvent *event) { + assert(screen->keyboard); + struct sc_key_processor *kp = &screen->keyboard->key_processor; + + struct sc_key_event evt = { + .action = sc_action_from_sdl_keyboard_type(event->type), + .keycode = sc_keycode_from_sdl(event->keysym.sym), + .scancode = sc_scancode_from_sdl(event->keysym.scancode), + .repeat = event->repeat, + .mods_state = sc_mods_state_from_sdl(event->keysym.mod), + }; + + assert(kp->ops->process_key); + kp->ops->process_key(kp, &evt, SC_SEQUENCE_INVALID); +} + +static void +sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen, + const SDL_MouseMotionEvent *event) { + assert(screen->mouse); + struct sc_mouse_processor *mp = &screen->mouse->mouse_processor; + + struct sc_mouse_motion_event evt = { + // .position not used for HID events + .xrel = event->xrel, + .yrel = event->yrel, + .buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true), + }; + + assert(mp->ops->process_mouse_motion); + mp->ops->process_mouse_motion(mp, &evt); +} + +static void +sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen, + const SDL_MouseButtonEvent *event) { + assert(screen->mouse); + struct sc_mouse_processor *mp = &screen->mouse->mouse_processor; + + uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL); + + struct sc_mouse_click_event evt = { + // .position not used for HID events + .action = sc_action_from_sdl_mousebutton_type(event->type), + .button = sc_mouse_button_from_sdl(event->button), + .buttons_state = + sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), + }; + + assert(mp->ops->process_mouse_click); + mp->ops->process_mouse_click(mp, &evt); +} + +static void +sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen, + const SDL_MouseWheelEvent *event) { + assert(screen->mouse); + struct sc_mouse_processor *mp = &screen->mouse->mouse_processor; + + uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL); + + struct sc_mouse_scroll_event evt = { + // .position not used for HID events + .hscroll = event->x, + .vscroll = event->y, + .buttons_state = + sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), + }; + + assert(mp->ops->process_mouse_scroll); + mp->ops->process_mouse_scroll(mp, &evt); +} + +void +sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { + switch (event->type) { + case SDL_WINDOWEVENT: + switch (event->window.event) { + case SDL_WINDOWEVENT_EXPOSED: + sc_screen_otg_render(screen); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + sc_screen_otg_capture_mouse(screen, false); + break; + } + return; + case SDL_KEYDOWN: { + SDL_Keycode key = event->key.keysym.sym; + if (sc_screen_otg_is_mouse_capture_key(key)) { + if (!screen->mouse_capture_key_pressed) { + screen->mouse_capture_key_pressed = key; + } else { + // Another mouse capture key has been pressed, cancel mouse + // (un)capture + screen->mouse_capture_key_pressed = 0; + } + // Mouse capture keys are never forwarded to the device + return; + } + + sc_screen_otg_process_key(screen, &event->key); + break; + } + case SDL_KEYUP: { + SDL_Keycode key = event->key.keysym.sym; + SDL_Keycode cap = screen->mouse_capture_key_pressed; + screen->mouse_capture_key_pressed = 0; + if (sc_screen_otg_is_mouse_capture_key(key)) { + if (key == cap) { + // A mouse capture key has been pressed then released: + // toggle the capture mouse mode + sc_screen_otg_capture_mouse(screen, + !screen->mouse_captured); + } + // Mouse capture keys are never forwarded to the device + return; + } + + sc_screen_otg_process_key(screen, &event->key); + break; + } + case SDL_MOUSEMOTION: + if (screen->mouse_captured) { + sc_screen_otg_process_mouse_motion(screen, &event->motion); + } + break; + case SDL_MOUSEBUTTONDOWN: + if (screen->mouse_captured) { + sc_screen_otg_process_mouse_button(screen, &event->button); + } + break; + case SDL_MOUSEBUTTONUP: + if (screen->mouse_captured) { + sc_screen_otg_process_mouse_button(screen, &event->button); + } else { + sc_screen_otg_capture_mouse(screen, true); + } + break; + case SDL_MOUSEWHEEL: + if (screen->mouse_captured) { + sc_screen_otg_process_mouse_wheel(screen, &event->wheel); + } + break; + } +} diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h new file mode 100644 index 00000000..3fa1c4ad --- /dev/null +++ b/app/src/usb/screen_otg.h @@ -0,0 +1,46 @@ +#ifndef SC_SCREEN_OTG_H +#define SC_SCREEN_OTG_H + +#include "common.h" + +#include +#include + +#include "hid_keyboard.h" +#include "hid_mouse.h" + +struct sc_screen_otg { + struct sc_hid_keyboard *keyboard; + struct sc_hid_mouse *mouse; + + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + + // See equivalent mechanism in screen.h + bool mouse_captured; + SDL_Keycode mouse_capture_key_pressed; +}; + +struct sc_screen_otg_params { + struct sc_hid_keyboard *keyboard; + struct sc_hid_mouse *mouse; + + const char *window_title; + bool always_on_top; + int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED + int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED + bool window_borderless; +}; + +bool +sc_screen_otg_init(struct sc_screen_otg *screen, + const struct sc_screen_otg_params *params); + +void +sc_screen_otg_destroy(struct sc_screen_otg *screen); + +void +sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event); + +#endif From c5be0d6438c14a059fc4b581b2645fce6b6666cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 22:30:57 +0100 Subject: [PATCH 0367/1133] Document OTG mode in README PR #2974 --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 9eb4bd21..af0e54f7 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Its features include: (Linux-only) - [physical mouse simulation (HID)](#physical-mouse-simulation-hid) (Linux-only) + - [OTG mode](#otg) (Linux-only) - and more… ## Requirements @@ -849,6 +850,26 @@ Special capture keys, either Alt or Super, toggle the mouse back to the computer. +#### OTG + +It is possible to run _scrcpy_ with only physical keyboard and mouse simulation +(HID), as if the computer keyboard and mouse were plugged directly to the device +via an OTG cable. + +In this mode, _adb_ (USB debugging) is not necessary, and mirroring is disabled. + +To enable OTG mode: + +```bash +scrcpy --otg +# Pass the serial if several USB devices are available +scrcpy --otg -s 0123456789abcdef +``` + +Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is +connected by USB, and is currently only supported on Linux. + + #### Text injection preference There are two kinds of [events][textevents] generated when typing text: From ea68a003a2e5d584a19f83d81d1aeb86690f6102 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 26 Jan 2022 22:50:10 +0100 Subject: [PATCH 0368/1133] Make HID keyboard and mouse optional in OTG mode Allow to only enable HID keyboard or HID mouse: scrcpy --otg -K # keyboard only scrcpy --otg -M # mouse only scrcpy --otg -KM # keyboard and mouse scrcpy --otg # keyboard and mouse PR #2974 --- README.md | 10 +++++ app/scrcpy.1 | 2 + app/src/cli.c | 2 + app/src/usb/scrcpy_otg.c | 31 ++++++++++---- app/src/usb/screen_otg.c | 87 +++++++++++++++++++++++----------------- 5 files changed, 87 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index af0e54f7..8509dd71 100644 --- a/README.md +++ b/README.md @@ -866,6 +866,16 @@ scrcpy --otg scrcpy --otg -s 0123456789abcdef ``` +It is possible to enable only HID keyboard or HID mouse: + +```bash +scrcpy --otg --hid-keyboard # keyboard only +scrcpy --otg --hid-mouse # mouse only +scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse +# for convenience, enable both by default +scrcpy --otg # keyboard and mouse +``` + Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is connected by USB, and is currently only supported on Linux. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e8e74f98..0f618cb4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -170,6 +170,8 @@ In this mode, adb (USB debugging) is not necessary, and mirroring is disabled. LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mouse back to the computer. +If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both. + It may only work over USB, and is currently only supported on Linux. See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR. diff --git a/app/src/cli.c b/app/src/cli.c index 28462efd..5587b2d8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -287,6 +287,8 @@ static const struct sc_option options[] = { "mirroring is disabled.\n" "LAlt, LSuper or RSuper toggle the mouse capture mode, to give " "control of the mouse back to the computer.\n" + "If any of --hid-keyboard or --hid-mouse is set, only enable " + "keyboard or mouse respectively, otherwise enable both." "It may only work over USB, and is currently only supported " "on Linux.\n" "See --hid-keyboard and --hid-mouse.", diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 0a28eda4..f2e5d549 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -134,17 +134,32 @@ scrcpy_otg(struct scrcpy_options *options) { } aoa_initialized = true; - ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); - if (!ok) { - goto end; + bool enable_keyboard = + options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; + bool enable_mouse = + options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; + + // If neither --hid-keyboard or --hid-mouse is passed, enable both + if (!enable_keyboard && !enable_mouse) { + enable_keyboard = true; + enable_mouse = true; } - keyboard = &s->keyboard; - ok = sc_hid_mouse_init(&s->mouse, &s->aoa); - if (!ok) { - goto end; + if (enable_keyboard) { + ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); + if (!ok) { + goto end; + } + keyboard = &s->keyboard; + } + + if (enable_mouse) { + ok = sc_hid_mouse_init(&s->mouse, &s->aoa); + if (!ok) { + goto end; + } + mouse = &s->mouse; } - mouse = &s->mouse; ok = sc_aoa_start(&s->aoa); if (!ok) { diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index a4b37264..cda0da5e 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -6,6 +6,7 @@ static void sc_screen_otg_capture_mouse(struct sc_screen_otg *screen, bool capture) { + assert(screen->mouse); if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); @@ -78,8 +79,10 @@ sc_screen_otg_init(struct sc_screen_otg *screen, LOGW("Could not load icon"); } - // Capture mouse on start - sc_screen_otg_capture_mouse(screen, true); + if (screen->mouse) { + // Capture mouse on start + sc_screen_otg_capture_mouse(screen, true); + } return true; @@ -189,64 +192,74 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { sc_screen_otg_render(screen); break; case SDL_WINDOWEVENT_FOCUS_LOST: - sc_screen_otg_capture_mouse(screen, false); + if (screen->mouse) { + sc_screen_otg_capture_mouse(screen, false); + } break; } return; - case SDL_KEYDOWN: { - SDL_Keycode key = event->key.keysym.sym; - if (sc_screen_otg_is_mouse_capture_key(key)) { - if (!screen->mouse_capture_key_pressed) { - screen->mouse_capture_key_pressed = key; - } else { - // Another mouse capture key has been pressed, cancel mouse - // (un)capture - screen->mouse_capture_key_pressed = 0; + case SDL_KEYDOWN: + if (screen->mouse) { + SDL_Keycode key = event->key.keysym.sym; + if (sc_screen_otg_is_mouse_capture_key(key)) { + if (!screen->mouse_capture_key_pressed) { + screen->mouse_capture_key_pressed = key; + } else { + // Another mouse capture key has been pressed, cancel + // mouse (un)capture + screen->mouse_capture_key_pressed = 0; + } + // Mouse capture keys are never forwarded to the device + return; } - // Mouse capture keys are never forwarded to the device - return; } - sc_screen_otg_process_key(screen, &event->key); + if (screen->keyboard) { + sc_screen_otg_process_key(screen, &event->key); + } break; - } - case SDL_KEYUP: { - SDL_Keycode key = event->key.keysym.sym; - SDL_Keycode cap = screen->mouse_capture_key_pressed; - screen->mouse_capture_key_pressed = 0; - if (sc_screen_otg_is_mouse_capture_key(key)) { - if (key == cap) { - // A mouse capture key has been pressed then released: - // toggle the capture mouse mode - sc_screen_otg_capture_mouse(screen, - !screen->mouse_captured); + case SDL_KEYUP: + if (screen->mouse) { + SDL_Keycode key = event->key.keysym.sym; + SDL_Keycode cap = screen->mouse_capture_key_pressed; + screen->mouse_capture_key_pressed = 0; + if (sc_screen_otg_is_mouse_capture_key(key)) { + if (key == cap) { + // A mouse capture key has been pressed then released: + // toggle the capture mouse mode + sc_screen_otg_capture_mouse(screen, + !screen->mouse_captured); + } + // Mouse capture keys are never forwarded to the device + return; } - // Mouse capture keys are never forwarded to the device - return; } - sc_screen_otg_process_key(screen, &event->key); + if (screen->keyboard) { + sc_screen_otg_process_key(screen, &event->key); + } break; - } case SDL_MOUSEMOTION: - if (screen->mouse_captured) { + if (screen->mouse && screen->mouse_captured) { sc_screen_otg_process_mouse_motion(screen, &event->motion); } break; case SDL_MOUSEBUTTONDOWN: - if (screen->mouse_captured) { + if (screen->mouse && screen->mouse_captured) { sc_screen_otg_process_mouse_button(screen, &event->button); } break; case SDL_MOUSEBUTTONUP: - if (screen->mouse_captured) { - sc_screen_otg_process_mouse_button(screen, &event->button); - } else { - sc_screen_otg_capture_mouse(screen, true); + if (screen->mouse) { + if (screen->mouse_captured) { + sc_screen_otg_process_mouse_button(screen, &event->button); + } else { + sc_screen_otg_capture_mouse(screen, true); + } } break; case SDL_MOUSEWHEEL: - if (screen->mouse_captured) { + if (screen->mouse && screen->mouse_captured) { sc_screen_otg_process_mouse_wheel(screen, &event->wheel); } break; From 5508c635cb11b61df9bfc3d9e5e603492100b067 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 27 Jan 2022 23:32:37 +0100 Subject: [PATCH 0369/1133] Enable mouse focus clickthrough in OTG mode A single click on the window must both give focus and capture the mouse. PR #2974 --- app/src/usb/scrcpy_otg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index f2e5d549..e27a3605 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -62,6 +62,10 @@ scrcpy_otg(struct scrcpy_options *options) { atexit(SDL_Quit); + if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) { + LOGW("Could not enable mouse focus clickthrough"); + } + bool ret = false; struct sc_hid_keyboard *keyboard = NULL; From 80bec708523ea7b8cefc9f74057611e8ac499b89 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 Jan 2022 09:05:36 +0100 Subject: [PATCH 0370/1133] Add helper to log Windows system errors It will help to log errors returned by GetLastError() or WSAGetLastError(): - - - Always log the errors in English to be able to read them in bug reports. --- app/src/util/log.c | 24 ++++++++++++++++++++++++ app/src/util/log.h | 6 ++++++ app/src/util/net.c | 9 +-------- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/app/src/util/log.c b/app/src/util/log.c index a285fffb..d8d7cd70 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -1,5 +1,8 @@ #include "log.h" +#if _WIN32 +# include +#endif #include static SDL_LogPriority @@ -51,3 +54,24 @@ sc_get_log_level(void) { SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); return log_level_sdl_to_sc(sdl_log); } + +#ifdef _WIN32 +bool +sc_log_windows_error(const char *prefix, int error) { + assert(prefix); + + char *message; + DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM; + DWORD lang_id = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + int ret = + FormatMessage(flags, NULL, error, lang_id, (char *) &message, 0, NULL); + if (ret <= 0) { + return false; + } + + // Note: message already contains a trailing '\n' + LOGE("%s: [%d] %s", prefix, error, message); + LocalFree(message); + return true; +} +#endif diff --git a/app/src/util/log.h b/app/src/util/log.h index 231e0846..e3efdbe5 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -26,4 +26,10 @@ sc_set_log_level(enum sc_log_level level); enum sc_log_level sc_get_log_level(void); +#ifdef _WIN32 +// Log system error (typically returned by GetLastError() or similar) +bool +sc_log_windows_error(const char *prefix, int error); +#endif + #endif diff --git a/app/src/util/net.c b/app/src/util/net.c index 565db2e9..b18566be 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -117,14 +117,7 @@ set_cloexec_flag(sc_raw_socket raw_sock) { 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); + sc_log_windows_error(s, WSAGetLastError()); #else perror(s); #endif From b8d7f36ba37c153aa1516392e08afdbe6386e46b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 08:05:59 +0100 Subject: [PATCH 0371/1133] Fix SC_EXIT_CODE_NONE value The exit code on windows is stored in a DWORD, an unsigned long: Use the max value of this type for SC_EXIT_CODE_NONE. --- app/src/util/process.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/util/process.h b/app/src/util/process.h index 17c09bc5..90920620 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -15,7 +15,7 @@ // # define SC_PRIsizet "Iu" # define SC_PROCESS_NONE NULL -# define SC_EXIT_CODE_NONE -1u // max value as unsigned +# define SC_EXIT_CODE_NONE -1UL // max value as unsigned long typedef HANDLE sc_pid; typedef DWORD sc_exit_code; typedef HANDLE sc_pipe; From eaba6136334e5fb50d3276ca7405b77e48c8f29e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 11:46:39 +0100 Subject: [PATCH 0372/1133] Revert "Upgrade platform-tools (32.0.0) for Windows" This reverts commit c0de365f672110959cec8fca936bd6aeaa01b59d. The new adb.exe crashes. Refs #2981 comment --- prebuilt-deps/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index 44ea6e1e..b40321b6 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -28,6 +28,6 @@ prepare-sdl2: SDL2-2.0.20 prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r32.0.0-windows.zip \ - 41f4c7512b32cbb3f8c624c20b56326abb692a6f169b03b4b63b6c5a6fdbb08c \ + @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ + 0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \ platform-tools From 38cdcdda50de8872230921fb5681bcd4665bc1e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 14:08:49 +0100 Subject: [PATCH 0373/1133] Improve prebuilt system This aims to fix two issues with the previous implementation: 1. the whole content of downloaded archives were extracted, while only few files are necessary; 2. the archives were extracted in the prebuild-deps/ directory as is. As a consequence of (2), the actual directory name relied on the root directory of the archive. For adb, this root directory was always "platform-tools", so when bumping the adb version, the target directory already existed and the dependency was not upgraded (the old one had to be removed manually). Expose common function to download a file and check its checksum, but let the custom script for each dependency extract only the needed files and reorganize the content if necessary. --- app/meson.build | 10 ++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- prebuilt-deps/.gitignore | 5 +-- prebuilt-deps/Makefile | 33 --------------- prebuilt-deps/common | 22 ++++++++++ prebuilt-deps/prepare-adb.sh | 32 ++++++++++++++ prebuilt-deps/prepare-dep | 61 --------------------------- prebuilt-deps/prepare-ffmpeg-win32.sh | 45 ++++++++++++++++++++ prebuilt-deps/prepare-ffmpeg-win64.sh | 35 +++++++++++++++ prebuilt-deps/prepare-sdl.sh | 32 ++++++++++++++ release.mk | 44 ++++++++++--------- 12 files changed, 198 insertions(+), 125 deletions(-) delete mode 100644 prebuilt-deps/Makefile create mode 100755 prebuilt-deps/common create mode 100755 prebuilt-deps/prepare-adb.sh delete mode 100755 prebuilt-deps/prepare-dep create mode 100755 prebuilt-deps/prepare-ffmpeg-win32.sh create mode 100755 prebuilt-deps/prepare-ffmpeg-win64.sh create mode 100755 prebuilt-deps/prepare-sdl.sh diff --git a/app/meson.build b/app/meson.build index 80a0d9d2..7f675035 100644 --- a/app/meson.build +++ b/app/meson.build @@ -109,9 +109,9 @@ if not crossbuild_windows else # cross-compile mingw32 build (from Linux to Windows) prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') - sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' - sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib' - sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include' + sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/bin' + sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/lib' + sdl2_include_dir = '../prebuilt-deps/data/' + prebuilt_sdl2 + '/include' sdl2 = declare_dependency( dependencies: [ @@ -122,8 +122,8 @@ else ) prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') - ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin' - ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include' + ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' + ffmpeg_include_dir = '../prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' # ffmpeg versions are different for win32 and win64 builds ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec') diff --git a/cross_win32.txt b/cross_win32.txt index 9bf974e3..db448a00 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -19,5 +19,5 @@ endian = 'little' ffmpeg_avcodec = 'avcodec-58' ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' -prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared' +prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' diff --git a/cross_win64.txt b/cross_win64.txt index 763e12e9..9d169a71 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -19,5 +19,5 @@ endian = 'little' ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' -prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared' +prebuilt_ffmpeg = 'ffmpeg-win64-5.0' prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' diff --git a/prebuilt-deps/.gitignore b/prebuilt-deps/.gitignore index 934bc04c..3af0ccb6 100644 --- a/prebuilt-deps/.gitignore +++ b/prebuilt-deps/.gitignore @@ -1,4 +1 @@ -* -!/.gitignore -!/Makefile -!/prepare-dep +/data diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile deleted file mode 100644 index b40321b6..00000000 --- a/prebuilt-deps/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -.PHONY: prepare-win32 prepare-win64 \ - prepare-ffmpeg-win32 \ - prepare-ffmpeg-win64 \ - prepare-sdl2 \ - prepare-adb - -prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb -prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb - -# Use old FFmpeg version for win32, there are no new prebuilts -prepare-ffmpeg-win32: - @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ - 357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \ - ffmpeg-4.3.1-win32-shared - @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \ - 230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \ - ffmpeg-4.3.1-win32-dev - ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/ - -prepare-ffmpeg-win64: - @./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \ - e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \ - ffmpeg-5.0-full_build-shared - -prepare-sdl2: - @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.20-mingw.tar.gz \ - 38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 \ - SDL2-2.0.20 - -prepare-adb: - @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ - 0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \ - platform-tools diff --git a/prebuilt-deps/common b/prebuilt-deps/common new file mode 100755 index 00000000..c97f7de4 --- /dev/null +++ b/prebuilt-deps/common @@ -0,0 +1,22 @@ +PREBUILT_DATA_DIR=data + +checksum() { + local file="$1" + local sum="$2" + echo "$file: verifying checksum..." + echo "$sum $file" | sha256sum -c +} + +get_file() { + local url="$1" + local file="$2" + local sum="$3" + if [[ -f "$file" ]] + then + echo "$file: found" + else + echo "$file: not found, downloading..." + wget "$url" -O "$file" + fi + checksum "$file" "$sum" +} diff --git a/prebuilt-deps/prepare-adb.sh b/prebuilt-deps/prepare-adb.sh new file mode 100755 index 00000000..2e7997e1 --- /dev/null +++ b/prebuilt-deps/prepare-adb.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +DEP_DIR=platform-tools-31.0.3 + +FILENAME=platform-tools_r31.0.3-windows.zip +SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://dl.google.com/android/repository/$FILENAME" \ + "$FILENAME" "$SHA256SUM" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +ZIP_PREFIX=platform-tools +unzip "../$FILENAME" \ + "$ZIP_PREFIX"/AdbWinApi.dll \ + "$ZIP_PREFIX"/AdbWinUsbApi.dll \ + "$ZIP_PREFIX"/adb.exe +mv "$ZIP_PREFIX"/* . +rmdir "$ZIP_PREFIX" diff --git a/prebuilt-deps/prepare-dep b/prebuilt-deps/prepare-dep deleted file mode 100755 index a95b9bb6..00000000 --- a/prebuilt-deps/prepare-dep +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -set -e -url="$1" -sum="$2" -dir="$3" - -checksum() { - local file="$1" - local sum="$2" - echo "$file: verifying checksum..." - echo "$sum $file" | sha256sum -c -} - -get_file() { - local url="$1" - local file="$2" - local sum="$3" - if [[ -f "$file" ]] - then - echo "$file: found" - else - echo "$file: not found, downloading..." - wget "$url" -O "$file" - fi - checksum "$file" "$sum" -} - -extract() { - local file="$1" - echo "Extracting $file..." - if [[ "$file" == *.zip ]] - then - unzip -q "$file" - elif [[ "$file" == *.tar.gz ]] - then - tar xf "$file" - elif [[ "$file" == *.7z ]] - then - 7z x "$file" - else - echo "Unsupported file: $file" - return 1 - fi -} - -get_dep() { - local url="$1" - local sum="$2" - local dir="$3" - local file="${url##*/}" - if [[ -d "$dir" ]] - then - echo "$dir: found" - else - echo "$dir: not found" - get_file "$url" "$file" "$sum" - extract "$file" - fi -} - -get_dep "$url" "$sum" "$dir" diff --git a/prebuilt-deps/prepare-ffmpeg-win32.sh b/prebuilt-deps/prepare-ffmpeg-win32.sh new file mode 100755 index 00000000..2a6a3841 --- /dev/null +++ b/prebuilt-deps/prepare-ffmpeg-win32.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +DEP_DIR=ffmpeg-win32-4.3.1 + +FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip +SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 + +FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip +SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \ + "$FILENAME_SHARED" "$SHA256SUM_SHARED" +get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \ + "$FILENAME_DEV" "$SHA256SUM_DEV" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared +unzip "../$FILENAME_SHARED" \ + "$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \ + "$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \ + "$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \ + "$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \ + "$ZIP_PREFIX_SHARED"/bin/swscale-5.dll + +ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev +unzip "../$FILENAME_DEV" \ + "$ZIP_PREFIX_DEV/include/*" + +mv "$ZIP_PREFIX_SHARED"/* . +mv "$ZIP_PREFIX_DEV"/* . +rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV" diff --git a/prebuilt-deps/prepare-ffmpeg-win64.sh b/prebuilt-deps/prepare-ffmpeg-win64.sh new file mode 100755 index 00000000..a62eb261 --- /dev/null +++ b/prebuilt-deps/prepare-ffmpeg-win64.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +DEP_DIR=ffmpeg-win64-5.0 + +FILENAME=ffmpeg-5.0-full_build-shared.7z +SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://github.com/GyanD/codexffmpeg/releases/download/5.0/$FILENAME" \ + "$FILENAME" "$SHA256SUM" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +ZIP_PREFIX=ffmpeg-5.0-full_build-shared +7z x "../$FILENAME" \ + "$ZIP_PREFIX"/bin/avutil-57.dll \ + "$ZIP_PREFIX"/bin/avcodec-59.dll \ + "$ZIP_PREFIX"/bin/avformat-59.dll \ + "$ZIP_PREFIX"/bin/swresample-4.dll \ + "$ZIP_PREFIX"/bin/swscale-6.dll \ + "$ZIP_PREFIX"/include +mv "$ZIP_PREFIX"/* . +rmdir "$ZIP_PREFIX" diff --git a/prebuilt-deps/prepare-sdl.sh b/prebuilt-deps/prepare-sdl.sh new file mode 100755 index 00000000..c414c854 --- /dev/null +++ b/prebuilt-deps/prepare-sdl.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +DEP_DIR=SDL2-2.0.20 + +FILENAME=SDL2-devel-2.0.20-mingw.tar.gz +SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name +tar xf "../$FILENAME" --strip-components=1 \ + "$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \ + "$TAR_PREFIX"/i686-w64-mingw32/include/ \ + "$TAR_PREFIX"/i686-w64-mingw32/lib/ \ + "$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \ + "$TAR_PREFIX"/x86_64-w64-mingw32/include/ \ + "$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \ diff --git a/release.mk b/release.mk index 2f2fe9e1..37b8d5c5 100644 --- a/release.mk +++ b/release.mk @@ -63,7 +63,9 @@ build-server: ninja -C "$(SERVER_BUILD_DIR)" prepare-deps-win32: - -$(MAKE) -C prebuilt-deps prepare-win32 + @prebuilt-deps/prepare-adb.sh + @prebuilt-deps/prepare-sdl.sh + @prebuilt-deps/prepare-ffmpeg-win32.sh build-win32: prepare-deps-win32 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ @@ -75,7 +77,9 @@ build-win32: prepare-deps-win32 ninja -C "$(WIN32_BUILD_DIR)" prepare-deps-win64: - -$(MAKE) -C prebuilt-deps prepare-win64 + @prebuilt-deps/prepare-adb.sh + @prebuilt-deps/prepare-sdl.sh + @prebuilt-deps/prepare-ffmpeg-win64.sh build-win64: prepare-deps-win64 [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ @@ -94,15 +98,15 @@ dist-win32: build-server build-win32 cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp data/open_a_terminal_here.bat "$(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)/" - cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -112,15 +116,15 @@ dist-win64: build-server build-win64 cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From 334f46995a92df381df4c0ce818c3a533eaa203c Mon Sep 17 00:00:00 2001 From: Cccc_owo Date: Fri, 28 Jan 2022 16:09:03 +0800 Subject: [PATCH 0374/1133] Update README.zh-Hans.md to v1.21 PR #2978 Signed-off-by: Romain Vimont --- README.md | 2 +- README.zh-Hans.md | 143 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 110 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index db0e2244..01ed0991 100644 --- a/README.md +++ b/README.md @@ -1052,7 +1052,7 @@ This README is available in other languages: - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Español (Spanish, `sp`) - v1.21](README.sp.md) -- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md) +- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.21](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index b96d6d5a..69c6e7c0 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -1,14 +1,14 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ -只有原版的[README](README.md)会保持最新。 +_只有原版的 [README](README.md)是保证最新的。_ -Current version is based on [65b023a] +Current version is based on [8615813] -本文根据[65b023a]进行翻译。 +本文根据[8615813]进行翻译。 -[65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md +[8615813]: https://github.com/Genymobile/scrcpy/blob/86158130051d450a449a2e7bb20b0fcef1b62e80/README.md -# scrcpy (v1.20) +# scrcpy (v1.21) scrcpy @@ -68,12 +68,18 @@ Current version is based on [65b023a] ### Linux -在 Debian (目前仅支持 _testing_ 和 _sid_ 分支) 和Ubuntu (20.04) 上: +在 Debian 和 Ubuntu 上: ``` apt install scrcpy ``` +在 Arch Linux 上: + +``` +pacman -S scrcpy +``` + 我们也提供 [Snap] 包: [`scrcpy`][snap-link]。 [snap-link]: https://snapstats.org/snaps/scrcpy @@ -85,11 +91,6 @@ apt install scrcpy [COPR]: https://fedoraproject.org/wiki/Category:Copr [copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ -对 Arch Linux 我们提供 [AUR] 包: [`scrcpy`][aur-link]。 - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - 对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。 [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild @@ -343,9 +344,32 @@ scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲 ### 连接 -#### 无线 +#### TCP/IP (无线) + +_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。 + +##### 自动配置 + +参数 `--tcpip` 允许自动配置连接。这里有两种方式。 + +对于传入的 adb 连接,如果设备(在这个例子中以192.168.1.1为可用地址)已经监听了一个端口(通常是5555),运行: + +```bash +scrcpy --tcpip=192.168.1.1 # 默认端口是5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +如果adb TCP/IP(无线) 模式在某些设备上不被启用(或者你不知道IP地址),用USB连接设备,然后运行: + +```bash +scrcpy --tcpip # 无需参数 +``` + +这将会自动寻找设备IP地址,启用TCP/IP模式,然后在启动之前连接到设备。 -_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备: +##### 手动配置 + +或者,可以通过 `adb` 使用手动启用 TCP/IP 连接: 1. 将设备和电脑连接至同一 Wi-Fi。 2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: @@ -354,12 +378,12 @@ _Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接 adb shell ip route | awk '{print $9}' ``` -3. 启用设备的网络 adb 功能: `adb tcpip 5555`。 +3. 启用设备的网络 adb 功能:`adb tcpip 5555`。 4. 断开设备的 USB 连接。 5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。 6. 正常运行 `scrcpy`。 -可能降低码率和分辨率会更好一些: +降低比特率和分辨率可能很有用: ```bash scrcpy --bit-rate 2M --max-size 800 @@ -397,33 +421,75 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -#### SSH 隧道 +#### 隧道 + +要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)。 + +##### 远程ADB服务器 -要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同): +要连接到一个远程ADB服务器,让服务器在所有接口上监听: ```bash -adb kill-server # 关闭本地 5037 端口上的 adb 服务端 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer +adb kill-server +adb -a nodaemon server start # 保持该窗口开启 ``` -在另一个终端: +**警告:所有客户端与ADB服务器的交流都是未加密的。** + +假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +默认情况下,scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用): + +``` +scrcpy --tunnel-port=1234 +``` + + +##### SSH 隧道 + +为了安全地与远程ADB服务器通信,最好使用SSH隧道。 + +首先,确保ADB服务器正在远程计算机上运行: + +```bash +adb start-server +``` + +然后,建立一个SSH隧道: + +```bash +# 本地 5038 --> 远程 5037 +# 本地 27183 <-- 远程 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer +# 保持该窗口开启 +``` + +在另一个终端上,运行scrcpy: + +```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy ``` -若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` 和 `-R` 的区别): +若要不使用远程端口转发,可以强制使用正向连接(注意是 `-L` 而不是 `-R` ): ```bash -adb kill-server # 关闭本地 5037 端口上的 adb 服务端 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# 本地 5038 --> 远程 5037 +# 本地 27183 <-- 远程 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer # 保持该窗口开启 ``` -在另一个终端: +在另一个终端上,运行scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy --force-adb-forward ``` @@ -441,7 +507,7 @@ scrcpy -b2M -m800 --max-fps 15 窗口的标题默认为设备型号。可以通过如下命令修改: ```bash -scrcpy --window-title 'My device' +scrcpy --window-title "我的设备" ``` #### 位置和大小 @@ -630,6 +696,8 @@ scrcpy --disable-screensaver 一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 Ctrl+vMOD+v 的工作方式,使它们通过按键事件 (同 MOD+Shift+v) 来注入电脑剪贴板内容。 +要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。 + #### 双指缩放 模拟“双指缩放”:Ctrl+_按住并移动鼠标_。 @@ -659,11 +727,11 @@ scrcpy -K # 简写 在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。 -[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 +[实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 #### 文本注入偏好 -打字的时候,系统会产生两种[事件][textevents]: +输入文字的时候,系统会产生两种[事件][textevents]: - _按键事件_ ,代表一个按键被按下或松开。 - _文本事件_ ,代表一个字符被输入。 @@ -675,7 +743,13 @@ scrcpy -K # 简写 scrcpy --prefer-text ``` -(这会导致键盘在游戏中工作不正常) +(但这会导致键盘在游戏中工作不正常) + +相反,您可以强制始终注入原始按键事件: + +```bash +scrcpy --raw-key-events +``` 该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。 @@ -765,7 +839,7 @@ _[Super] 键通常是指 WindowsCmd 键。 | 点按 `主屏幕` | MOD+h \| _中键_ | 点按 `返回` | MOD+b \| _右键²_ | 点按 `切换应用` | MOD+s \| _第4键³_ - | 点按 `菜单` (解锁屏幕) | MOD+m + | 点按 `菜单` (解锁屏幕)⁴ | MOD+m | 点按 `音量+` | MOD+ _(上箭头)_ | 点按 `音量-` | MOD+ _(下箭头)_ | 点按 `电源` | MOD+p @@ -776,9 +850,9 @@ _[Super] 键通常是指 WindowsCmd 键。 | 展开通知面板 | MOD+n \| _第5键³_ | 展开设置面板 | MOD+n+n \| _双击第5键³_ | 收起通知面板 | MOD+Shift+n - | 复制到剪贴板⁴ | MOD+c - | 剪切到剪贴板⁴ | MOD+x - | 同步剪贴板并粘贴⁴ | MOD+v + | 复制到剪贴板⁵ | MOD+c + | 剪切到剪贴板⁵ | MOD+x + | 同步剪贴板并粘贴⁵ | MOD+v | 注入电脑剪贴板文本 | MOD+Shift+v | 打开/关闭FPS显示 (至标准输出) | MOD+i | 捏拉缩放 | Ctrl+_按住并移动鼠标_ @@ -788,7 +862,8 @@ _[Super] 键通常是指 WindowsCmd 键。 _¹双击黑边可以去除黑边。_ _²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_ _³鼠标的第4键和第5键。_ -_⁴需要安卓版本 Android >= 7。_ +_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_ +_⁵需要安卓版本 Android >= 7。_ 有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”: @@ -816,7 +891,7 @@ ADB=/path/to/adb scrcpy 一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。 -[`strcpy`] 复制一个 **str**ing (字符串); `scrcpy` 复制一个 **scr**een (屏幕)。 +[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。 [gnirehtet]: https://github.com/Genymobile/gnirehtet [`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html From 64a09513ae8a3941952e6e82969193f4cf393e81 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 15:44:52 +0100 Subject: [PATCH 0375/1133] Bump version to 1.22 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 7525d092..b130dc6a 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "1.21" + VALUE "ProductVersion", "1.22" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 0b4ed174..b4b59353 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.21', + version: '1.22', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 1f939a1a..f262b02d 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 12100 - versionName "1.21" + versionCode 12200 + versionName "1.22" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index ab5e19e4..ecf7b604 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.21 +SCRCPY_VERSION_NAME=1.22 PLATFORM=${ANDROID_PLATFORM:-31} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} From f4c7044b46ae28eb64cb5e1a15c9649a44023c70 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 16:13:50 +0100 Subject: [PATCH 0376/1133] Update links to v1.22 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index c9473f27..1e713b93 100644 --- a/BUILD.md +++ b/BUILD.md @@ -270,10 +270,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.21`][direct-scrcpy-server] - _(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_ + - [`scrcpy-server-v1.22`][direct-scrcpy-server] + _(SHA-256: c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index feea032d..8306e62e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.21) +# scrcpy (v1.22) scrcpy @@ -108,10 +108,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.21.zip`][direct-win64] - _(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_ + - [`scrcpy-win64-v1.22.zip`][direct-win64] + _(SHA-256: ce4d9b8cc761e29862c4a72d8ad6f538bdd1f1831d15fd1f36633cd3b403db82)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-win64-v1.22.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index 2a59a6f1..69dfefdc 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21 -PREBUILT_SERVER_SHA256=dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22 +PREBUILT_SERVER_SHA256=c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From a86deab3d4dcf7471b493ba82365aa284e1f74b8 Mon Sep 17 00:00:00 2001 From: Cccc_owo <47687154+Cccc-owo@users.noreply.github.com> Date: Sun, 30 Jan 2022 10:43:12 +0800 Subject: [PATCH 0377/1133] Update README.zh-Hans.md to v1.22 PR #2989 Signed-off-by: Romain Vimont --- README.md | 2 +- README.zh-Hans.md | 63 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8306e62e..e8ddf907 100644 --- a/README.md +++ b/README.md @@ -1116,7 +1116,7 @@ This README is available in other languages: - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Español (Spanish, `sp`) - v1.21](README.sp.md) -- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.21](README.zh-Hans.md) +- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index acdf9a7e..caf964b3 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -2,16 +2,18 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._ _只有原版的 [README](README.md)是保证最新的。_ -Current version is based on [8615813] +Current version is based on [f4c7044] -本文根据[8615813]进行翻译。 +本文根据[f4c7044]进行翻译。 -[8615813]: https://github.com/Genymobile/scrcpy/blob/86158130051d450a449a2e7bb20b0fcef1b62e80/README.md +[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md -# scrcpy (v1.21) +# scrcpy (v1.22) scrcpy +_发音为 "**scr**een **c**o**py**"_ + 本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。 ![screenshot](assets/screenshot-debian-600.jpg) @@ -36,6 +38,8 @@ Current version is based on [8615813] - [可配置显示质量](#采集设置) - 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux) - [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux) + - [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux) + - [OTG模式](#otg) (仅限 Linux) - 更多 …… ## 系统要求 @@ -362,7 +366,7 @@ scrcpy --tcpip=192.168.1.1:5555 如果adb TCP/IP(无线) 模式在某些设备上不被启用(或者你不知道IP地址),用USB连接设备,然后运行: ```bash -scrcpy --tcpip # 无需参数 +scrcpy --tcpip # 无需其他参数 ``` 这将会自动寻找设备IP地址,启用TCP/IP模式,然后在启动之前连接到设备。 @@ -729,6 +733,55 @@ scrcpy -K # 简写 [实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 +#### 物理鼠标模拟 (HID) + +与物理键盘模拟类似,可以模拟一个物理鼠标。 同样,它仅在设备通过 USB 连接时才有效,并且目前仅在 Linux 上受支持。 + +默认情况下,scrcpy 使用 Android 鼠标事件注入,使用绝对坐标。 通过模拟物理鼠标,在Android设备上出现鼠标指针,并注入鼠标相对运动、点击和滚动。 + +启用此模式: + +```bash +scrcpy --hid-mouse +scrcpy -M # 简写 +``` + +您还可以将 `--forward-all-clicks` 添加到 [转发所有点击][forward_all_clicks]. + +[forward_all_clicks]: #右键和中键 + +启用此模式后,计算机鼠标将被“捕获”(鼠标指针从计算机上消失并出现在 Android 设备上)。 + +特殊的捕获键,AltSuper,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。 + + +#### OTG + +可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_,就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。 + +在这个模式下,_adb_ (USB 调试)是不必要的,且镜像被禁用。 + +启用 OTG 模式: + +```bash +scrcpy --otg +# 如果有多个 USB 设备可用,则通过序列号选择 +scrcpy --otg -s 0123456789abcdef +``` + +只开启 HID 键盘 或 HID 鼠标 是可行的: + +```bash +scrcpy --otg --hid-keyboard # 只开启 HID 键盘 +scrcpy --otg --hid-mouse # 只开启 HID 鼠标 +scrcpy --otg --hid-keyboard --hid-mouse # 开启 HID 键盘 和 HID 鼠标 +# 为了方便,默认两者都开启 +scrcpy --otg # 开启 HID 键盘 和 HID 鼠标 +``` + +像 `--hid-keyboard` 和 `--hid-mouse` 一样,它只在设备通过 USB 连接时才有效,且目前仅在 Linux 上支持。 + + #### 文本注入偏好 输入文字的时候,系统会产生两种[事件][textevents]: From a1967b4dfdc01e70a29e7d679f425ab413c90aa4 Mon Sep 17 00:00:00 2001 From: Cccc_owo <47687154+Cccc-owo@users.noreply.github.com> Date: Sun, 30 Jan 2022 11:02:16 +0800 Subject: [PATCH 0378/1133] Update FAQ.zh-Hans.md to v1.22 PR #2989 Signed-off-by: Romain Vimont --- FAQ.md | 2 +- FAQ.zh-Hans.md | 74 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/FAQ.md b/FAQ.md index 6b76190d..5829136d 100644 --- a/FAQ.md +++ b/FAQ.md @@ -305,4 +305,4 @@ This FAQ is available in other languages: - [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) + - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md) diff --git a/FAQ.zh-Hans.md b/FAQ.zh-Hans.md index 136b5f2e..23674eed 100644 --- a/FAQ.zh-Hans.md +++ b/FAQ.zh-Hans.md @@ -1,7 +1,12 @@ -只有原版的[FAQ](FAQ.md)会保持更新。 -本文根据[d6aaa5]翻译。 +_Only the original [FAQ.md](FAQ.md) is guaranteed to be up-to-date._ -[d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md +_只有原版的 [FAQ.md](FAQ.md)是保证最新的。_ + +Current version is based on [28054cd] + +本文根据[28054cd]进行翻译。 + +[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md # 常见问题 @@ -9,11 +14,11 @@ ## `adb` 相关问题 -`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果`adb` 执行失败了, scrcpy 就无法工作。 +`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果 `adb` 执行失败了, scrcpy 就无法工作。 在这种情况中,将会输出这个错误: -> ERROR: "adb push" returned with value 1 +> ERROR: "adb get-serialno" returned with value 1 这通常不是 _scrcpy_ 的bug,而是你的环境的问题。 @@ -33,28 +38,37 @@ adb devices ### 设备未授权 -参见这里 [stackoverflow][device-unauthorized]. + +> error: device unauthorized. +> This adb server's $ADB_VENDOR_KEYS is not set +> Try 'adb kill-server' if that seems wrong. +> Otherwise check for a confirmation dialog on your device. + +连接时,在设备上应该会打开一个弹出窗口。 您必须授权 USB 调试。 + +如果没有打开,参见[stackoverflow][device-unauthorized]. [device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized ### 未检测到设备 -> adb: error: failed to get feature set: no devices/emulators found +> error: no devices/emulators found 确认已经正确启用 [adb debugging][enable-adb]. -如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上). +如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver]. [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [drivers]: https://developer.android.com/studio/run/oem-usb.html +[google-usb-driver]: https://developer.android.com/studio/run/win-usb ### 已连接多个设备 如果连接了多个设备,您将遇到以下错误: -> adb: error: failed to get feature set: more than one device/emulator +> error: more than one device/emulator 必须提供要镜像的设备的标识符: @@ -90,19 +104,19 @@ scrcpy ### 设备断开连接 如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。 -请尝试使用另一条USB线或者电脑上的另一个USB接口。 -请参看 [#281] 和 [#283]。 + +请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。 [#281]: https://github.com/Genymobile/scrcpy/issues/281 [#283]: https://github.com/Genymobile/scrcpy/issues/283 + ## 控制相关问题 ### 鼠标和键盘不起作用 在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。 - 在开发者选项中,打开: > **USB调试 (安全设置)** @@ -115,10 +129,12 @@ scrcpy 可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。 +自 Linux 上的 scrcpy v1.20 之后,可以模拟[物理键盘][hid] (HID)。 [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 ## 客户端相关问题 @@ -129,7 +145,6 @@ scrcpy [#40]: https://github.com/Genymobile/scrcpy/issues/40 - 为了提升降尺度的质量,如果渲染器是OpenGL并且支持mip映射,就会自动开启三线性过滤。 在Windows上,你可能希望强制使用OpenGL: @@ -177,6 +192,7 @@ scrcpy ## 崩溃 ### 异常 + 可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码: > ``` @@ -204,12 +220,40 @@ scrcpy -m 1024 scrcpy -m 800 ``` +自 scrcpy v1.22以来,scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。 + 你也可以尝试另一种 [编码器](README.md#encoder)。 +如果您在 Android 12 上遇到此异常,则只需升级到 scrcpy >= 1.18 (见 [#2129]): + +``` +> ERROR: Exception on thread Thread[main,5,main] +java.lang.AssertionError: java.lang.reflect.InvocationTargetException + at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75) + ... +Caused by: java.lang.reflect.InvocationTargetException + at java.lang.reflect.Method.invoke(Native Method) + at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73) + ... 7 more +Caused by: java.lang.IllegalArgumentException: displayToken must not be null + at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067) + at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147) + ... 9 more +``` + +[#2129]: https://github.com/Genymobile/scrcpy/issues/2129 + + ## Windows命令行 -一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`: +从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如: + +``` +scrcpy --record file.mkv +``` + +您也可以打开终端并手动转到 scrcpy 文件夹: 1. 按下 Windows+r,打开一个对话框。 2. 输入 `cmd` 并按 Enter,这样就打开了一个终端。 @@ -233,7 +277,7 @@ scrcpy -m 800 scrcpy --prefer-text --turn-screen-off --stay-awake ``` -然后双击刚刚创建的文件。 +然后只需双击刚刚创建的文件。 你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。 From 22d9f0faf4fe22e6316450041585a2cdd04d0eca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Feb 2022 21:09:02 +0100 Subject: [PATCH 0379/1133] Fix comment typo --- app/src/adb_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/adb_parser.c b/app/src/adb_parser.c index 5ac21ede..d660aaab 100644 --- a/app/src/adb_parser.c +++ b/app/src/adb_parser.c @@ -8,7 +8,7 @@ static char * sc_adb_parse_device_ip_from_line(char *line, size_t len) { - // One line from "ip route" looks lile: + // One line from "ip route" looks like: // "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x" // Get the location of the device name (index of "wlan0" in the example) From c8d0f5cdeb75cde4f5cff9a75daf000f15077acc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Feb 2022 21:28:26 +0100 Subject: [PATCH 0380/1133] Fix sc_str_truncate() documentation The function was initially implemented to truncate lines, but was later generalized to accept custom delimiters. The whole documentation has not been updated accordingly. Refs 9619ade706824a92ea387bdc9d0d27816bf79da5 --- app/src/util/str.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/util/str.h b/app/src/util/str.h index b81764ef..dfe0cb30 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -106,10 +106,10 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); /** * Truncate the data after any of the characters from `endchars` * - * An '\0' is always written at the end of the data, even if no newline - * character is encountered. + * An '\0' is always written at the end of the data string, even if no + * character from `endchars` is encountered. * - * Return the size of the resulting line. + * Return the size of the resulting string (as strlen() would return). */ size_t sc_str_truncate(char *data, size_t len, const char *endchars); From 9b4360b6b8e1ba4234f5b11b8217379faafcb3b7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Feb 2022 21:35:02 +0100 Subject: [PATCH 0381/1133] Add warning in function documentation The function parsing "ip route" output modifies the input buffer to tokenize in place. This must be mentioned in the function documentation. --- app/src/adb_parser.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/adb_parser.h b/app/src/adb_parser.h index 79f26631..ed7363d1 100644 --- a/app/src/adb_parser.h +++ b/app/src/adb_parser.h @@ -7,6 +7,8 @@ /** * Parse the ip from the output of `adb shell ip route` + * + * Warning: this function modifies the buffer for optimization purposes. */ char * sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len); From f02f2135cdd03f174bba6ce18ee7840469111378 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Feb 2022 21:36:37 +0100 Subject: [PATCH 0382/1133] Fix include for standard library header --- app/src/adb_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/adb_parser.h b/app/src/adb_parser.h index ed7363d1..bce5126c 100644 --- a/app/src/adb_parser.h +++ b/app/src/adb_parser.h @@ -3,7 +3,7 @@ #include "common.h" -#include "stddef.h" +#include /** * Parse the ip from the output of `adb shell ip route` From c0a75ca7462b6c22847e8cefe5c25aa410591b79 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jan 2022 23:10:24 +0100 Subject: [PATCH 0383/1133] Downscale and retry also on early MediaCodec error The new retry mechanism with a lower definition only worked if the error occurred during encode(). For example: java.lang.IllegalStateException at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:3452) at com.genymobile.scrcpy.ScreenEncoder.encode(ScreenEncoder.java:114) at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:95) at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:61) at com.genymobile.scrcpy.Server.scrcpy(Server.java:80) at com.genymobile.scrcpy.Server.main(Server.java:255) However, MediaCodec may also fail before encoding, during configure() or start(). For example: android.media.MediaCodec$CodecException: Error 0xfffffc0e at android.media.MediaCodec.native_configure(Native Method) at android.media.MediaCodec.configure(MediaCodec.java:1956) at android.media.MediaCodec.configure(MediaCodec.java:1885) at com.genymobile.scrcpy.ScreenEncoder.configure(ScreenEncoder.java:158) at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:68) at com.genymobile.scrcpy.Server.scrcpy(Server.java:28) at com.genymobile.scrcpy.Server.main(Server.java:110) Also downscale and retry in these cases. Refs #2947 Refs #2988 PR #2990 --- .../com/genymobile/scrcpy/ScreenEncoder.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 4c23dd92..79efc17c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -89,13 +89,15 @@ public class ScreenEncoder implements Device.RotationListener { Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); int layerStack = device.getLayerStack(); - setSize(format, videoRect.width(), videoRect.height()); - configure(codec, format); - Surface surface = codec.createInputSurface(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); - codec.start(); + + Surface surface = null; try { + configure(codec, format); + surface = codec.createInputSurface(); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + codec.start(); + alive = encode(codec, fd); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); @@ -119,7 +121,9 @@ public class ScreenEncoder implements Device.RotationListener { } finally { destroyDisplay(display); codec.release(); - surface.release(); + if (surface != null) { + surface.release(); + } } } while (alive); } finally { From 0bf7e4ddc42dc73d43666f58dd10edf89c62ae66 Mon Sep 17 00:00:00 2001 From: CennoxX Date: Wed, 2 Feb 2022 11:49:34 +0100 Subject: [PATCH 0384/1133] Add missing spaces in help PR #2994 Signed-off-by: Romain Vimont --- app/src/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 5587b2d8..7567a10b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -288,7 +288,7 @@ static const struct sc_option options[] = { "LAlt, LSuper or RSuper toggle the mouse capture mode, to give " "control of the mouse back to the computer.\n" "If any of --hid-keyboard or --hid-mouse is set, only enable " - "keyboard or mouse respectively, otherwise enable both." + "keyboard or mouse respectively, otherwise enable both.\n" "It may only work over USB, and is currently only supported " "on Linux.\n" "See --hid-keyboard and --hid-mouse.", @@ -309,7 +309,7 @@ static const struct sc_option options[] = { { .longopt_id = OPT_PREFER_TEXT, .longopt = "prefer-text", - .text = "Inject alpha characters and space as text events instead of" + .text = "Inject alpha characters and space as text events instead of " "key events.\n" "This avoids issues when combining multiple keys to enter a " "special character, but breaks the expected behavior of alpha " From 0080d0b0ff5a8b91430e12942ba8bb8e66e9e84b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 2 Feb 2022 19:27:41 +0100 Subject: [PATCH 0385/1133] Use sc_ prefix for decoder --- app/src/decoder.c | 55 ++++++++++++++++++++++++----------------------- app/src/decoder.h | 14 ++++++------ app/src/scrcpy.c | 10 ++++----- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index a20986dd..9424ec1d 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -9,10 +9,10 @@ #include "util/log.h" /** Downcast packet_sink to decoder */ -#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink) +#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink) static void -decoder_close_first_sinks(struct decoder *decoder, unsigned count) { +sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) { while (count) { struct sc_frame_sink *sink = decoder->sinks[--count]; sink->ops->close(sink); @@ -20,17 +20,17 @@ decoder_close_first_sinks(struct decoder *decoder, unsigned count) { } static inline void -decoder_close_sinks(struct decoder *decoder) { - decoder_close_first_sinks(decoder, decoder->sink_count); +sc_decoder_close_sinks(struct sc_decoder *decoder) { + sc_decoder_close_first_sinks(decoder, decoder->sink_count); } static bool -decoder_open_sinks(struct decoder *decoder) { +sc_decoder_open_sinks(struct sc_decoder *decoder) { for (unsigned i = 0; i < decoder->sink_count; ++i) { struct sc_frame_sink *sink = decoder->sinks[i]; if (!sink->ops->open(sink)) { LOGE("Could not open frame sink %d", i); - decoder_close_first_sinks(decoder, i); + sc_decoder_close_first_sinks(decoder, i); return false; } } @@ -39,7 +39,7 @@ decoder_open_sinks(struct decoder *decoder) { } static bool -decoder_open(struct decoder *decoder, const AVCodec *codec) { +sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); if (!decoder->codec_ctx) { LOG_OOM(); @@ -62,7 +62,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { return false; } - if (!decoder_open_sinks(decoder)) { + if (!sc_decoder_open_sinks(decoder)) { LOGE("Could not open decoder sinks"); av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); @@ -74,15 +74,15 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) { } static void -decoder_close(struct decoder *decoder) { - decoder_close_sinks(decoder); +sc_decoder_close(struct sc_decoder *decoder) { + sc_decoder_close_sinks(decoder); av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); } static bool -push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) { +push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) { for (unsigned i = 0; i < decoder->sink_count; ++i) { struct sc_frame_sink *sink = decoder->sinks[i]; if (!sink->ops->push(sink, frame)) { @@ -95,7 +95,7 @@ push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) { } static bool -decoder_push(struct decoder *decoder, const AVPacket *packet) { +sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; if (is_config) { // nothing to do @@ -124,39 +124,40 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) { } static bool -decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { - struct decoder *decoder = DOWNCAST(sink); - return decoder_open(decoder, codec); +sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { + struct sc_decoder *decoder = DOWNCAST(sink); + return sc_decoder_open(decoder, codec); } static void -decoder_packet_sink_close(struct sc_packet_sink *sink) { - struct decoder *decoder = DOWNCAST(sink); - decoder_close(decoder); +sc_decoder_packet_sink_close(struct sc_packet_sink *sink) { + struct sc_decoder *decoder = DOWNCAST(sink); + sc_decoder_close(decoder); } static bool -decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { - struct decoder *decoder = DOWNCAST(sink); - return decoder_push(decoder, packet); +sc_decoder_packet_sink_push(struct sc_packet_sink *sink, + const AVPacket *packet) { + struct sc_decoder *decoder = DOWNCAST(sink); + return sc_decoder_push(decoder, packet); } void -decoder_init(struct decoder *decoder) { +sc_decoder_init(struct sc_decoder *decoder) { decoder->sink_count = 0; static const struct sc_packet_sink_ops ops = { - .open = decoder_packet_sink_open, - .close = decoder_packet_sink_close, - .push = decoder_packet_sink_push, + .open = sc_decoder_packet_sink_open, + .close = sc_decoder_packet_sink_close, + .push = sc_decoder_packet_sink_push, }; decoder->packet_sink.ops = &ops; } void -decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) { - assert(decoder->sink_count < DECODER_MAX_SINKS); +sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) { + assert(decoder->sink_count < SC_DECODER_MAX_SINKS); assert(sink); assert(sink->ops); decoder->sinks[decoder->sink_count++] = sink; diff --git a/app/src/decoder.h b/app/src/decoder.h index e2972cb1..16adc5ec 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -1,5 +1,5 @@ -#ifndef DECODER_H -#define DECODER_H +#ifndef SC_DECODER_H +#define SC_DECODER_H #include "common.h" @@ -9,12 +9,12 @@ #include #include -#define DECODER_MAX_SINKS 2 +#define SC_DECODER_MAX_SINKS 2 -struct decoder { +struct sc_decoder { struct sc_packet_sink packet_sink; // packet sink trait - struct sc_frame_sink *sinks[DECODER_MAX_SINKS]; + struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS]; unsigned sink_count; AVCodecContext *codec_ctx; @@ -22,9 +22,9 @@ struct decoder { }; void -decoder_init(struct decoder *decoder); +sc_decoder_init(struct sc_decoder *decoder); void -decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink); +sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5f67f7f8..9efae799 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -40,7 +40,7 @@ struct scrcpy { struct sc_server server; struct sc_screen screen; struct stream stream; - struct decoder decoder; + struct sc_decoder decoder; struct recorder recorder; #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; @@ -371,13 +371,13 @@ scrcpy(struct scrcpy_options *options) { file_pusher_initialized = true; } - struct decoder *dec = NULL; + struct sc_decoder *dec = NULL; bool needs_decoder = options->display; #ifdef HAVE_V4L2 needs_decoder |= !!options->v4l2_device; #endif if (needs_decoder) { - decoder_init(&s->decoder); + sc_decoder_init(&s->decoder); dec = &s->decoder; } @@ -608,7 +608,7 @@ aoa_hid_end: } screen_initialized = true; - decoder_add_sink(&s->decoder, &s->screen.frame_sink); + sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink); } #ifdef HAVE_V4L2 @@ -618,7 +618,7 @@ aoa_hid_end: goto end; } - decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); + sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); v4l2_sink_initialized = true; } From 4ee62abe1d7fc14fa4812ed40cf7e2d6577ba962 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 2 Feb 2022 19:37:28 +0100 Subject: [PATCH 0386/1133] Use sc_ prefix for recorder --- app/src/recorder.c | 94 +++++++++++++++++++++++----------------------- app/src/recorder.h | 23 ++++++------ app/src/scrcpy.c | 14 +++---- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 74cfce07..2a82e172 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -9,7 +9,7 @@ #include "util/str.h" /** Downcast packet_sink to recorder */ -#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) +#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink) static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us @@ -30,9 +30,9 @@ find_muxer(const char *name) { return oformat; } -static struct record_packet * -record_packet_new(const AVPacket *packet) { - struct record_packet *rec = malloc(sizeof(*rec)); +static struct sc_record_packet * +sc_record_packet_new(const AVPacket *packet) { + struct sc_record_packet *rec = malloc(sizeof(*rec)); if (!rec) { LOG_OOM(); return NULL; @@ -54,22 +54,22 @@ record_packet_new(const AVPacket *packet) { } static void -record_packet_delete(struct record_packet *rec) { +sc_record_packet_delete(struct sc_record_packet *rec) { av_packet_free(&rec->packet); free(rec); } static void -recorder_queue_clear(struct recorder_queue *queue) { +sc_recorder_queue_clear(struct sc_recorder_queue *queue) { while (!sc_queue_is_empty(queue)) { - struct record_packet *rec; + struct sc_record_packet *rec; sc_queue_take(queue, next, &rec); - record_packet_delete(rec); + sc_record_packet_delete(rec); } } static const char * -recorder_get_format_name(enum sc_record_format format) { +sc_recorder_get_format_name(enum sc_record_format format) { switch (format) { case SC_RECORD_FORMAT_MP4: return "mp4"; case SC_RECORD_FORMAT_MKV: return "matroska"; @@ -78,7 +78,7 @@ recorder_get_format_name(enum sc_record_format format) { } static bool -recorder_write_header(struct recorder *recorder, const AVPacket *packet) { +sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) { AVStream *ostream = recorder->ctx->streams[0]; uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); @@ -103,19 +103,19 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) { } static void -recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) { +sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) { AVStream *ostream = recorder->ctx->streams[0]; av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); } static bool -recorder_write(struct recorder *recorder, AVPacket *packet) { +sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) { if (!recorder->header_written) { if (packet->pts != AV_NOPTS_VALUE) { LOGE("The first packet is not a config packet"); return false; } - bool ok = recorder_write_header(recorder, packet); + bool ok = sc_recorder_write_header(recorder, packet); if (!ok) { return false; } @@ -128,13 +128,13 @@ recorder_write(struct recorder *recorder, AVPacket *packet) { return true; } - recorder_rescale_packet(recorder, packet); + sc_recorder_rescale_packet(recorder, packet); return av_write_frame(recorder->ctx, packet) >= 0; } static int run_recorder(void *data) { - struct recorder *recorder = data; + struct sc_recorder *recorder = data; for (;;) { sc_mutex_lock(&recorder->mutex); @@ -148,29 +148,29 @@ run_recorder(void *data) { if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { sc_mutex_unlock(&recorder->mutex); - struct record_packet *last = recorder->previous; + struct sc_record_packet *last = recorder->previous; if (last) { // assign an arbitrary duration to the last packet last->packet->duration = 100000; - bool ok = recorder_write(recorder, last->packet); + bool ok = sc_recorder_write(recorder, last->packet); if (!ok) { // failing to write the last frame is not very serious, no // future frame may depend on it, so the resulting file // will still be valid LOGW("Could not record last packet"); } - record_packet_delete(last); + sc_record_packet_delete(last); } break; } - struct record_packet *rec; + struct sc_record_packet *rec; sc_queue_take(&recorder->queue, next, &rec); sc_mutex_unlock(&recorder->mutex); // recorder->previous is only written from this thread, no need to lock - struct record_packet *previous = recorder->previous; + struct sc_record_packet *previous = recorder->previous; recorder->previous = rec; if (!previous) { @@ -186,15 +186,15 @@ run_recorder(void *data) { rec->packet->pts - previous->packet->pts; } - bool ok = recorder_write(recorder, previous->packet); - record_packet_delete(previous); + bool ok = sc_recorder_write(recorder, previous->packet); + sc_record_packet_delete(previous); if (!ok) { LOGE("Could not record packet"); sc_mutex_lock(&recorder->mutex); recorder->failed = true; // discard pending packets - recorder_queue_clear(&recorder->queue); + sc_recorder_queue_clear(&recorder->queue); sc_mutex_unlock(&recorder->mutex); break; } @@ -216,7 +216,7 @@ run_recorder(void *data) { if (recorder->failed) { LOGE("Recording failed to %s", recorder->filename); } else { - const char *format_name = recorder_get_format_name(recorder->format); + const char *format_name = sc_recorder_get_format_name(recorder->format); LOGI("Recording complete to %s file: %s", format_name, recorder->filename); } @@ -227,7 +227,7 @@ run_recorder(void *data) { } static bool -recorder_open(struct recorder *recorder, const AVCodec *input_codec) { +sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { bool ok = sc_mutex_init(&recorder->mutex); if (!ok) { return false; @@ -244,7 +244,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) { recorder->header_written = false; recorder->previous = NULL; - const char *format_name = recorder_get_format_name(recorder->format); + const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); const AVOutputFormat *format = find_muxer(format_name); if (!format) { @@ -311,7 +311,7 @@ error_mutex_destroy: } static void -recorder_close(struct recorder *recorder) { +sc_recorder_close(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); recorder->stopped = true; sc_cond_signal(&recorder->queue_cond); @@ -326,7 +326,7 @@ recorder_close(struct recorder *recorder) { } static bool -recorder_push(struct recorder *recorder, const AVPacket *packet) { +sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) { sc_mutex_lock(&recorder->mutex); assert(!recorder->stopped); @@ -336,7 +336,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { return false; } - struct record_packet *rec = record_packet_new(packet); + struct sc_record_packet *rec = sc_record_packet_new(packet); if (!rec) { LOG_OOM(); sc_mutex_unlock(&recorder->mutex); @@ -351,28 +351,30 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) { } static bool -recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { - struct recorder *recorder = DOWNCAST(sink); - return recorder_open(recorder, codec); +sc_recorder_packet_sink_open(struct sc_packet_sink *sink, + const AVCodec *codec) { + struct sc_recorder *recorder = DOWNCAST(sink); + return sc_recorder_open(recorder, codec); } static void -recorder_packet_sink_close(struct sc_packet_sink *sink) { - struct recorder *recorder = DOWNCAST(sink); - recorder_close(recorder); +sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST(sink); + sc_recorder_close(recorder); } static bool -recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { - struct recorder *recorder = DOWNCAST(sink); - return recorder_push(recorder, packet); +sc_recorder_packet_sink_push(struct sc_packet_sink *sink, + const AVPacket *packet) { + struct sc_recorder *recorder = DOWNCAST(sink); + return sc_recorder_push(recorder, packet); } bool -recorder_init(struct recorder *recorder, - const char *filename, - enum sc_record_format format, - struct sc_size declared_frame_size) { +sc_recorder_init(struct sc_recorder *recorder, + const char *filename, + enum sc_record_format format, + struct sc_size declared_frame_size) { recorder->filename = strdup(filename); if (!recorder->filename) { LOG_OOM(); @@ -383,9 +385,9 @@ recorder_init(struct recorder *recorder, recorder->declared_frame_size = declared_frame_size; static const struct sc_packet_sink_ops ops = { - .open = recorder_packet_sink_open, - .close = recorder_packet_sink_close, - .push = recorder_packet_sink_push, + .open = sc_recorder_packet_sink_open, + .close = sc_recorder_packet_sink_close, + .push = sc_recorder_packet_sink_push, }; recorder->packet_sink.ops = &ops; @@ -394,6 +396,6 @@ recorder_init(struct recorder *recorder, } void -recorder_destroy(struct recorder *recorder) { +sc_recorder_destroy(struct sc_recorder *recorder) { free(recorder->filename); } diff --git a/app/src/recorder.h b/app/src/recorder.h index 27ea5526..373278e6 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -1,5 +1,5 @@ -#ifndef RECORDER_H -#define RECORDER_H +#ifndef SC_RECORDER_H +#define SC_RECORDER_H #include "common.h" @@ -12,14 +12,14 @@ #include "util/queue.h" #include "util/thread.h" -struct record_packet { +struct sc_record_packet { AVPacket *packet; - struct record_packet *next; + struct sc_record_packet *next; }; -struct recorder_queue SC_QUEUE(struct record_packet); +struct sc_recorder_queue SC_QUEUE(struct sc_record_packet); -struct recorder { +struct sc_recorder { struct sc_packet_sink packet_sink; // packet sink trait char *filename; @@ -33,20 +33,21 @@ struct recorder { sc_cond queue_cond; bool stopped; // set on recorder_close() bool failed; // set on packet write failure - struct recorder_queue queue; + struct sc_recorder_queue queue; // we can write a packet only once we received the next one so that we can // set its duration (next_pts - current_pts) // "previous" is only accessed from the recorder thread, so it does not // need to be protected by the mutex - struct record_packet *previous; + struct sc_record_packet *previous; }; bool -recorder_init(struct recorder *recorder, const char *filename, - enum sc_record_format format, struct sc_size declared_frame_size); +sc_recorder_init(struct sc_recorder *recorder, const char *filename, + enum sc_record_format format, + struct sc_size declared_frame_size); void -recorder_destroy(struct recorder *recorder); +sc_recorder_destroy(struct sc_recorder *recorder); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9efae799..91ee0a2a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -41,7 +41,7 @@ struct scrcpy { struct sc_screen screen; struct stream stream; struct sc_decoder decoder; - struct recorder recorder; + struct sc_recorder recorder; #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; #endif @@ -381,12 +381,12 @@ scrcpy(struct scrcpy_options *options) { dec = &s->decoder; } - struct recorder *rec = NULL; + struct sc_recorder *rec = NULL; if (options->record_filename) { - if (!recorder_init(&s->recorder, - options->record_filename, - options->record_format, - info->frame_size)) { + if (!sc_recorder_init(&s->recorder, + options->record_filename, + options->record_format, + info->frame_size)) { goto end; } rec = &s->recorder; @@ -708,7 +708,7 @@ end: } if (recorder_initialized) { - recorder_destroy(&s->recorder); + sc_recorder_destroy(&s->recorder); } if (file_pusher_initialized) { From 7dec225cebbf208cf1d607bd45fb49e3a9fa1167 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 2 Feb 2022 20:51:07 +0100 Subject: [PATCH 0387/1133] Rename stream to sc_demuxer For consistency with recorder and decoder, name the component which demuxes a "demuxer". And add the missing sc_ prefix. --- app/meson.build | 2 +- app/src/{stream.c => demuxer.c} | 142 ++++++++++++++++---------------- app/src/demuxer.h | 51 ++++++++++++ app/src/scrcpy.c | 38 ++++----- 4 files changed, 142 insertions(+), 91 deletions(-) rename app/src/{stream.c => demuxer.c} (55%) create mode 100644 app/src/demuxer.h diff --git a/app/meson.build b/app/meson.build index 7f675035..1d31a442 100644 --- a/app/meson.build +++ b/app/meson.build @@ -9,6 +9,7 @@ src = [ 'src/control_msg.c', 'src/controller.c', 'src/decoder.c', + 'src/demuxer.c', 'src/device_msg.c', 'src/icon.c', 'src/file_pusher.c', @@ -24,7 +25,6 @@ src = [ 'src/scrcpy.c', 'src/screen.c', 'src/server.c', - 'src/stream.c', 'src/video_buffer.c', 'src/util/acksync.c', 'src/util/file.c', diff --git a/app/src/stream.c b/app/src/demuxer.c similarity index 55% rename from app/src/stream.c rename to app/src/demuxer.c index c873c4ad..3dd62491 100644 --- a/app/src/stream.c +++ b/app/src/demuxer.c @@ -1,4 +1,4 @@ -#include "stream.h" +#include "demuxer.h" #include #include @@ -16,7 +16,7 @@ #define NO_PTS UINT64_C(-1) static bool -stream_recv_packet(struct stream *stream, AVPacket *packet) { +sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // The video stream contains raw packets, without time information. When we // record, we retrieve the timestamps separately, from a "meta" header // added by the server before each raw packet. @@ -30,7 +30,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) { // It is followed by bytes containing the packet/frame. uint8_t header[HEADER_SIZE]; - ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE); + ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE); if (r < HEADER_SIZE) { return false; } @@ -45,7 +45,7 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) { return false; } - r = net_recv_all(stream->socket, packet->data, len); + r = net_recv_all(demuxer->socket, packet->data, len); if (r < 0 || ((uint32_t) r) < len) { av_packet_unref(packet); return false; @@ -57,9 +57,9 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) { } static bool -push_packet_to_sinks(struct stream *stream, const AVPacket *packet) { - for (unsigned i = 0; i < stream->sink_count; ++i) { - struct sc_packet_sink *sink = stream->sinks[i]; +push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { + for (unsigned i = 0; i < demuxer->sink_count; ++i) { + struct sc_packet_sink *sink = demuxer->sinks[i]; if (!sink->ops->push(sink, packet)) { LOGE("Could not send config packet to sink %d", i); return false; @@ -70,12 +70,12 @@ push_packet_to_sinks(struct stream *stream, const AVPacket *packet) { } static bool -stream_parse(struct stream *stream, AVPacket *packet) { +sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) { uint8_t *in_data = packet->data; int in_len = packet->size; uint8_t *out_data = NULL; int out_len = 0; - int r = av_parser_parse2(stream->parser, stream->codec_ctx, + int r = av_parser_parse2(demuxer->parser, demuxer->codec_ctx, &out_data, &out_len, in_data, in_len, AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); @@ -84,13 +84,13 @@ stream_parse(struct stream *stream, AVPacket *packet) { (void) r; assert(out_len == in_len); - if (stream->parser->key_frame == 1) { + if (demuxer->parser->key_frame == 1) { packet->flags |= AV_PKT_FLAG_KEY; } packet->dts = packet->pts; - bool ok = push_packet_to_sinks(stream, packet); + bool ok = push_packet_to_sinks(demuxer, packet); if (!ok) { LOGE("Could not process packet"); return false; @@ -100,57 +100,57 @@ stream_parse(struct stream *stream, AVPacket *packet) { } static bool -stream_push_packet(struct stream *stream, AVPacket *packet) { +sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; // A config packet must not be decoded immediately (it contains no // frame); instead, it must be concatenated with the future data packet. - if (stream->pending || is_config) { + if (demuxer->pending || is_config) { size_t offset; - if (stream->pending) { - offset = stream->pending->size; - if (av_grow_packet(stream->pending, packet->size)) { + if (demuxer->pending) { + offset = demuxer->pending->size; + if (av_grow_packet(demuxer->pending, packet->size)) { LOG_OOM(); return false; } } else { offset = 0; - stream->pending = av_packet_alloc(); - if (!stream->pending) { + demuxer->pending = av_packet_alloc(); + if (!demuxer->pending) { LOG_OOM(); return false; } - if (av_new_packet(stream->pending, packet->size)) { + if (av_new_packet(demuxer->pending, packet->size)) { LOG_OOM(); - av_packet_free(&stream->pending); + av_packet_free(&demuxer->pending); return false; } } - memcpy(stream->pending->data + offset, packet->data, packet->size); + memcpy(demuxer->pending->data + offset, packet->data, packet->size); if (!is_config) { // prepare the concat packet to send to the decoder - stream->pending->pts = packet->pts; - stream->pending->dts = packet->dts; - stream->pending->flags = packet->flags; - packet = stream->pending; + demuxer->pending->pts = packet->pts; + demuxer->pending->dts = packet->dts; + demuxer->pending->flags = packet->flags; + packet = demuxer->pending; } } if (is_config) { // config packet - bool ok = push_packet_to_sinks(stream, packet); + bool ok = push_packet_to_sinks(demuxer, packet); if (!ok) { return false; } } else { // data packet - bool ok = stream_parse(stream, packet); + bool ok = sc_demuxer_parse(demuxer, packet); - if (stream->pending) { + if (demuxer->pending) { // the pending packet must be discarded (consumed or error) - av_packet_free(&stream->pending); + av_packet_free(&demuxer->pending); } if (!ok) { @@ -161,25 +161,25 @@ stream_push_packet(struct stream *stream, AVPacket *packet) { } static void -stream_close_first_sinks(struct stream *stream, unsigned count) { +sc_demuxer_close_first_sinks(struct sc_demuxer *demuxer, unsigned count) { while (count) { - struct sc_packet_sink *sink = stream->sinks[--count]; + struct sc_packet_sink *sink = demuxer->sinks[--count]; sink->ops->close(sink); } } static inline void -stream_close_sinks(struct stream *stream) { - stream_close_first_sinks(stream, stream->sink_count); +sc_demuxer_close_sinks(struct sc_demuxer *demuxer) { + sc_demuxer_close_first_sinks(demuxer, demuxer->sink_count); } static bool -stream_open_sinks(struct stream *stream, const AVCodec *codec) { - for (unsigned i = 0; i < stream->sink_count; ++i) { - struct sc_packet_sink *sink = stream->sinks[i]; +sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) { + for (unsigned i = 0; i < demuxer->sink_count; ++i) { + struct sc_packet_sink *sink = demuxer->sinks[i]; if (!sink->ops->open(sink, codec)) { LOGE("Could not open packet sink %d", i); - stream_close_first_sinks(stream, i); + sc_demuxer_close_first_sinks(demuxer, i); return false; } } @@ -188,8 +188,8 @@ stream_open_sinks(struct stream *stream, const AVCodec *codec) { } static int -run_stream(void *data) { - struct stream *stream = data; +run_demuxer(void *data) { + struct sc_demuxer *demuxer = data; const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { @@ -197,26 +197,26 @@ run_stream(void *data) { goto end; } - stream->codec_ctx = avcodec_alloc_context3(codec); - if (!stream->codec_ctx) { + demuxer->codec_ctx = avcodec_alloc_context3(codec); + if (!demuxer->codec_ctx) { LOG_OOM(); goto end; } - if (!stream_open_sinks(stream, codec)) { - LOGE("Could not open stream sinks"); + if (!sc_demuxer_open_sinks(demuxer, codec)) { + LOGE("Could not open demuxer sinks"); goto finally_free_codec_ctx; } - stream->parser = av_parser_init(AV_CODEC_ID_H264); - if (!stream->parser) { + demuxer->parser = av_parser_init(AV_CODEC_ID_H264); + if (!demuxer->parser) { LOGE("Could not initialize parser"); goto finally_close_sinks; } // We must only pass complete frames to av_parser_parse2()! // It's more complicated, but this allows to reduce the latency by 1 frame! - stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; + demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; AVPacket *packet = av_packet_alloc(); if (!packet) { @@ -225,13 +225,13 @@ run_stream(void *data) { } for (;;) { - bool ok = stream_recv_packet(stream, packet); + bool ok = sc_demuxer_recv_packet(demuxer, packet); if (!ok) { // end of stream break; } - ok = stream_push_packet(stream, packet); + ok = sc_demuxer_push_packet(demuxer, packet); av_packet_unref(packet); if (!ok) { // cannot process packet (error already logged) @@ -241,58 +241,58 @@ run_stream(void *data) { LOGD("End of frames"); - if (stream->pending) { - av_packet_free(&stream->pending); + if (demuxer->pending) { + av_packet_free(&demuxer->pending); } av_packet_free(&packet); finally_close_parser: - av_parser_close(stream->parser); + av_parser_close(demuxer->parser); finally_close_sinks: - stream_close_sinks(stream); + sc_demuxer_close_sinks(demuxer); finally_free_codec_ctx: - avcodec_free_context(&stream->codec_ctx); + avcodec_free_context(&demuxer->codec_ctx); end: - stream->cbs->on_eos(stream, stream->cbs_userdata); + demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata); return 0; } void -stream_init(struct stream *stream, sc_socket socket, - const struct stream_callbacks *cbs, void *cbs_userdata) { - stream->socket = socket; - stream->pending = NULL; - stream->sink_count = 0; +sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, + const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) { + demuxer->socket = socket; + demuxer->pending = NULL; + demuxer->sink_count = 0; assert(cbs && cbs->on_eos); - stream->cbs = cbs; - stream->cbs_userdata = cbs_userdata; + demuxer->cbs = cbs; + demuxer->cbs_userdata = cbs_userdata; } void -stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) { - assert(stream->sink_count < STREAM_MAX_SINKS); +sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) { + assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS); assert(sink); assert(sink->ops); - stream->sinks[stream->sink_count++] = sink; + demuxer->sinks[demuxer->sink_count++] = sink; } bool -stream_start(struct stream *stream) { - LOGD("Starting stream thread"); +sc_demuxer_start(struct sc_demuxer *demuxer) { + LOGD("Starting demuxer thread"); - bool ok = - sc_thread_create(&stream->thread, run_stream, "scrcpy-stream", stream); + bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer", + demuxer); if (!ok) { - LOGC("Could not start stream thread"); + LOGC("Could not start demuxer thread"); return false; } return true; } void -stream_join(struct stream *stream) { - sc_thread_join(&stream->thread, NULL); +sc_demuxer_join(struct sc_demuxer *demuxer) { + sc_thread_join(&demuxer->thread, NULL); } diff --git a/app/src/demuxer.h b/app/src/demuxer.h new file mode 100644 index 00000000..11e20ad6 --- /dev/null +++ b/app/src/demuxer.h @@ -0,0 +1,51 @@ +#ifndef SC_DEMUXER_H +#define SC_DEMUXER_H + +#include "common.h" + +#include +#include +#include +#include + +#include "trait/packet_sink.h" +#include "util/net.h" +#include "util/thread.h" + +#define SC_DEMUXER_MAX_SINKS 2 + +struct sc_demuxer { + sc_socket socket; + sc_thread thread; + + struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS]; + unsigned sink_count; + + AVCodecContext *codec_ctx; + AVCodecParserContext *parser; + // successive packets may need to be concatenated, until a non-config + // packet is available + AVPacket *pending; + + const struct sc_demuxer_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_demuxer_callbacks { + void (*on_eos)(struct sc_demuxer *demuxer, void *userdata); +}; + +void +sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, + const struct sc_demuxer_callbacks *cbs, void *cbs_userdata); + +void +sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink); + +bool +sc_demuxer_start(struct sc_demuxer *demuxer); + +void +sc_demuxer_join(struct sc_demuxer *demuxer); + +#endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 91ee0a2a..270fe2b3 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -15,6 +15,7 @@ #include "controller.h" #include "decoder.h" +#include "demuxer.h" #include "events.h" #include "file_pusher.h" #include "keyboard_inject.h" @@ -22,7 +23,6 @@ #include "recorder.h" #include "screen.h" #include "server.h" -#include "stream.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/hid_keyboard.h" @@ -39,7 +39,7 @@ struct scrcpy { struct sc_server server; struct sc_screen screen; - struct stream stream; + struct sc_demuxer demuxer; struct sc_decoder decoder; struct sc_recorder recorder; #ifdef HAVE_V4L2 @@ -231,8 +231,8 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { } static void -stream_on_eos(struct stream *stream, void *userdata) { - (void) stream; +sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) { + (void) demuxer; (void) userdata; PUSH_EVENT(EVENT_STREAM_STOPPED); @@ -285,7 +285,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_V4L2 bool v4l2_sink_initialized = false; #endif - bool stream_started = false; + bool demuxer_started = false; #ifdef HAVE_USB bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; @@ -395,17 +395,17 @@ scrcpy(struct scrcpy_options *options) { av_log_set_callback(av_log_callback); - static const struct stream_callbacks stream_cbs = { - .on_eos = stream_on_eos, + static const struct sc_demuxer_callbacks demuxer_cbs = { + .on_eos = sc_demuxer_on_eos, }; - stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL); + sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); if (dec) { - stream_add_sink(&s->stream, &dec->packet_sink); + sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink); } if (rec) { - stream_add_sink(&s->stream, &rec->packet_sink); + sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink); } struct sc_controller *controller = NULL; @@ -625,21 +625,21 @@ aoa_hid_end: #endif // now we consumed the header values, the socket receives the video stream - // start the stream - if (!stream_start(&s->stream)) { + // start the demuxer + if (!sc_demuxer_start(&s->demuxer)) { goto end; } - stream_started = true; + demuxer_started = true; ret = event_loop(s); LOGD("quit..."); // Close the window immediately on closing, because screen_destroy() may - // only be called once the stream thread is joined (it may take time) + // only be called once the demuxer thread is joined (it may take time) sc_screen_hide_window(&s->screen); end: - // The stream is not stopped explicitly, because it will stop by itself on + // The demuxer is not stopped explicitly, because it will stop by itself on // end-of-stream #ifdef HAVE_USB if (aoa_hid_initialized) { @@ -671,10 +671,10 @@ end: sc_server_stop(&s->server); } - // now that the sockets are shutdown, the stream and controller are + // now that the sockets are shutdown, the demuxer and controller are // interrupted, we can join them - if (stream_started) { - stream_join(&s->stream); + if (demuxer_started) { + sc_demuxer_join(&s->demuxer); } #ifdef HAVE_V4L2 @@ -693,7 +693,7 @@ end: } #endif - // Destroy the screen only after the stream is guaranteed to be finished, + // Destroy the screen only after the demuxer is guaranteed to be finished, // because otherwise the screen could receive new frames after destruction if (screen_initialized) { sc_screen_join(&s->screen); From c460243ce2c21cfd89535ec7cd301684e8f3ee18 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 2 Feb 2022 21:03:24 +0100 Subject: [PATCH 0388/1133] Simplify demuxer Call the same push_packet_to_sinks() in all cases, and make sc_demuxer_parse() return void. --- app/src/demuxer.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 3dd62491..c683dfe0 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -69,7 +69,7 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { return true; } -static bool +static void sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) { uint8_t *in_data = packet->data; int in_len = packet->size; @@ -89,14 +89,6 @@ sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) { } packet->dts = packet->pts; - - bool ok = push_packet_to_sinks(demuxer, packet); - if (!ok) { - LOGE("Could not process packet"); - return false; - } - - return true; } static bool @@ -138,25 +130,23 @@ sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { } } - if (is_config) { - // config packet - bool ok = push_packet_to_sinks(demuxer, packet); - if (!ok) { - return false; - } - } else { + if (!is_config) { // data packet - bool ok = sc_demuxer_parse(demuxer, packet); + sc_demuxer_parse(demuxer, packet); + } - if (demuxer->pending) { - // the pending packet must be discarded (consumed or error) - av_packet_free(&demuxer->pending); - } + bool ok = push_packet_to_sinks(demuxer, packet); - if (!ok) { - return false; - } + if (!is_config && demuxer->pending) { + // the pending packet must be discarded (consumed or error) + av_packet_free(&demuxer->pending); } + + if (!ok) { + LOGE("Could not process packet"); + return false; + } + return true; } From 7810ca61b0022500a9b19b23fb1b163c032a90ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 3 Feb 2022 22:46:24 +0100 Subject: [PATCH 0389/1133] Move ADB code to adb/ --- app/meson.build | 8 ++++---- app/src/{ => adb}/adb.c | 0 app/src/{ => adb}/adb.h | 0 app/src/{ => adb}/adb_parser.c | 0 app/src/{ => adb}/adb_parser.h | 0 app/src/{ => adb}/adb_tunnel.c | 0 app/src/{ => adb}/adb_tunnel.h | 0 app/src/file_pusher.c | 2 +- app/src/file_pusher.h | 1 - app/src/server.c | 2 +- app/src/server.h | 3 +-- app/tests/test_adb_parser.c | 2 +- 12 files changed, 8 insertions(+), 10 deletions(-) rename app/src/{ => adb}/adb.c (100%) rename app/src/{ => adb}/adb.h (100%) rename app/src/{ => adb}/adb_parser.c (100%) rename app/src/{ => adb}/adb_parser.h (100%) rename app/src/{ => adb}/adb_tunnel.c (100%) rename app/src/{ => adb}/adb_tunnel.h (100%) diff --git a/app/meson.build b/app/meson.build index 1d31a442..b2c0bc55 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,8 +1,8 @@ src = [ 'src/main.c', - 'src/adb.c', - 'src/adb_parser.c', - 'src/adb_tunnel.c', + 'src/adb/adb.c', + 'src/adb/adb_parser.c', + 'src/adb/adb_tunnel.c', 'src/cli.c', 'src/clock.c', 'src/compat.c', @@ -221,7 +221,7 @@ if get_option('buildtype') == 'debug' tests = [ ['test_adb_parser', [ 'tests/test_adb_parser.c', - 'src/adb_parser.c', + 'src/adb/adb_parser.c', 'src/util/str.c', 'src/util/strbuf.c', ]], diff --git a/app/src/adb.c b/app/src/adb/adb.c similarity index 100% rename from app/src/adb.c rename to app/src/adb/adb.c diff --git a/app/src/adb.h b/app/src/adb/adb.h similarity index 100% rename from app/src/adb.h rename to app/src/adb/adb.h diff --git a/app/src/adb_parser.c b/app/src/adb/adb_parser.c similarity index 100% rename from app/src/adb_parser.c rename to app/src/adb/adb_parser.c diff --git a/app/src/adb_parser.h b/app/src/adb/adb_parser.h similarity index 100% rename from app/src/adb_parser.h rename to app/src/adb/adb_parser.h diff --git a/app/src/adb_tunnel.c b/app/src/adb/adb_tunnel.c similarity index 100% rename from app/src/adb_tunnel.c rename to app/src/adb/adb_tunnel.c diff --git a/app/src/adb_tunnel.h b/app/src/adb/adb_tunnel.h similarity index 100% rename from app/src/adb_tunnel.h rename to app/src/adb/adb_tunnel.h diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c index 738e3616..78ea48c2 100644 --- a/app/src/file_pusher.c +++ b/app/src/file_pusher.c @@ -3,7 +3,7 @@ #include #include -#include "adb.h" +#include "adb/adb.h" #include "util/log.h" #include "util/process_intr.h" diff --git a/app/src/file_pusher.h b/app/src/file_pusher.h index 56b107a0..0d934d6c 100644 --- a/app/src/file_pusher.h +++ b/app/src/file_pusher.h @@ -5,7 +5,6 @@ #include -#include "adb.h" #include "util/cbuf.h" #include "util/thread.h" #include "util/intr.h" diff --git a/app/src/server.c b/app/src/server.c index e5acef95..b7a28949 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -7,7 +7,7 @@ #include #include -#include "adb.h" +#include "adb/adb.h" #include "util/file.h" #include "util/log.h" #include "util/net_intr.h" diff --git a/app/src/server.h b/app/src/server.h index 89cdc2f4..1dff19a7 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -7,8 +7,7 @@ #include #include -#include "adb.h" -#include "adb_tunnel.h" +#include "adb/adb_tunnel.h" #include "coords.h" #include "options.h" #include "util/intr.h" diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index cb3abb0e..51e7d6d2 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -2,7 +2,7 @@ #include -#include "adb_parser.h" +#include "adb/adb_parser.h" static void test_get_ip_single_line() { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " From 9e3902f30c9132d275b5bd1a58cb174b7c96b3e9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 3 Feb 2022 23:04:01 +0100 Subject: [PATCH 0390/1133] Use sc_ prefix for adb --- app/src/adb/adb.c | 83 +++++++++++++++++++++------------------- app/src/adb/adb.h | 46 +++++++++++----------- app/src/adb/adb_tunnel.c | 17 ++++---- app/src/file_pusher.c | 4 +- app/src/server.c | 16 ++++---- 5 files changed, 85 insertions(+), 81 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 5bc4e4cc..215fc6be 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -158,7 +158,8 @@ process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, } static const char ** -adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) { +sc_adb_create_argv(const char *serial, const char *const adb_cmd[], + size_t len) { const char **argv = malloc((len + 4) * sizeof(*argv)); if (!argv) { LOG_OOM(); @@ -181,9 +182,9 @@ adb_create_argv(const char *serial, const char *const adb_cmd[], size_t len) { } static sc_pid -adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags, sc_pipe *pout) { - const char **argv = adb_create_argv(serial, adb_cmd, len); +sc_adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, + unsigned flags, sc_pipe *pout) { + const char **argv = sc_adb_create_argv(serial, adb_cmd, len); if (!argv) { return SC_PROCESS_NONE; } @@ -211,63 +212,63 @@ adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, } sc_pid -adb_execute(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags) { - return adb_execute_p(serial, adb_cmd, len, flags, NULL); +sc_adb_execute(const char *serial, const char *const adb_cmd[], size_t len, + unsigned flags) { + return sc_adb_execute_p(serial, adb_cmd, len, flags, NULL); } bool -adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, - const char *device_socket_name, unsigned flags) { +sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, + const char *device_socket_name, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"forward", local, remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb forward", flags); } bool -adb_forward_remove(struct sc_intr *intr, const char *serial, - uint16_t local_port, unsigned flags) { +sc_adb_forward_remove(struct sc_intr *intr, const char *serial, + uint16_t local_port, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); const char *const adb_cmd[] = {"forward", "--remove", local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb forward --remove", flags); } bool -adb_reverse(struct sc_intr *intr, const char *serial, - const char *device_socket_name, uint16_t local_port, - unsigned flags) { +sc_adb_reverse(struct sc_intr *intr, const char *serial, + const char *device_socket_name, uint16_t local_port, + unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", remote, local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb reverse", flags); } bool -adb_reverse_remove(struct sc_intr *intr, const char *serial, - const char *device_socket_name, unsigned flags) { +sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, + const char *device_socket_name, unsigned flags) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); const char *const adb_cmd[] = {"reverse", "--remove", remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb reverse --remove", flags); } bool -adb_push(struct sc_intr *intr, const char *serial, const char *local, - const char *remote, unsigned flags) { +sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, + const char *remote, unsigned flags) { #ifdef __WINDOWS__ // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) @@ -283,7 +284,7 @@ adb_push(struct sc_intr *intr, const char *serial, const char *local, #endif const char *const adb_cmd[] = {"push", local, remote}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ free((void *) remote); @@ -294,8 +295,8 @@ adb_push(struct sc_intr *intr, const char *serial, const char *local, } bool -adb_install(struct sc_intr *intr, const char *serial, const char *local, - unsigned flags) { +sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, + unsigned flags) { #ifdef __WINDOWS__ // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) @@ -306,7 +307,7 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local, #endif const char *const adb_cmd[] = {"install", "-r", local}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ free((void *) local); @@ -316,22 +317,23 @@ adb_install(struct sc_intr *intr, const char *serial, const char *local, } bool -adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, - unsigned flags) { +sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, + unsigned flags) { char port_string[5 + 1]; sprintf(port_string, "%" PRIu16, port); const char *const adb_cmd[] = {"tcpip", port_string}; - sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb tcpip", flags); } bool -adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { +sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { const char *const adb_cmd[] = {"connect", ip_port}; sc_pipe pout; - sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_pid pid = + sc_adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb connect\""); return false; @@ -364,23 +366,23 @@ adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { } bool -adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { +sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { const char *const adb_cmd[] = {"disconnect", ip_port}; size_t len = ip_port ? ARRAY_LEN(adb_cmd) : ARRAY_LEN(adb_cmd) - 1; - sc_pid pid = adb_execute(NULL, adb_cmd, len, flags); + sc_pid pid = sc_adb_execute(NULL, adb_cmd, len, flags); return process_check_success_intr(intr, pid, "adb disconnect", flags); } char * -adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, - unsigned flags) { +sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, + unsigned flags) { const char *const adb_cmd[] = {"shell", "getprop", prop}; sc_pipe pout; sc_pid pid = - adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb getprop\""); return NULL; @@ -405,11 +407,12 @@ adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, } char * -adb_get_serialno(struct sc_intr *intr, unsigned flags) { +sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { const char *const adb_cmd[] = {"get-serialno"}; sc_pipe pout; - sc_pid pid = adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_pid pid = + sc_adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb get-serialno\""); return NULL; @@ -434,11 +437,11 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) { } char * -adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { +sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { const char *const cmd[] = {"shell", "ip", "route"}; sc_pipe pout; - sc_pid pid = adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout); + sc_pid pid = sc_adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout); if (pid == SC_PROCESS_NONE) { LOGD("Could not execute \"ip route\""); return NULL; diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 4d1278cf..34fd7866 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -15,40 +15,40 @@ #define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR) sc_pid -adb_execute(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags); +sc_adb_execute(const char *serial, const char *const adb_cmd[], size_t len, + unsigned flags); bool -adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, - const char *device_socket_name, unsigned flags); +sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, + const char *device_socket_name, unsigned flags); bool -adb_forward_remove(struct sc_intr *intr, const char *serial, - uint16_t local_port, unsigned flags); +sc_adb_forward_remove(struct sc_intr *intr, const char *serial, + uint16_t local_port, unsigned flags); bool -adb_reverse(struct sc_intr *intr, const char *serial, - const char *device_socket_name, uint16_t local_port, - unsigned flags); +sc_adb_reverse(struct sc_intr *intr, const char *serial, + const char *device_socket_name, uint16_t local_port, + unsigned flags); bool -adb_reverse_remove(struct sc_intr *intr, const char *serial, - const char *device_socket_name, unsigned flags); +sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, + const char *device_socket_name, unsigned flags); bool -adb_push(struct sc_intr *intr, const char *serial, const char *local, - const char *remote, unsigned flags); +sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, + const char *remote, unsigned flags); bool -adb_install(struct sc_intr *intr, const char *serial, const char *local, - unsigned flags); +sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, + unsigned flags); /** * Execute `adb tcpip ` */ bool -adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, - unsigned flags); +sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, + unsigned flags); /** * Execute `adb connect ` @@ -56,7 +56,7 @@ adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, * `ip_port` may not be NULL. */ bool -adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); +sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); /** * Execute `adb disconnect []` @@ -65,14 +65,14 @@ adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); * Otherwise, execute `adb disconnect `. */ bool -adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); +sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); /** * Execute `adb getprop ` */ char * -adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, - unsigned flags); +sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, + unsigned flags); /** * Execute `adb get-serialno` @@ -80,7 +80,7 @@ adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, * Return the result, to be freed by the caller, or NULL on error. */ char * -adb_get_serialno(struct sc_intr *intr, unsigned flags); +sc_adb_get_serialno(struct sc_intr *intr, unsigned flags); /** * Attempt to retrieve the device IP @@ -89,6 +89,6 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags); * caller, or NULL on error. */ char * -adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); +sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); #endif diff --git a/app/src/adb/adb_tunnel.c b/app/src/adb/adb_tunnel.c index 00552597..c613bc2b 100644 --- a/app/src/adb/adb_tunnel.c +++ b/app/src/adb/adb_tunnel.c @@ -20,8 +20,8 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, struct sc_port_range port_range) { uint16_t port = port_range.first; for (;;) { - if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port, - SC_ADB_NO_STDOUT)) { + if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port, + SC_ADB_NO_STDOUT)) { // the command itself failed, it will fail on any port return false; } @@ -52,7 +52,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, } // failure, disable tunnel and try another port - if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -83,7 +83,8 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, uint16_t port = port_range.first; for (;;) { - if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_ADB_NO_STDOUT)) { + if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME, + SC_ADB_NO_STDOUT)) { // success tunnel->local_port = port; tunnel->enabled = true; @@ -148,11 +149,11 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, bool ret; if (tunnel->forward) { - ret = adb_forward_remove(intr, serial, tunnel->local_port, - SC_ADB_NO_STDOUT); + ret = sc_adb_forward_remove(intr, serial, tunnel->local_port, + SC_ADB_NO_STDOUT); } else { - ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME, - SC_ADB_NO_STDOUT); + ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + SC_ADB_NO_STDOUT); assert(tunnel->server_socket != SC_SOCKET_NONE); if (!net_close(tunnel->server_socket)) { diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c index 78ea48c2..cbbeb2d7 100644 --- a/app/src/file_pusher.c +++ b/app/src/file_pusher.c @@ -129,7 +129,7 @@ run_file_pusher(void *data) { if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) { LOGI("Installing %s...", req.file); - bool ok = adb_install(intr, serial, req.file, 0); + bool ok = sc_adb_install(intr, serial, req.file, 0); if (ok) { LOGI("%s successfully installed", req.file); } else { @@ -137,7 +137,7 @@ run_file_pusher(void *data) { } } else { LOGI("Pushing %s...", req.file); - bool ok = adb_push(intr, serial, req.file, push_target, 0); + bool ok = sc_adb_push(intr, serial, req.file, push_target, 0); if (ok) { LOGI("%s successfully pushed to %s", req.file, push_target); } else { diff --git a/app/src/server.c b/app/src/server.c index b7a28949..2770df05 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -114,7 +114,7 @@ push_server(struct sc_intr *intr, const char *serial) { free(server_path); return false; } - bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0); + bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0); free(server_path); return ok; } @@ -254,7 +254,7 @@ execute_server(struct sc_server *server, // Then click on "Debug" #endif // Inherit both stdout and stderr (all server logs are printed to stdout) - pid = adb_execute(params->serial, cmd, count, 0); + pid = sc_adb_execute(params->serial, cmd, count, 0); end: for (unsigned i = dyn_idx; i < count; ++i) { @@ -501,7 +501,7 @@ sc_server_fill_serial(struct sc_server *server) { // device/emulator" error) if (!server->params.serial) { // The serial is owned by sc_server_params, and will be freed on destroy - server->params.serial = adb_get_serialno(&server->intr, 0); + server->params.serial = sc_adb_get_serialno(&server->intr, 0); if (!server->params.serial) { LOGE("Could not get device serial"); return false; @@ -519,7 +519,7 @@ is_tcpip_mode_enabled(struct sc_server *server) { const char *serial = server->params.serial; char *current_port = - adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); + sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); if (!current_port) { return false; } @@ -580,7 +580,7 @@ sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { struct sc_intr *intr = &server->intr; - char *ip = adb_get_device_ip(intr, serial, 0); + char *ip = sc_adb_get_device_ip(intr, serial, 0); if (!ip) { LOGE("Device IP not found"); return false; @@ -595,7 +595,7 @@ sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { bool tcp_mode = is_tcpip_mode_enabled(server); if (!tcp_mode) { - bool ok = adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); + bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); if (!ok) { LOGE("Could not restart adbd in TCP/IP mode"); goto error; @@ -623,9 +623,9 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { struct sc_intr *intr = &server->intr; // Error expected if not connected, do not report any error - adb_disconnect(intr, ip_port, SC_ADB_SILENT); + sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT); - bool ok = adb_connect(intr, ip_port, 0); + bool ok = sc_adb_connect(intr, ip_port, 0); if (!ok) { LOGE("Could not connect to %s", ip_port); return false; From bd3c93ae3dfa3dd1b41594450a20dc5611fef50a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 4 Feb 2022 08:37:36 +0100 Subject: [PATCH 0391/1133] Remove platform-tools installation suggestion On Windows, adb is provided in the release archive. Most missing adb issues come from users setting the ADB environment variable to an incorrect value (on all platforms). Suggesting to install platform-tools to solve the problem will just make things worse (there will be one more adb in yet another location). --- app/src/adb/adb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 215fc6be..4810dcbd 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -77,9 +77,6 @@ show_adb_installation_msg() { } } #endif - - LOGI("You may download and install 'adb' from " - "https://developer.android.com/studio/releases/platform-tools"); } static void From 21106bd70a07696198982bbaf00b72558b1e58c0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 4 Feb 2022 09:11:07 +0100 Subject: [PATCH 0392/1133] Remove screensaver log If --disable-screensaver is passed, then screensaver is disabled, otherwise it is enabled. No need for a log. --- app/src/scrcpy.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 270fe2b3..5da6588c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -143,10 +143,8 @@ sdl_configure(bool display, bool disable_screensaver) { } if (disable_screensaver) { - LOGD("Screensaver disabled"); SDL_DisableScreenSaver(); } else { - LOGD("Screensaver enabled"); SDL_EnableScreenSaver(); } } From f807131c0af4c77139fb7a7b3e67fa8000c3bde5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 10:08:51 +0100 Subject: [PATCH 0393/1133] Remove useless undef The #define was removed in 1c71bd16bec1db0788e72a7c6b02f80ed40f1601. --- app/src/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 2770df05..c4172d88 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -240,7 +240,6 @@ execute_server(struct sc_server *server, } #undef ADD_PARAM -#undef STRBOOL #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " From 6ca9825c0f54b2d8cd11c01c6faa970bfec96330 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 10:11:13 +0100 Subject: [PATCH 0394/1133] Assert "adb disconnect" is called with an argument Calling "adb disconnect" without argument would disconnect every TCP/IP device. We must make sure scrcpy never does that. --- app/src/adb/adb.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 4810dcbd..b6084129 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -364,11 +364,10 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { bool sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { + assert(ip_port); const char *const adb_cmd[] = {"disconnect", ip_port}; - size_t len = ip_port ? ARRAY_LEN(adb_cmd) - : ARRAY_LEN(adb_cmd) - 1; - sc_pid pid = sc_adb_execute(NULL, adb_cmd, len, flags); + sc_pid pid = sc_adb_execute(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags); return process_check_success_intr(intr, pid, "adb disconnect", flags); } From 028e7afe32091a6073a1eb7854a47e13719a9156 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 09:51:18 +0100 Subject: [PATCH 0395/1133] Assert non-NULL serial If no serial is passed, then the command would work if there is exactly one device connected, but will fail with multiple devices. To avoid such cases, ensure that a serial is always provided. --- app/src/adb/adb.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index b6084129..c10c7496 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -221,6 +221,8 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + + assert(serial); const char *const adb_cmd[] = {"forward", local, remote}; sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); @@ -232,6 +234,8 @@ sc_adb_forward_remove(struct sc_intr *intr, const char *serial, uint16_t local_port, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT sprintf(local, "tcp:%" PRIu16, local_port); + + assert(serial); const char *const adb_cmd[] = {"forward", "--remove", local}; sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); @@ -246,6 +250,8 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial, char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + + assert(serial); const char *const adb_cmd[] = {"reverse", remote, local}; sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); @@ -257,6 +263,8 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, const char *device_socket_name, unsigned flags) { char remote[108 + 14 + 1]; // localabstract:NAME snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + + assert(serial); const char *const adb_cmd[] = {"reverse", "--remove", remote}; sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); @@ -280,7 +288,9 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, } #endif + assert(serial); const char *const adb_cmd[] = {"push", local, remote}; + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ @@ -303,7 +313,9 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, } #endif + assert(serial); const char *const adb_cmd[] = {"install", "-r", local}; + sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); #ifdef __WINDOWS__ @@ -318,6 +330,8 @@ sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, unsigned flags) { char port_string[5 + 1]; sprintf(port_string, "%" PRIu16, port); + + assert(serial); const char *const adb_cmd[] = {"tcpip", port_string}; sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); @@ -374,6 +388,7 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { char * sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, unsigned flags) { + assert(serial); const char *const adb_cmd[] = {"shell", "getprop", prop}; sc_pipe pout; @@ -434,6 +449,7 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { + assert(serial); const char *const cmd[] = {"shell", "ip", "route"}; sc_pipe pout; From ba30ca5c1edc53fca3f230a2fdc5dbb7648588a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 09:21:53 +0100 Subject: [PATCH 0396/1133] Rename adb_command to adb_executable Semantically, a "command" refers to the whole command line argv (adb and its arguments). --- app/src/adb/adb.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index c10c7496..44c197eb 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -11,16 +11,16 @@ #include "util/process_intr.h" #include "util/str.h" -static const char *adb_command; +static const char *adb_executable; static inline const char * -get_adb_command(void) { - if (!adb_command) { - adb_command = getenv("ADB"); - if (!adb_command) - adb_command = "adb"; +get_adb_executable(void) { + if (!adb_executable) { + adb_executable = getenv("ADB"); + if (!adb_executable) + adb_executable = "adb"; } - return adb_command; + return adb_executable; } // serialize argv to string "[arg1], [arg2], [arg3]" @@ -163,7 +163,7 @@ sc_adb_create_argv(const char *serial, const char *const adb_cmd[], return NULL; } - argv[0] = get_adb_command(); + argv[0] = get_adb_executable(); int i; if (serial) { argv[1] = "-s"; From 5e2bfccab4e8489da02c01a8fca1f568de50e6b7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 09:37:00 +0100 Subject: [PATCH 0397/1133] Expose adb executable path publicly This will allow the caller to build the argv array directly. --- app/src/adb/adb.c | 6 +++--- app/src/adb/adb.h | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 44c197eb..ded0298b 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -13,8 +13,8 @@ static const char *adb_executable; -static inline const char * -get_adb_executable(void) { +const char * +sc_adb_get_executable(void) { if (!adb_executable) { adb_executable = getenv("ADB"); if (!adb_executable) @@ -163,7 +163,7 @@ sc_adb_create_argv(const char *serial, const char *const adb_cmd[], return NULL; } - argv[0] = get_adb_executable(); + argv[0] = sc_adb_get_executable(); int i; if (serial) { argv[1] = "-s"; diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 34fd7866..8b8954f8 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -14,6 +14,9 @@ #define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR) +const char * +sc_adb_get_executable(void); + sc_pid sc_adb_execute(const char *serial, const char *const adb_cmd[], size_t len, unsigned flags); From 386cf7d7acf305827dc49c621be7a6b64e6633d2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 10:07:13 +0100 Subject: [PATCH 0398/1133] Build adb argv statically Now that providing a serial is mandatory for adb commands where it is relevant, the whole argv array may be built statically, without allocations at runtime. --- app/src/adb/adb.c | 111 ++++++++++++++++++++-------------------------- app/src/adb/adb.h | 3 +- app/src/server.c | 10 ++++- 3 files changed, 58 insertions(+), 66 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index ded0298b..2aad516a 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -11,6 +11,18 @@ #include "util/process_intr.h" #include "util/str.h" +/* Convenience macro to expand: + * + * const char *const argv[] = + * SC_ADB_COMMAND("shell", "echo", "hello"); + * + * to: + * + * const char *const argv[] = + * { sc_adb_get_executable(), "shell", "echo", "hello", NULL }; + */ +#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL } + static const char *adb_executable; const char * @@ -154,38 +166,8 @@ process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, return ret; } -static const char ** -sc_adb_create_argv(const char *serial, const char *const adb_cmd[], - size_t len) { - const char **argv = malloc((len + 4) * sizeof(*argv)); - if (!argv) { - LOG_OOM(); - return NULL; - } - - argv[0] = sc_adb_get_executable(); - int i; - if (serial) { - argv[1] = "-s"; - argv[2] = serial; - i = 3; - } else { - i = 1; - } - - memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); - argv[len + i] = NULL; - return argv; -} - static sc_pid -sc_adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags, sc_pipe *pout) { - const char **argv = sc_adb_create_argv(serial, adb_cmd, len); - if (!argv) { - return SC_PROCESS_NONE; - } - +sc_adb_execute_p(const char *const argv[], unsigned flags, sc_pipe *pout) { unsigned process_flags = 0; if (flags & SC_ADB_NO_STDOUT) { process_flags |= SC_PROCESS_NO_STDOUT; @@ -204,14 +186,12 @@ sc_adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len, pid = SC_PROCESS_NONE; } - free(argv); return pid; } sc_pid -sc_adb_execute(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags) { - return sc_adb_execute_p(serial, adb_cmd, len, flags, NULL); +sc_adb_execute(const char *const argv[], unsigned flags) { + return sc_adb_execute_p(argv, flags, NULL); } bool @@ -223,9 +203,10 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); assert(serial); - const char *const adb_cmd[] = {"forward", local, remote}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "forward", local, remote); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb forward", flags); } @@ -236,9 +217,10 @@ sc_adb_forward_remove(struct sc_intr *intr, const char *serial, sprintf(local, "tcp:%" PRIu16, local_port); assert(serial); - const char *const adb_cmd[] = {"forward", "--remove", local}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "forward", "--remove", local); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb forward --remove", flags); } @@ -250,11 +232,11 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial, char remote[108 + 14 + 1]; // localabstract:NAME sprintf(local, "tcp:%" PRIu16, local_port); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); - assert(serial); - const char *const adb_cmd[] = {"reverse", remote, local}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "reverse", remote, local); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb reverse", flags); } @@ -265,9 +247,10 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); assert(serial); - const char *const adb_cmd[] = {"reverse", "--remove", remote}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "reverse", "--remove", remote); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb reverse --remove", flags); } @@ -289,9 +272,10 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, #endif assert(serial); - const char *const adb_cmd[] = {"push", local, remote}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "push", local, remote); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); #ifdef __WINDOWS__ free((void *) remote); @@ -314,9 +298,10 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, #endif assert(serial); - const char *const adb_cmd[] = {"install", "-r", local}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "install", "-r", local); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); #ifdef __WINDOWS__ free((void *) local); @@ -332,19 +317,19 @@ sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, sprintf(port_string, "%" PRIu16, port); assert(serial); - const char *const adb_cmd[] = {"tcpip", port_string}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "tcpip", port_string); - sc_pid pid = sc_adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb tcpip", flags); } bool sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { - const char *const adb_cmd[] = {"connect", ip_port}; + const char *const argv[] = SC_ADB_COMMAND("connect", ip_port); sc_pipe pout; - sc_pid pid = - sc_adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb connect\""); return false; @@ -379,9 +364,9 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { bool sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { assert(ip_port); - const char *const adb_cmd[] = {"disconnect", ip_port}; + const char *const argv[] = SC_ADB_COMMAND("disconnect", ip_port); - sc_pid pid = sc_adb_execute(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags); + sc_pid pid = sc_adb_execute(argv, flags); return process_check_success_intr(intr, pid, "adb disconnect", flags); } @@ -389,11 +374,11 @@ char * sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, unsigned flags) { assert(serial); - const char *const adb_cmd[] = {"shell", "getprop", prop}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "shell", "getprop", prop); sc_pipe pout; - sc_pid pid = - sc_adb_execute_p(serial, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb getprop\""); return NULL; @@ -419,11 +404,10 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, char * sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { - const char *const adb_cmd[] = {"get-serialno"}; + const char *const argv[] = SC_ADB_COMMAND("get-serialno"); sc_pipe pout; - sc_pid pid = - sc_adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), flags, &pout); + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb get-serialno\""); return NULL; @@ -450,10 +434,11 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { assert(serial); - const char *const cmd[] = {"shell", "ip", "route"}; + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "shell", "ip", "route"); sc_pipe pout; - sc_pid pid = sc_adb_execute_p(serial, cmd, ARRAY_LEN(cmd), flags, &pout); + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGD("Could not execute \"ip route\""); return NULL; diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 8b8954f8..1a97c203 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -18,8 +18,7 @@ const char * sc_adb_get_executable(void); sc_pid -sc_adb_execute(const char *serial, const char *const adb_cmd[], size_t len, - unsigned flags); +sc_adb_execute(const char *const argv[], unsigned flags); bool sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, diff --git a/app/src/server.c b/app/src/server.c index c4172d88..abdaa6a4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -157,8 +157,14 @@ execute_server(struct sc_server *server, const struct sc_server_params *params) { sc_pid pid = SC_PROCESS_NONE; + const char *serial = params->serial; + assert(serial); + const char *cmd[128]; unsigned count = 0; + cmd[count++] = sc_adb_get_executable(); + cmd[count++] = "-s"; + cmd[count++] = serial; cmd[count++] = "shell"; cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; cmd[count++] = "app_process"; @@ -241,6 +247,8 @@ execute_server(struct sc_server *server, #undef ADD_PARAM + cmd[count++] = NULL; + #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "..."); @@ -253,7 +261,7 @@ execute_server(struct sc_server *server, // Then click on "Debug" #endif // Inherit both stdout and stderr (all server logs are printed to stdout) - pid = sc_adb_execute(params->serial, cmd, count, 0); + pid = sc_adb_execute(cmd, 0); end: for (unsigned i = dyn_idx; i < count; ++i) { From 08f16a9dde12a5631ca3a24b888816465212963b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 4 Feb 2022 19:42:42 +0100 Subject: [PATCH 0399/1133] Simplify switch to TCPIP function Do not use an output parameter to return the value. Instead, return the actual ip:port string on success or NULL on error. --- app/src/server.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index abdaa6a4..2d4f70b8 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -580,8 +580,8 @@ append_port_5555(const char *ip) { return ip_port; } -static bool -sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { +static char * +sc_server_switch_to_tcpip(struct sc_server *server) { const char *serial = server->params.serial; assert(serial); @@ -590,13 +590,13 @@ sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { char *ip = sc_adb_get_device_ip(intr, serial, 0); if (!ip) { LOGE("Device IP not found"); - return false; + return NULL; } char *ip_port = append_port_5555(ip); free(ip); if (!ip_port) { - return false; + return NULL; } bool tcp_mode = is_tcpip_mode_enabled(server); @@ -616,13 +616,11 @@ sc_server_switch_to_tcpip(struct sc_server *server, char **out_ip_port) { } } - *out_ip_port = ip_port; - - return true; + return ip_port; error: free(ip_port); - return false; + return NULL; } static bool @@ -687,8 +685,8 @@ sc_server_configure_tcpip(struct sc_server *server) { return true; } - bool ok = sc_server_switch_to_tcpip(server, &ip_port); - if (!ok) { + ip_port = sc_server_switch_to_tcpip(server); + if (!ip_port) { return false; } } From 5b3ae2cb2f95a333b3b90253ac959a34d51dd4ea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 4 Feb 2022 20:49:35 +0100 Subject: [PATCH 0400/1133] Store actual serial in sc_server Before starting the server, the actual device serial (possibly its ip:port if it's over TCP/IP) must be known. A serial might be requested via -s/--serial (stored in the sc_server_params), but the actual serial may change afterwards: - if none is provided, then it is retrieved with "adb get-serialno"; - if --tcpip is requested, then the final serial will be the target ip:port. The requested serial was overwritten by the actual serial in the sc_server_params struct, which was a bit hacky. Instead, store a separate serial field in sc_server (and rename the one from sc_server_params to "req_serial" to avoid confusion). --- app/src/scrcpy.c | 4 +- app/src/server.c | 116 +++++++++++++++++++++++------------------------ app/src/server.h | 3 +- 3 files changed, 60 insertions(+), 63 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5da6588c..c846ee18 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -296,7 +296,7 @@ scrcpy(struct scrcpy_options *options) { struct sc_acksync *acksync = NULL; struct sc_server_params params = { - .serial = options->serial, + .req_serial = options->serial, .log_level = options->log_level, .crop = options->crop, .port_range = options->port_range, @@ -355,7 +355,7 @@ scrcpy(struct scrcpy_options *options) { // It is necessarily initialized here, since the device is connected struct sc_server_info *info = &s->server.info; - const char *serial = s->server.params.serial; + const char *serial = s->server.serial; assert(serial); struct sc_file_pusher *fp = NULL; diff --git a/app/src/server.c b/app/src/server.c index 2d4f70b8..e8ae32af 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -65,7 +65,7 @@ get_server_path(void) { static void sc_server_params_destroy(struct sc_server_params *params) { // The server stores a copy of the params provided by the user - free((char *) params->serial); + free((char *) params->req_serial); free((char *) params->crop); free((char *) params->codec_options); free((char *) params->encoder_name); @@ -89,7 +89,7 @@ sc_server_params_copy(struct sc_server_params *dst, } \ } - COPY(serial); + COPY(req_serial); COPY(crop); COPY(codec_options); COPY(encoder_name); @@ -157,7 +157,7 @@ execute_server(struct sc_server *server, const struct sc_server_params *params) { sc_pid pid = SC_PROCESS_NONE; - const char *serial = params->serial; + const char *serial = server->serial; assert(serial); const char *cmd[128]; @@ -353,6 +353,7 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, return false; } + server->serial = NULL; server->stopped = false; server->video_socket = SC_SOCKET_NONE; @@ -397,7 +398,9 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { assert(tunnel->enabled); - const char *serial = server->params.serial; + const char *serial = server->serial; + assert(serial); + bool control = server->params.control; sc_socket video_socket = SC_SOCKET_NONE; @@ -500,30 +503,25 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } -static bool -sc_server_fill_serial(struct sc_server *server) { - // Retrieve the actual device immediately if not provided, so that all - // future adb commands are executed for this specific device, even if other - // devices are connected afterwards (without "more than one - // device/emulator" error) - if (!server->params.serial) { - // The serial is owned by sc_server_params, and will be freed on destroy - server->params.serial = sc_adb_get_serialno(&server->intr, 0); - if (!server->params.serial) { - LOGE("Could not get device serial"); - return false; +static char * +sc_server_read_serial(struct sc_server *server) { + char *serial; + if (server->params.req_serial) { + // The serial is already known + serial = strdup(server->params.req_serial); + if (!serial) { + LOG_OOM(); } - - LOGD("Device serial: %s", server->params.serial); + } else { + serial = sc_adb_get_serialno(&server->intr, 0); } - return true; + return serial; } static bool -is_tcpip_mode_enabled(struct sc_server *server) { +is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { struct sc_intr *intr = &server->intr; - const char *serial = server->params.serial; char *current_port = sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); @@ -538,9 +536,9 @@ is_tcpip_mode_enabled(struct sc_server *server) { } static bool -wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts, - sc_tick delay) { - if (is_tcpip_mode_enabled(server)) { +wait_tcpip_mode_enabled(struct sc_server *server, const char *serial, + unsigned attempts, sc_tick delay) { + if (is_tcpip_mode_enabled(server, serial)) { LOGI("TCP/IP mode enabled"); return true; } @@ -555,7 +553,7 @@ wait_tcpip_mode_enabled(struct sc_server *server, unsigned attempts, return false; } - if (is_tcpip_mode_enabled(server)) { + if (is_tcpip_mode_enabled(server, serial)) { LOGI("TCP/IP mode enabled"); return true; } @@ -581,12 +579,13 @@ append_port_5555(const char *ip) { } static char * -sc_server_switch_to_tcpip(struct sc_server *server) { - const char *serial = server->params.serial; +sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) { assert(serial); struct sc_intr *intr = &server->intr; + LOGI("Switching device %s to TCP/IP...", serial); + char *ip = sc_adb_get_device_ip(intr, serial, 0); if (!ip) { LOGE("Device IP not found"); @@ -599,7 +598,7 @@ sc_server_switch_to_tcpip(struct sc_server *server) { return NULL; } - bool tcp_mode = is_tcpip_mode_enabled(server); + bool tcp_mode = is_tcpip_mode_enabled(server, serial); if (!tcp_mode) { bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); @@ -610,7 +609,7 @@ sc_server_switch_to_tcpip(struct sc_server *server) { unsigned attempts = 40; sc_tick delay = SC_TICK_FROM_MS(250); - ok = wait_tcpip_mode_enabled(server, attempts, delay); + ok = wait_tcpip_mode_enabled(server, serial, attempts, delay); if (!ok) { goto error; } @@ -630,20 +629,14 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { // Error expected if not connected, do not report any error sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT); + LOGI("Connecting to %s...", ip_port); + bool ok = sc_adb_connect(intr, ip_port, 0); if (!ok) { LOGE("Could not connect to %s", ip_port); return false; } - // Override the serial, owned by the sc_server_params - free((void *) server->params.serial); - server->params.serial = strdup(ip_port); - if (!server->params.serial) { - LOG_OOM(); - return false; - } - LOGI("Connected to %s", ip_port); return true; } @@ -657,7 +650,7 @@ sc_server_configure_tcpip(struct sc_server *server) { // If tcpip parameter is given, then it must connect to this address. // Therefore, the device is unknown, so serial is meaningless at this point. - assert(!params->serial || !params->tcpip_dst); + assert(!params->req_serial || !params->tcpip_dst); if (params->tcpip_dst) { // Append ":5555" if no port is present @@ -671,30 +664,32 @@ sc_server_configure_tcpip(struct sc_server *server) { } else { // The device IP address must be retrieved from the current // connected device - if (!sc_server_fill_serial(server)) { + char *serial = sc_server_read_serial(server); + if (!serial) { + LOGE("Could not get device serial"); return false; } // The serial is either the real serial when connected via USB, or // the IP:PORT when connected over TCP/IP. Only the latter contains // a colon. - bool is_already_tcpip = strchr(params->serial, ':'); + bool is_already_tcpip = strchr(serial, ':'); if (is_already_tcpip) { // Nothing to do - LOGI("Device already connected via TCP/IP: %s", params->serial); + LOGI("Device already connected via TCP/IP: %s", serial); + free(serial); return true; } - ip_port = sc_server_switch_to_tcpip(server); + ip_port = sc_server_switch_to_tcpip(server, serial); + free(serial); if (!ip_port) { return false; } } - // On success, this call changes params->serial - bool ok = sc_server_connect_to_tcpip(server, ip_port); - free(ip_port); - return ok; + server->serial = ip_port; + return sc_server_connect_to_tcpip(server, ip_port); } static int @@ -703,30 +698,30 @@ run_server(void *data) { const struct sc_server_params *params = &server->params; - if (params->serial) { - LOGD("Device serial: %s", params->serial); - } - if (params->tcpip) { - // params->serial may be changed after this call bool ok = sc_server_configure_tcpip(server); if (!ok) { goto error_connection_failed; } + assert(server->serial); + } else { + server->serial = sc_server_read_serial(server); + if (!server->serial) { + LOGD("Could not get device serial"); + goto error_connection_failed; + } } - // It is ok to call this function even if the device serial has been - // changed by switching over TCP/IP - if (!sc_server_fill_serial(server)) { - goto error_connection_failed; - } + const char *serial = server->serial; + assert(serial); + LOGD("Device serial: %s", serial); - bool ok = push_server(&server->intr, params->serial); + bool ok = push_server(&server->intr, serial); if (!ok) { goto error_connection_failed; } - ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial, + ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial, params->port_range, params->force_adb_forward); if (!ok) { goto error_connection_failed; @@ -735,7 +730,7 @@ run_server(void *data) { // server will connect to our server socket sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { - sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); + sc_adb_tunnel_close(&server->tunnel, &server->intr, serial); goto error_connection_failed; } @@ -747,7 +742,7 @@ run_server(void *data) { if (!ok) { sc_process_terminate(pid); sc_process_wait(pid, true); // ignore exit code - sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); + sc_adb_tunnel_close(&server->tunnel, &server->intr, serial); goto error_connection_failed; } @@ -840,6 +835,7 @@ sc_server_destroy(struct sc_server *server) { net_close(server->control_socket); } + free(server->serial); sc_server_params_destroy(&server->params); sc_intr_destroy(&server->intr); sc_cond_destroy(&server->cond_stopped); diff --git a/app/src/server.h b/app/src/server.h index 1dff19a7..c2293118 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -22,7 +22,7 @@ struct sc_server_info { }; struct sc_server_params { - const char *serial; + const char *req_serial; enum sc_log_level log_level; const char *crop; const char *codec_options; @@ -49,6 +49,7 @@ struct sc_server_params { struct sc_server { // The internal allocated strings are copies owned by the server struct sc_server_params params; + char *serial; sc_thread thread; struct sc_server_info info; // initialized once connected From 5d6bd8f9cd69036b331bb1898fe7c6353777f592 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 10:52:55 +0100 Subject: [PATCH 0401/1133] Fix adb device ip parsing The parser assumed that its input was a NUL-terminated string, but it was not the case: it is just the raw output of "adb devices ip route". In practice, it was harmless, since the output always ended with '\n' (which was replaced by '\0' on truncation), but it was incorrect nonetheless. Always write a '\0' at the end of the buffer, and explicitly parse as a NUL-terminated string. For that purpose, avoid the error-prone sc_str_truncate() util function. --- app/src/adb/adb.c | 11 +++++++---- app/src/adb/adb_parser.c | 27 +++++++++++++++++---------- app/src/adb/adb_parser.h | 4 +++- app/tests/test_adb_parser.c | 23 ++++++++++++++++------- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 2aad516a..5a1ed25d 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -446,7 +446,7 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { // "adb shell ip route" output should contain only a few lines char buf[1024]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); sc_pipe_close(pout); bool ok = process_check_success_intr(intr, pid, "ip route", flags); @@ -458,8 +458,8 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { return NULL; } - assert((size_t) r <= sizeof(buf)); - if (r == sizeof(buf) && buf[sizeof(buf) - 1] != '\0') { + assert((size_t) r < sizeof(buf)); + if (r == sizeof(buf) - 1) { // The implementation assumes that the output of "ip route" fits in the // buffer in a single pass LOGW("Result of \"ip route\" does not fit in 1Kb. " @@ -467,5 +467,8 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { return NULL; } - return sc_adb_parse_device_ip_from_output(buf, r); + // It is parsed as a NUL-terminated string + buf[r] = '\0'; + + return sc_adb_parse_device_ip_from_output(buf); } diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index d660aaab..2a0fd8da 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -7,7 +7,7 @@ #include "util/str.h" static char * -sc_adb_parse_device_ip_from_line(char *line, size_t len) { +sc_adb_parse_device_ip_from_line(char *line) { // One line from "ip route" looks like: // "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x" @@ -27,10 +27,12 @@ sc_adb_parse_device_ip_from_line(char *line, size_t len) { idx_ip += idx_dev_name; char *dev_name = &line[idx_dev_name]; - sc_str_truncate(dev_name, len - idx_dev_name + 1, " \t"); + size_t dev_name_len = strcspn(dev_name, " \t"); + dev_name[dev_name_len] = '\0'; char *ip = &line[idx_ip]; - sc_str_truncate(ip, len - idx_ip + 1, " \t"); + size_t ip_len = strcspn(ip, " \t"); + ip[ip_len] = '\0'; // Only consider lines where the device name starts with "wlan" if (strncmp(dev_name, "wlan", sizeof("wlan") - 1)) { @@ -42,23 +44,28 @@ sc_adb_parse_device_ip_from_line(char *line, size_t len) { } char * -sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len) { +sc_adb_parse_device_ip_from_output(char *str) { size_t idx_line = 0; - while (idx_line < buf_len && buf[idx_line] != '\0') { - char *line = &buf[idx_line]; - size_t len = sc_str_truncate(line, buf_len - idx_line, "\n"); + while (str[idx_line] != '\0') { + char *line = &str[idx_line]; + size_t len = strcspn(line, "\n"); // The same, but without any trailing '\r' size_t line_len = sc_str_remove_trailing_cr(line, len); + line[line_len] = '\0'; - char *ip = sc_adb_parse_device_ip_from_line(line, line_len); + char *ip = sc_adb_parse_device_ip_from_line(line); if (ip) { // Found return ip; } - // The next line starts after the '\n' (replaced by `\0`) - idx_line += len + 1; + idx_line += len; + + if (str[idx_line] != '\0') { + // The next line starts after the '\n' + idx_line += 1; + } } return NULL; diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index bce5126c..7d116713 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -8,9 +8,11 @@ /** * Parse the ip from the output of `adb shell ip route` * + * The parameter must be a NUL-terminated string. + * * Warning: this function modifies the buffer for optimization purposes. */ char * -sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len); +sc_adb_parse_device_ip_from_output(char *str); #endif diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 51e7d6d2..749ce433 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -8,7 +8,7 @@ static void test_get_ip_single_line() { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34\r\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -18,7 +18,7 @@ static void test_get_ip_single_line_without_eol() { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -28,7 +28,7 @@ static void test_get_ip_single_line_with_trailing_space() { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34 \n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -40,7 +40,7 @@ static void test_get_ip_multiline_first_ok() { "10.0.0.0/24 dev rmnet proto kernel scope link src " "10.0.0.2\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(ip); assert(!strcmp(ip, "192.168.1.2")); free(ip); @@ -52,7 +52,7 @@ static void test_get_ip_multiline_second_ok() { "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.1.3\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(ip); assert(!strcmp(ip, "192.168.1.3")); free(ip); @@ -62,7 +62,15 @@ static void test_get_ip_no_wlan() { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34\r\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); + assert(!ip); +} + +static void test_get_ip_no_wlan_without_eol() { + char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " + "192.168.12.34"; + + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(!ip); } @@ -70,7 +78,7 @@ static void test_get_ip_truncated() { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route, sizeof(ip_route)); + char *ip = sc_adb_parse_device_ip_from_output(ip_route); assert(!ip); } @@ -84,5 +92,6 @@ int main(int argc, char *argv[]) { test_get_ip_multiline_first_ok(); test_get_ip_multiline_second_ok(); test_get_ip_no_wlan(); + test_get_ip_no_wlan_without_eol(); test_get_ip_truncated(); } From 2ea12f73db333d0d6b01f92f2a092ada034c7f27 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 12:17:39 +0100 Subject: [PATCH 0402/1133] Fix adb getprop parsing The function assumed that the raw output of "adb getprop" was a NUL-terminated string, but it is not the case. It this output did not end with a space or a new line character, then sc_str_truncate() would write '\0' over the last character. Even worse, if the output was empty, then sc_str_truncate() would write out-of-bounds. Avoid the error-prone sc_str_truncate() util function. --- app/src/adb/adb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 5a1ed25d..2fd4b35d 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -385,7 +385,7 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, } char buf[128]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); sc_pipe_close(pout); bool ok = process_check_success_intr(intr, pid, "adb getprop", flags); @@ -397,7 +397,10 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, return NULL; } - sc_str_truncate(buf, r, " \r\n"); + assert((size_t) r < sizeof(buf)); + buf[r] = '\0'; + size_t len = strcspn(buf, " \r\n"); + buf[len] = '\0'; return strdup(buf); } From 8d540e83c707e249a72a88ef939a5b8395e36e87 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 12:18:17 +0100 Subject: [PATCH 0403/1133] Fix adb get-serialno parsing The function assumed that the raw output of "adb get-serialno" was a NUL-terminated string, but it is not the case. It this output did not end with a space or a new line character, then sc_str_truncate() would write '\0' over the last character. Even worse, if the output was empty, then sc_str_truncate() would write out-of-bounds. Avoid the error-prone sc_str_truncate() util function. --- app/src/adb/adb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 2fd4b35d..11c1b298 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -417,7 +417,7 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { } char buf[128]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); sc_pipe_close(pout); bool ok = process_check_success_intr(intr, pid, "adb get-serialno", flags); @@ -429,7 +429,10 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { return NULL; } - sc_str_truncate(buf, r, " \r\n"); + assert((size_t) r < sizeof(buf)); + buf[r] = '\0'; + size_t len = strcspn(buf, " \r\n"); + buf[len] = '\0'; return strdup(buf); } From 6d41c53b61dbd433a094d4b33e86c83e12e3399b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 12:22:17 +0100 Subject: [PATCH 0404/1133] Fix adb connect parsing The function assumed that the raw output of "adb connect" was a NUL-terminated string, but it is not the case. It this output did not end with a space or a new line character, then sc_str_truncate() would write '\0' over the last character. Even worse, if the output was empty, then sc_str_truncate() would write out-of-bounds. Avoid the error-prone sc_str_truncate() util function. --- app/src/adb/adb.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 11c1b298..3cfc5a95 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -339,7 +339,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { // case of failure. As a workaround, check if its output starts with // "connected". char buf[128]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf)); + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); sc_pipe_close(pout); bool ok = process_check_success_intr(intr, pid, "adb connect", flags); @@ -351,11 +351,15 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) { return false; } + assert((size_t) r < sizeof(buf)); + buf[r] = '\0'; + ok = !strncmp("connected", buf, sizeof("connected") - 1); if (!ok && !(flags & SC_ADB_NO_STDERR)) { // "adb connect" also prints errors to stdout. Since we capture it, // re-print the error to stderr. - sc_str_truncate(buf, r, "\r\n"); + size_t len = strcspn(buf, "\r\n"); + buf[len] = '\0'; fprintf(stderr, "%s\n", buf); } return ok; From 137d2c97914fd9ac4b79869185c94c402d7719ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 12:24:40 +0100 Subject: [PATCH 0405/1133] Remove confusing sc_str_truncate() This util function was error-prone: - it accepted a buffer as parameter (not necessarily a NUL-terminated string) and its length (including the NUL char, if any); - it wrote '\0' over the last character of the buffer, so the last character was lost if the buffer was not a NUL-terminated string, and even worse, it caused undefined behavior if the length was empty; - it returned the length of the resulting NUL-terminated string, which was inconsistent with the input buffer length. In addition, it was not necessarily optimal: - it wrote '\0' twice; - it required to know the buffer length, that is the input string length + 1, in advance. Remove this function, and let the client use strcspn() manually. --- app/src/util/str.c | 8 -------- app/src/util/str.h | 11 ----------- app/tests/test_str.c | 27 --------------------------- 3 files changed, 46 deletions(-) diff --git a/app/src/util/str.c b/app/src/util/str.c index 2d67f816..d78aa9d7 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -297,14 +297,6 @@ error: return NULL; } -size_t -sc_str_truncate(char *data, size_t len, const char *endchars) { - data[len - 1] = '\0'; - size_t idx = strcspn(data, endchars); - data[idx] = '\0'; - return idx; -} - ssize_t sc_str_index_of_column(const char *s, unsigned col, const char *seps) { size_t colidx = 0; diff --git a/app/src/util/str.h b/app/src/util/str.h index dfe0cb30..1736bd95 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -103,17 +103,6 @@ sc_str_from_wchars(const wchar_t *s); char * sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent); -/** - * Truncate the data after any of the characters from `endchars` - * - * An '\0' is always written at the end of the data string, even if no - * character from `endchars` is encountered. - * - * Return the size of the resulting string (as strlen() would return). - */ -size_t -sc_str_truncate(char *data, size_t len, const char *endchars); - /** * Find the start of a column in a string * diff --git a/app/tests/test_str.c b/app/tests/test_str.c index cc3039e7..4fe8a1df 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -338,32 +338,6 @@ static void test_wrap_lines(void) { free(formatted); } -static void test_truncate(void) { - char s[] = "hello\nworld\n!"; - size_t len = sc_str_truncate(s, sizeof(s), "\n"); - - assert(len == 5); - assert(!strcmp("hello", s)); - - char s2[] = "hello\r\nworkd\r\n!"; - len = sc_str_truncate(s2, sizeof(s2), "\n\r"); - - assert(len == 5); - assert(!strcmp("hello", s)); - - char s3[] = "hello world\n!"; - len = sc_str_truncate(s3, sizeof(s3), " \n\r"); - - assert(len == 5); - assert(!strcmp("hello", s3)); - - char s4[] = "hello "; - len = sc_str_truncate(s4, sizeof(s4), " \n\r"); - - assert(len == 5); - assert(!strcmp("hello", s4)); -} - static void test_index_of_column(void) { assert(sc_str_index_of_column("a bc d", 0, " ") == 0); assert(sc_str_index_of_column("a bc d", 1, " ") == 2); @@ -417,7 +391,6 @@ int main(int argc, char *argv[]) { test_parse_integer_with_suffix(); test_strlist_contains(); test_wrap_lines(); - test_truncate(); test_index_of_column(); test_remove_trailing_cr(); return 0; From b0e04aa327141937f47175a86a10efc5cb11da8d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 12:56:25 +0100 Subject: [PATCH 0406/1133] Remove log_libusb_error() This helper did not help a lot, and prevented the client to choose the log level and the prefix error message. --- app/src/usb/aoa_hid.c | 13 ++++--------- app/src/usb/usb.c | 16 +++++----------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 6e3bd2e7..ca10934a 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -45,11 +45,6 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) { free(hid_event->buffer); } -static inline void -log_libusb_error(enum libusb_error errcode) { - LOGW("libusb error: %s", libusb_strerror(errcode)); -} - bool sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { @@ -99,7 +94,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { - log_libusb_error((enum libusb_error) result); + LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result)); return false; } @@ -135,7 +130,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { - log_libusb_error((enum libusb_error) result); + LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result)); return false; } @@ -177,7 +172,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { - log_libusb_error((enum libusb_error) result); + LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result)); return false; } @@ -199,7 +194,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { request, value, index, buffer, length, DEFAULT_TIMEOUT); if (result < 0) { - log_libusb_error((enum libusb_error) result); + LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); return false; } diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 729df7f0..143bca04 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -4,11 +4,6 @@ #include "util/log.h" -static inline void -log_libusb_error(enum libusb_error errcode) { - LOGW("libusb error: %s", libusb_strerror(errcode)); -} - static char * read_string(libusb_device_handle *handle, uint8_t desc_index) { char buffer[128]; @@ -96,7 +91,7 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, libusb_device **list; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { - log_libusb_error((enum libusb_error) count); + LOGE("List USB devices: libusb error: %s", libusb_strerror(count)); return -1; } @@ -118,7 +113,7 @@ sc_usb_open_handle(libusb_device *device) { libusb_device_handle *handle; int result = libusb_open(device, &handle); if (result < 0) { - log_libusb_error((enum libusb_error) result); + LOGE("Open device: libusb error: %s", libusb_strerror(result)); return NULL; } return handle; @@ -183,8 +178,7 @@ sc_usb_register_callback(struct sc_usb *usb) { struct libusb_device_descriptor desc; int result = libusb_get_device_descriptor(device, &desc); if (result < 0) { - log_libusb_error((enum libusb_error) result); - LOGW("Could not read USB device descriptor"); + LOGE("Device descriptor: libusb error: %s", libusb_strerror(result)); return false; } @@ -198,8 +192,8 @@ sc_usb_register_callback(struct sc_usb *usb) { sc_usb_libusb_callback, usb, &usb->callback_handle); if (result < 0) { - log_libusb_error((enum libusb_error) result); - LOGW("Could not register USB callback"); + LOGE("Register hotplog callback: libusb error: %s", + libusb_strerror(result)); return false; } From b60809a4da23f52c1efaebc2813d3a45d97a8ca5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 13:01:49 +0100 Subject: [PATCH 0407/1133] Inline USB device opening Such a separate function was useless. --- app/src/usb/usb.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 143bca04..1b5f4a5d 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -108,17 +108,6 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, return idx; } -static libusb_device_handle * -sc_usb_open_handle(libusb_device *device) { - libusb_device_handle *handle; - int result = libusb_open(device, &handle); - if (result < 0) { - LOGE("Open device: libusb error: %s", libusb_strerror(result)); - return NULL; - } - return handle; - } - bool sc_usb_init(struct sc_usb *usb) { usb->handle = NULL; @@ -204,8 +193,9 @@ sc_usb_register_callback(struct sc_usb *usb) { bool sc_usb_connect(struct sc_usb *usb, libusb_device *device, const struct sc_usb_callbacks *cbs, void *cbs_userdata) { - usb->handle = sc_usb_open_handle(device); - if (!usb->handle) { + int result = libusb_open(device, &usb->handle); + if (result < 0) { + LOGE("Open device: libusb error: %s", libusb_strerror(result)); return false; } From f20137d2ac524dd9dc0c7073fbc0f5f4e266b371 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 13:10:53 +0100 Subject: [PATCH 0408/1133] Improve USB device open log For consistency with "List USB devices", log "Open USB device". --- app/src/usb/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 1b5f4a5d..f2dab658 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -195,7 +195,7 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device, const struct sc_usb_callbacks *cbs, void *cbs_userdata) { int result = libusb_open(device, &usb->handle); if (result < 0) { - LOGE("Open device: libusb error: %s", libusb_strerror(result)); + LOGE("Open USB device: libusb error: %s", libusb_strerror(result)); return false; } From 61b6324ee902f48da4c4e6a76b3123e8768b4a9f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 14:06:03 +0100 Subject: [PATCH 0409/1133] Remove LOGC() It is not clear when to use LOGC() rather than LOGE(). Always use LOGE(). Moreover, enum sc_log_level has no "critical" log level. --- app/src/controller.c | 2 +- app/src/demuxer.c | 2 +- app/src/file_pusher.c | 2 +- app/src/receiver.c | 2 +- app/src/recorder.c | 2 +- app/src/scrcpy.c | 4 ++-- app/src/screen.c | 8 ++++---- app/src/sys/unix/process.c | 2 +- app/src/usb/aoa_hid.c | 2 +- app/src/usb/scrcpy_otg.c | 2 +- app/src/util/log.h | 3 +-- app/src/util/net.c | 2 +- app/src/util/thread.c | 12 ++++++------ app/src/v4l2_sink.c | 2 +- 14 files changed, 23 insertions(+), 24 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 626a5e30..cdf53edb 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -113,7 +113,7 @@ sc_controller_start(struct sc_controller *controller) { bool ok = sc_thread_create(&controller->thread, run_controller, "scrcpy-ctl", controller); if (!ok) { - LOGC("Could not start controller thread"); + LOGE("Could not start controller thread"); return false; } diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c683dfe0..bebdb6d6 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -276,7 +276,7 @@ sc_demuxer_start(struct sc_demuxer *demuxer) { bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer", demuxer); if (!ok) { - LOGC("Could not start demuxer thread"); + LOGE("Could not start demuxer thread"); return false; } return true; diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c index cbbeb2d7..f6757870 100644 --- a/app/src/file_pusher.c +++ b/app/src/file_pusher.c @@ -156,7 +156,7 @@ sc_file_pusher_start(struct sc_file_pusher *fp) { bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp); if (!ok) { - LOGC("Could not start file_pusher thread"); + LOGE("Could not start file_pusher thread"); return false; } diff --git a/app/src/receiver.c b/app/src/receiver.c index 1e25536e..0376948d 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -114,7 +114,7 @@ receiver_start(struct receiver *receiver) { bool ok = sc_thread_create(&receiver->thread, run_receiver, "scrcpy-receiver", receiver); if (!ok) { - LOGC("Could not start receiver thread"); + LOGE("Could not start receiver thread"); return false; } diff --git a/app/src/recorder.c b/app/src/recorder.c index 2a82e172..b14b6050 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -290,7 +290,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", recorder); if (!ok) { - LOGC("Could not start recorder thread"); + LOGE("Could not start recorder thread"); goto error_avio_close; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c846ee18..dc4ac25a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -269,7 +269,7 @@ scrcpy(struct scrcpy_options *options) { // Minimal SDL initialization if (SDL_Init(SDL_INIT_EVENTS)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); + LOGE("Could not initialize SDL: %s", SDL_GetError()); return false; } @@ -341,7 +341,7 @@ scrcpy(struct scrcpy_options *options) { // Initialize SDL video in addition if display is enabled if (options->display && SDL_Init(SDL_INIT_VIDEO)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); + LOGE("Could not initialize SDL: %s", SDL_GetError()); goto end; } diff --git a/app/src/screen.c b/app/src/screen.c index c62920bc..98626909 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -423,14 +423,14 @@ sc_screen_init(struct sc_screen *screen, screen->window = SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); if (!screen->window) { - LOGC("Could not create window: %s", SDL_GetError()); + LOGE("Could not create window: %s", SDL_GetError()); goto error_destroy_fps_counter; } screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED); if (!screen->renderer) { - LOGC("Could not create renderer: %s", SDL_GetError()); + LOGE("Could not create renderer: %s", SDL_GetError()); goto error_destroy_window; } @@ -479,7 +479,7 @@ sc_screen_init(struct sc_screen *screen, params->frame_size.height); screen->texture = create_texture(screen); if (!screen->texture) { - LOGC("Could not create texture: %s", SDL_GetError()); + LOGE("Could not create texture: %s", SDL_GetError()); goto error_destroy_renderer; } @@ -666,7 +666,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { screen->frame_size.width, screen->frame_size.height); screen->texture = create_texture(screen); if (!screen->texture) { - LOGC("Could not create texture: %s", SDL_GetError()); + LOGE("Could not create texture: %s", SDL_GetError()); return false; } } diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index cb82f3a9..cef227ed 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -176,7 +176,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, bool sc_process_terminate(pid_t pid) { if (pid <= 0) { - LOGC("Requested to kill %d, this is an error. Please report the bug.\n", + LOGE("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); abort(); } diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index ca10934a..57296bfc 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -278,7 +278,7 @@ sc_aoa_start(struct sc_aoa *aoa) { bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa); if (!ok) { - LOGC("Could not start AOA thread"); + LOGE("Could not start AOA thread"); return false; } diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index e27a3605..e39ab27a 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -56,7 +56,7 @@ scrcpy_otg(struct scrcpy_options *options) { // Minimal SDL initialization if (SDL_Init(SDL_INIT_EVENTS)) { - LOGC("Could not initialize SDL: %s", SDL_GetError()); + LOGE("Could not initialize SDL: %s", SDL_GetError()); return false; } diff --git a/app/src/util/log.h b/app/src/util/log.h index e3efdbe5..94787347 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -15,10 +15,9 @@ #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) -#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOG_OOM() \ - LOGC("OOM: %s:%d %s()", __FILE__, __LINE__, __func__) + LOGE("OOM: %s:%d %s()", __FILE__, __LINE__, __func__) void sc_set_log_level(enum sc_log_level level); diff --git a/app/src/util/net.c b/app/src/util/net.c index b18566be..b1aa7445 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -33,7 +33,7 @@ net_init(void) { WSADATA wsa; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; if (res < 0) { - LOGC("WSAStartup failed with error %d", res); + LOGE("WSAStartup failed with error %d", res); return false; } #endif diff --git a/app/src/util/thread.c b/app/src/util/thread.c index c6e6b81e..0d098b3f 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -54,7 +54,7 @@ sc_mutex_lock(sc_mutex *mutex) { int r = SDL_LockMutex(mutex->mutex); #ifndef NDEBUG if (r) { - LOGC("Could not lock mutex: %s", SDL_GetError()); + LOGE("Could not lock mutex: %s", SDL_GetError()); abort(); } @@ -74,7 +74,7 @@ sc_mutex_unlock(sc_mutex *mutex) { int r = SDL_UnlockMutex(mutex->mutex); #ifndef NDEBUG if (r) { - LOGC("Could not lock mutex: %s", SDL_GetError()); + LOGE("Could not lock mutex: %s", SDL_GetError()); abort(); } #else @@ -118,7 +118,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) { int r = SDL_CondWait(cond->cond, mutex->mutex); #ifndef NDEBUG if (r) { - LOGC("Could not wait on condition: %s", SDL_GetError()); + LOGE("Could not wait on condition: %s", SDL_GetError()); abort(); } @@ -140,7 +140,7 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); #ifndef NDEBUG if (r < 0) { - LOGC("Could not wait on condition with timeout: %s", SDL_GetError()); + LOGE("Could not wait on condition with timeout: %s", SDL_GetError()); abort(); } @@ -156,7 +156,7 @@ sc_cond_signal(sc_cond *cond) { int r = SDL_CondSignal(cond->cond); #ifndef NDEBUG if (r) { - LOGC("Could not signal a condition: %s", SDL_GetError()); + LOGE("Could not signal a condition: %s", SDL_GetError()); abort(); } #else @@ -169,7 +169,7 @@ sc_cond_broadcast(sc_cond *cond) { int r = SDL_CondBroadcast(cond->cond); #ifndef NDEBUG if (r) { - LOGC("Could not broadcast a condition: %s", SDL_GetError()); + LOGE("Could not broadcast a condition: %s", SDL_GetError()); abort(); } #else diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 7675fd92..9a0011f2 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -274,7 +274,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { LOGD("Starting v4l2 thread"); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "scrcpy-v4l2", vs); if (!ok) { - LOGC("Could not start v4l2 thread"); + LOGE("Could not start v4l2 thread"); goto error_av_packet_free; } From 6df2205cf3450feb3023990fecc7aba8d2982911 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 14:18:12 +0100 Subject: [PATCH 0410/1133] Add generic LOG() macro with level parameter One log macro was provided for each log level (LOGV(), LOGD(), LOGI(), LOGW(), LOGE()). Add a generic macro LOG(LEVEL, ...) accepting a log level as parameter, so that it is possible to write logging wrappers. PR #3005 --- app/src/util/log.c | 10 ++++++++++ app/src/util/log.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/app/src/util/log.c b/app/src/util/log.c index d8d7cd70..72cd2877 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -55,6 +55,16 @@ sc_get_log_level(void) { return log_level_sdl_to_sc(sdl_log); } +void +sc_log(enum sc_log_level level, const char *fmt, ...) { + SDL_LogPriority sdl_level = log_level_sc_to_sdl(level); + + va_list ap; + va_start(ap, fmt); + SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, sdl_level, fmt, ap); + va_end(ap); +} + #ifdef _WIN32 bool sc_log_windows_error(const char *prefix, int error) { diff --git a/app/src/util/log.h b/app/src/util/log.h index 94787347..6bd8506c 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -25,6 +25,10 @@ sc_set_log_level(enum sc_log_level level); enum sc_log_level sc_get_log_level(void); +void +sc_log(enum sc_log_level level, const char *fmt, ...); +#define LOG(LEVEL, ...) sc_log((LEVEL), __VA_ARGS__) + #ifdef _WIN32 // Log system error (typically returned by GetLastError() or similar) bool From 0eadf95a3e8382ad6f5aceb94eba9f1e2d4780e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 18:09:02 +0100 Subject: [PATCH 0411/1133] Rename function to destroy a list of USB devices Rename from "usb_device_" to "usb_devices_". PR #3005 --- app/src/scrcpy.c | 2 +- app/src/usb/scrcpy_otg.c | 2 +- app/src/usb/usb.c | 2 +- app/src/usb/usb.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index dc4ac25a..92159bba 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -442,7 +442,7 @@ scrcpy(struct scrcpy_options *options) { if (count > 1) { LOGE("Multiple (%d) devices with serial %s", (int) count, serial); - sc_usb_device_destroy_all(usb_devices, count); + sc_usb_devices_destroy_all(usb_devices, count); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); goto aoa_hid_end; diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index e39ab27a..34c16ce1 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -115,7 +115,7 @@ scrcpy_otg(struct scrcpy_options *options) { if (!serial) { LOGE("Specify the device via -s or --serial"); } - sc_usb_device_destroy_all(usb_devices, count); + sc_usb_devices_destroy_all(usb_devices, count); goto end; } usb_device_initialized = true; diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index f2dab658..d705f647 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -79,7 +79,7 @@ sc_usb_device_destroy(struct sc_usb_device *usb_device) { } void -sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) { +sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) { for (size_t i = 0; i < count; ++i) { sc_usb_device_destroy(&usb_devices[i]); } diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index eda7c2f9..a8810b90 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -41,7 +41,7 @@ void sc_usb_device_destroy(struct sc_usb_device *usb_device); void -sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count); +sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count); bool sc_usb_init(struct sc_usb *usb); From 8c50342fb28b71f3632ad53903dc10920f0fb1d8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 18:55:10 +0100 Subject: [PATCH 0412/1133] Move SC_PRIsizet to compat.h Define the printf format macro for size_t in compat.h so that it can be used from anywhere. PR #3005 --- app/src/compat.h | 2 ++ app/src/util/process.h | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/compat.h b/app/src/compat.h index 311d617d..62435718 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -8,8 +8,10 @@ #ifndef __WIN32 # define PRIu64_ PRIu64 +# define SC_PRIsizet "zu" #else # define PRIu64_ "I64u" // Windows... +# define SC_PRIsizet "Iu" #endif // In ffmpeg/doc/APIchanges: diff --git a/app/src/util/process.h b/app/src/util/process.h index 90920620..4d9d1684 100644 --- a/app/src/util/process.h +++ b/app/src/util/process.h @@ -12,8 +12,6 @@ # include # include # define SC_PRIexitcode "lu" -// -# define SC_PRIsizet "Iu" # define SC_PROCESS_NONE NULL # define SC_EXIT_CODE_NONE -1UL // max value as unsigned long typedef HANDLE sc_pid; @@ -23,7 +21,6 @@ #else # include -# define SC_PRIsizet "zu" # define SC_PRIexitcode "d" # define SC_PROCESS_NONE -1 # define SC_EXIT_CODE_NONE -1 From b88c4aa75e73c0bbe981dd8f5e986c67d18abe22 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 19:07:53 +0100 Subject: [PATCH 0413/1133] Add move-function for sc_usb_device Add a function to "move" a sc_usb_device into another instance. This will avoid unnecessary copies. PR #3005 --- app/src/usb/usb.c | 13 ++++++++++++- app/src/usb/usb.h | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index d705f647..cc9c78cc 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -72,12 +72,23 @@ accept_device(libusb_device *device, const char *serial, void sc_usb_device_destroy(struct sc_usb_device *usb_device) { - libusb_unref_device(usb_device->device); + if (usb_device->device) { + libusb_unref_device(usb_device->device); + } free(usb_device->serial); free(usb_device->manufacturer); free(usb_device->product); } +void +sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src) { + *dst = *src; + src->device = NULL; + src->serial = NULL; + src->manufacturer = NULL; + src->product = NULL; +} + void sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) { for (size_t i = 0; i < count; ++i) { diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index a8810b90..bed41cd6 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -40,6 +40,18 @@ struct sc_usb_device { void sc_usb_device_destroy(struct sc_usb_device *usb_device); +/** + * Move src to dst + * + * After this call, the content of src is undefined, except that + * sc_usb_device_destroy() can be called. + * + * This is useful to take a device from a list that will be destroyed, without + * making unnecessary copies. + */ +void +sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src); + void sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count); From 61969aeb80093d0777c7716a61698cbdaf9ddd71 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 19:12:59 +0100 Subject: [PATCH 0414/1133] Expose simple API to select a single USB device The caller just wants a single device. Handle all cases and error messages internally. PR #3005 --- app/src/scrcpy.c | 27 ++++++--------------- app/src/usb/scrcpy_otg.c | 51 +++++++--------------------------------- app/src/usb/usb.c | 45 ++++++++++++++++++++++++++++++++++- app/src/usb/usb.h | 6 ++--- 4 files changed, 63 insertions(+), 66 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 92159bba..d0aa2627 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -430,32 +430,19 @@ scrcpy(struct scrcpy_options *options) { } assert(serial); - struct sc_usb_device usb_devices[16]; - ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, - ARRAY_LEN(usb_devices)); - if (count <= 0) { - LOGE("Could not find USB device %s", serial); - sc_usb_destroy(&s->usb); - sc_acksync_destroy(&s->acksync); - goto aoa_hid_end; - } - - if (count > 1) { - LOGE("Multiple (%d) devices with serial %s", (int) count, serial); - sc_usb_devices_destroy_all(usb_devices, count); + struct sc_usb_device usb_device; + ok = sc_usb_select_device(&s->usb, serial, &usb_device); + if (!ok) { sc_usb_destroy(&s->usb); - sc_acksync_destroy(&s->acksync); goto aoa_hid_end; } - struct sc_usb_device *usb_device = &usb_devices[0]; - LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - usb_device->serial, usb_device->vid, usb_device->pid, - usb_device->manufacturer, usb_device->product); + usb_device.serial, usb_device.vid, usb_device.pid, + usb_device.manufacturer, usb_device.product); - ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL); - sc_usb_device_destroy(usb_device); + ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL); + sc_usb_device_destroy(&usb_device); if (!ok) { LOGE("Failed to connect to USB device %s", serial); sc_usb_destroy(&s->usb); diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 34c16ce1..9a7e3fe6 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -83,50 +83,17 @@ scrcpy_otg(struct scrcpy_options *options) { return false; } - struct sc_usb_device usb_devices[16]; - ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices, - ARRAY_LEN(usb_devices)); - if (count < 0) { - LOGE("Could not list USB devices"); - goto end; - } - - if (count == 0) { - if (serial) { - LOGE("Could not find USB device %s", serial); - } else { - LOGE("Could not find any USB device"); - } - goto end; - } - - if (count > 1) { - if (serial) { - LOGE("Multiple (%d) USB devices with serial %s:", (int) count, - serial); - } else { - LOGE("Multiple (%d) USB devices:", (int) count); - } - for (size_t i = 0; i < (size_t) count; ++i) { - struct sc_usb_device *d = &usb_devices[i]; - LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - d->serial, d->vid, d->pid, d->manufacturer, d->product); - } - if (!serial) { - LOGE("Specify the device via -s or --serial"); - } - sc_usb_devices_destroy_all(usb_devices, count); + struct sc_usb_device usb_device; + ok = sc_usb_select_device(&s->usb, serial, &usb_device); + if (!ok) { goto end; } - usb_device_initialized = true; - - struct sc_usb_device *usb_device = &usb_devices[0]; LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - usb_device->serial, usb_device->vid, usb_device->pid, - usb_device->manufacturer, usb_device->product); + usb_device.serial, usb_device.vid, usb_device.pid, + usb_device.manufacturer, usb_device.product); - ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL); + ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL); if (!ok) { goto end; } @@ -173,7 +140,7 @@ scrcpy_otg(struct scrcpy_options *options) { const char *window_title = options->window_title; if (!window_title) { - window_title = usb_device->product ? usb_device->product : "scrcpy"; + window_title = usb_device.product ? usb_device.product : "scrcpy"; } struct sc_screen_otg_params params = { @@ -192,7 +159,7 @@ scrcpy_otg(struct scrcpy_options *options) { } // usb_device not needed anymore - sc_usb_device_destroy(usb_device); + sc_usb_device_destroy(&usb_device); usb_device_initialized = false; ret = event_loop(s); @@ -223,7 +190,7 @@ end: } if (usb_device_initialized) { - sc_usb_device_destroy(usb_device); + sc_usb_device_destroy(&usb_device); } sc_usb_destroy(&s->usb); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index cc9c78cc..f1e008cd 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -96,7 +96,7 @@ sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) { } } -ssize_t +static ssize_t sc_usb_find_devices(struct sc_usb *usb, const char *serial, struct sc_usb_device *devices, size_t len) { libusb_device **list; @@ -119,6 +119,49 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, return idx; } +bool +sc_usb_select_device(struct sc_usb *usb, const char *serial, + struct sc_usb_device *out_device) { + struct sc_usb_device usb_devices[16]; + ssize_t count = sc_usb_find_devices(usb, serial, usb_devices, + ARRAY_LEN(usb_devices)); + if (count == -1) { + LOGE("Could not list USB devices"); + return false; + } + + if (count == 0) { + if (serial) { + LOGE("Could not find USB device %s", serial); + } else { + LOGE("Could not find any USB device"); + } + return false; + } + + if (count > 1) { + if (serial) { + LOGE("Multiple (%" SC_PRIsizet ") USB devices with serial %s:", + count, serial); + } else { + LOGE("Multiple (%" SC_PRIsizet ") USB devices:", count); + } + for (size_t i = 0; i < (size_t) count; ++i) { + struct sc_usb_device *d = &usb_devices[i]; + LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", + d->serial, d->vid, d->pid, d->manufacturer, d->product); + } + LOGE("Select a device via -s (--serial)"); + sc_usb_devices_destroy_all(usb_devices, count); + return false; + } + + assert(count == 1); + // Move usb_devices[0] into out_device (do not destroy usb_devices[0]) + *out_device = usb_devices[0]; + return true; +} + bool sc_usb_init(struct sc_usb *usb) { usb->handle = NULL; diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index bed41cd6..f0b62daf 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -61,9 +61,9 @@ sc_usb_init(struct sc_usb *usb); void sc_usb_destroy(struct sc_usb *usb); -ssize_t -sc_usb_find_devices(struct sc_usb *usb, const char *serial, - struct sc_usb_device *devices, size_t len); +bool +sc_usb_select_device(struct sc_usb *usb, const char *serial, + struct sc_usb_device *out_device); bool sc_usb_connect(struct sc_usb *usb, libusb_device *device, From 700503df6c4df77867f6172bc7a344a56bb91481 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 19:34:45 +0100 Subject: [PATCH 0415/1133] List and select USB devices separately List all USB devices in a first step, then select the matching one(s). This allows to report a user-friendly log message containing the list of devices, with the matching one(s) highlighted. PR #3005 --- app/src/usb/usb.c | 104 +++++++++++++++++++++++++++++++--------------- app/src/usb/usb.h | 1 + 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index f1e008cd..aec263cd 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -25,8 +25,7 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) { } static bool -accept_device(libusb_device *device, const char *serial, - struct sc_usb_device *out) { +sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) { // Do not log any USB error in this function, it is expected that many USB // devices available on the computer have permission restrictions @@ -48,22 +47,13 @@ accept_device(libusb_device *device, const char *serial, return false; } - if (serial) { - // Filter by serial - bool matches = !strcmp(serial, device_serial); - if (!matches) { - free(device_serial); - libusb_close(handle); - return false; - } - } - out->device = libusb_ref_device(device); out->serial = device_serial; out->vid = desc.idVendor; out->pid = desc.idProduct; out->manufacturer = read_string(handle, desc.iManufacturer); out->product = read_string(handle, desc.iProduct); + out->selected = false; libusb_close(handle); @@ -97,8 +87,8 @@ sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) { } static ssize_t -sc_usb_find_devices(struct sc_usb *usb, const char *serial, - struct sc_usb_device *devices, size_t len) { +sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices, + size_t len) { libusb_device **list; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { @@ -110,7 +100,7 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, for (size_t i = 0; i < (size_t) count && idx < len; ++i) { libusb_device *device = list[i]; - if (accept_device(device, serial, &devices[idx])) { + if (sc_usb_read_device(device, &devices[idx])) { ++idx; } } @@ -119,46 +109,94 @@ sc_usb_find_devices(struct sc_usb *usb, const char *serial, return idx; } +static bool +sc_usb_accept_device(const struct sc_usb_device *device, const char *serial) { + if (!serial) { + return true; + } + + return !strcmp(serial, device->serial); +} + +static size_t +sc_usb_devices_select(struct sc_usb_device *devices, size_t len, + const char *serial, size_t *idx_out) { + size_t count = 0; + for (size_t i = 0; i < len; ++i) { + struct sc_usb_device *device = &devices[i]; + device->selected = sc_usb_accept_device(device, serial); + if (device->selected) { + if (idx_out && !count) { + *idx_out = i; + } + ++count; + } + } + + return count; +} + +static void +sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices, + size_t count) { + for (size_t i = 0; i < count; ++i) { + struct sc_usb_device *d = &devices[i]; + const char *selection = d->selected ? "-->" : " "; + LOG(level, " %s %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", + selection, d->serial, d->vid, d->pid, d->manufacturer, d->product); + } +} + bool sc_usb_select_device(struct sc_usb *usb, const char *serial, struct sc_usb_device *out_device) { struct sc_usb_device usb_devices[16]; - ssize_t count = sc_usb_find_devices(usb, serial, usb_devices, - ARRAY_LEN(usb_devices)); + ssize_t count = + sc_usb_list_devices(usb, usb_devices, ARRAY_LEN(usb_devices)); if (count == -1) { LOGE("Could not list USB devices"); return false; } if (count == 0) { - if (serial) { - LOGE("Could not find USB device %s", serial); - } else { - LOGE("Could not find any USB device"); - } + LOGE("Could not find any USB device"); + return false; + } + + size_t sel_idx; // index of the single matching device if sel_count == 1 + size_t sel_count = + sc_usb_devices_select(usb_devices, count, serial, &sel_idx); + + if (sel_count == 0) { + // if count > 0 && sel_count == 0, then necessarily a serial is provided + assert(serial); + LOGE("Could not find USB device %s", serial); + sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count); + sc_usb_devices_destroy_all(usb_devices, count); return false; } - if (count > 1) { + if (sel_count > 1) { if (serial) { LOGE("Multiple (%" SC_PRIsizet ") USB devices with serial %s:", - count, serial); + sel_count, serial); } else { - LOGE("Multiple (%" SC_PRIsizet ") USB devices:", count); - } - for (size_t i = 0; i < (size_t) count; ++i) { - struct sc_usb_device *d = &usb_devices[i]; - LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - d->serial, d->vid, d->pid, d->manufacturer, d->product); + LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count); } LOGE("Select a device via -s (--serial)"); sc_usb_devices_destroy_all(usb_devices, count); return false; } - assert(count == 1); - // Move usb_devices[0] into out_device (do not destroy usb_devices[0]) - *out_device = usb_devices[0]; + assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 + struct sc_usb_device *device = &usb_devices[sel_idx]; + + LOGD("USB device found:"); + sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, usb_devices, count); + + // Move device into out_device (do not destroy device) + sc_usb_device_move(out_device, device); + sc_usb_devices_destroy_all(usb_devices, count); return true; } diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index f0b62daf..d264a536 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -35,6 +35,7 @@ struct sc_usb_device { char *product; uint16_t vid; uint16_t pid; + bool selected; }; void From 85ff70fc95b9b6a10f3aa834db0d2d1efcbbce0a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 21:20:12 +0100 Subject: [PATCH 0416/1133] Refactor device configuration Depending on the parameters passed to scrcpy, either the initial device serial is necessary or not. Reorganize to simplify the logic. PR #3005 --- app/src/server.c | 108 ++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index e8ae32af..b0b596ad 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -641,51 +641,37 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { return true; } - static bool -sc_server_configure_tcpip(struct sc_server *server) { - char *ip_port; - - const struct sc_server_params *params = &server->params; - - // If tcpip parameter is given, then it must connect to this address. - // Therefore, the device is unknown, so serial is meaningless at this point. - assert(!params->req_serial || !params->tcpip_dst); +sc_server_configure_tcpip_known_address(struct sc_server *server, + const char *addr) { + // Append ":5555" if no port is present + bool contains_port = strchr(addr, ':'); + char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr); + if (!ip_port) { + LOG_OOM(); + return false; + } - if (params->tcpip_dst) { - // Append ":5555" if no port is present - bool contains_port = strchr(params->tcpip_dst, ':'); - ip_port = contains_port ? strdup(params->tcpip_dst) - : append_port_5555(params->tcpip_dst); - if (!ip_port) { - LOG_OOM(); - return false; - } - } else { - // The device IP address must be retrieved from the current - // connected device - char *serial = sc_server_read_serial(server); - if (!serial) { - LOGE("Could not get device serial"); - return false; - } + server->serial = ip_port; + return sc_server_connect_to_tcpip(server, ip_port); +} - // The serial is either the real serial when connected via USB, or - // the IP:PORT when connected over TCP/IP. Only the latter contains - // a colon. - bool is_already_tcpip = strchr(serial, ':'); - if (is_already_tcpip) { - // Nothing to do - LOGI("Device already connected via TCP/IP: %s", serial); - free(serial); - return true; - } +static bool +sc_server_configure_tcpip_unknown_address(struct sc_server *server, + const char *serial) { + // The serial is either the real serial when connected via USB, or + // the IP:PORT when connected over TCP/IP. Only the latter contains + // a colon. + bool is_already_tcpip = strchr(serial, ':'); + if (is_already_tcpip) { + // Nothing to do + LOGI("Device already connected via TCP/IP: %s", serial); + return true; + } - ip_port = sc_server_switch_to_tcpip(server, serial); - free(serial); - if (!ip_port) { - return false; - } + char *ip_port = sc_server_switch_to_tcpip(server, serial); + if (!ip_port) { + return false; } server->serial = ip_port; @@ -698,16 +684,40 @@ run_server(void *data) { const struct sc_server_params *params = &server->params; - if (params->tcpip) { - bool ok = sc_server_configure_tcpip(server); - if (!ok) { + // params->tcpip_dst implies params->tcpip + assert(!params->tcpip_dst || params->tcpip); + + // If tcpip_dst parameter is given, then it must connect to this address. + // Therefore, the device is unknown, so serial is meaningless at this point. + assert(!params->req_serial || !params->tcpip_dst); + + // A device must be selected via a serial in all cases except when --tcpip= + // is called with a parameter (in that case, the device may initially not + // exist, and scrcpy will execute "adb connect"). + bool need_initial_serial = !params->tcpip_dst; + + bool ok; + if (need_initial_serial) { + char *serial = sc_server_read_serial(server); + if (!serial) { + LOGE("Could not get device serial"); goto error_connection_failed; } - assert(server->serial); + + if (params->tcpip) { + assert(!params->tcpip_dst); + ok = sc_server_configure_tcpip_unknown_address(server, serial); + free(serial); + if (!ok) { + goto error_connection_failed; + } + assert(server->serial); + } else { + server->serial = serial; + } } else { - server->serial = sc_server_read_serial(server); - if (!server->serial) { - LOGD("Could not get device serial"); + ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst); + if (!ok) { goto error_connection_failed; } } @@ -716,7 +726,7 @@ run_server(void *data) { assert(serial); LOGD("Device serial: %s", serial); - bool ok = push_server(&server->intr, serial); + ok = push_server(&server->intr, serial); if (!ok) { goto error_connection_failed; } From 4389de1c239c524f2a3aed2c985893dd3de74824 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 5 Feb 2022 22:32:40 +0100 Subject: [PATCH 0417/1133] Add adb devices parser Add a parser of `adb device -l` output, to extract a list of devices with their serial, state and model. PR #3005 --- app/meson.build | 2 + app/src/adb/adb_device.c | 26 ++++++ app/src/adb/adb_device.h | 33 ++++++++ app/src/adb/adb_parser.c | 159 +++++++++++++++++++++++++++++++++++- app/src/adb/adb_parser.h | 13 +++ app/tests/test_adb_parser.c | 159 ++++++++++++++++++++++++++++++++++++ 6 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 app/src/adb/adb_device.c create mode 100644 app/src/adb/adb_device.h diff --git a/app/meson.build b/app/meson.build index b2c0bc55..a9b39b1e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,7 @@ src = [ 'src/main.c', 'src/adb/adb.c', + 'src/adb/adb_device.c', 'src/adb/adb_parser.c', 'src/adb/adb_tunnel.c', 'src/cli.c', @@ -221,6 +222,7 @@ if get_option('buildtype') == 'debug' tests = [ ['test_adb_parser', [ 'tests/test_adb_parser.c', + 'src/adb/adb_device.c', 'src/adb/adb_parser.c', 'src/util/str.c', 'src/util/strbuf.c', diff --git a/app/src/adb/adb_device.c b/app/src/adb/adb_device.c new file mode 100644 index 00000000..b6ff16a7 --- /dev/null +++ b/app/src/adb/adb_device.c @@ -0,0 +1,26 @@ +#include "adb_device.h" + +#include + +void +sc_adb_device_destroy(struct sc_adb_device *device) { + free(device->serial); + free(device->state); + free(device->model); +} + +void +sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) { + *dst = *src; + src->serial = NULL; + src->state = NULL; + src->model = NULL; +} + +void +sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count) { + for (size_t i = 0; i < count; ++i) { + sc_adb_device_destroy(&devices[i]); + } +} + diff --git a/app/src/adb/adb_device.h b/app/src/adb/adb_device.h new file mode 100644 index 00000000..11b46c0c --- /dev/null +++ b/app/src/adb/adb_device.h @@ -0,0 +1,33 @@ +#ifndef SC_ADB_DEVICE_H +#define SC_ADB_DEVICE_H + +#include "common.h" + +#include +#include + +struct sc_adb_device { + char *serial; + char *state; + char *model; +}; + +void +sc_adb_device_destroy(struct sc_adb_device *device); + +/** + * Move src to dst + * + * After this call, the content of src is undefined, except that + * sc_adb_device_destroy() can be called. + * + * This is useful to take a device from a list that will be destroyed, without + * making unnecessary copies. + */ +void +sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src); + +void +sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count); + +#endif diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index 2a0fd8da..d41e1bcd 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -1,11 +1,168 @@ #include "adb_parser.h" #include +#include #include #include "util/log.h" #include "util/str.h" +bool +sc_adb_parse_device(char *line, struct sc_adb_device *device) { + // One device line looks like: + // "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + // "device:MyDevice transport_id:1" + + if (line[0] == '*') { + // Garbage lines printed by adb daemon while starting start with a '*' + return false; + } + + if (!strncmp("adb server", line, sizeof("adb server") - 1)) { + // Ignore lines starting with "adb server": + // adb server version (41) doesn't match this client (39); killing... + return false; + } + + char *s = line; // cursor in the line + + // After the serial: + // - "adb devices" writes a single '\t' + // - "adb devices -l" writes multiple spaces + // For flexibility, accept both. + size_t serial_len = strcspn(s, " \t"); + if (!serial_len) { + // empty serial + return false; + } + bool eol = s[serial_len] == '\0'; + if (eol) { + // serial alone is unexpected + return false; + } + s[serial_len] = '\0'; + char *serial = s; + s += serial_len + 1; + // After the serial, there might be several spaces + s += strspn(s, " \t"); // consume all separators + + size_t state_len = strcspn(s, " "); + if (!state_len) { + // empty state + return false; + } + eol = s[state_len] == '\0'; + s[state_len] = '\0'; + char *state = s; + + char *model = NULL; + if (!eol) { + s += state_len + 1; + + // Iterate over all properties "key:value key:value ..." + for (;;) { + size_t token_len = strcspn(s, " "); + if (!token_len) { + break; + } + eol = s[token_len] == '\0'; + s[token_len] = '\0'; + char *token = s; + + if (!strncmp("model:", token, sizeof("model:") - 1)) { + model = &token[sizeof("model:") - 1]; + // We only need the model + break; + } + + if (eol) { + break; + } else { + s+= token_len + 1; + } + } + } + + device->serial = strdup(serial); + if (!device->serial) { + return false; + } + + device->state = strdup(state); + if (!device->state) { + free(device->serial); + return false; + } + + if (model) { + device->model = strdup(model); + if (!device->model) { + LOG_OOM(); + // model is optional, do not fail + } + } else { + device->model = NULL; + } + + return true; +} + +ssize_t +sc_adb_parse_devices(char *str, struct sc_adb_device *devices, + size_t devices_len) { + size_t dev_count = 0; + +#define HEADER "List of devices attached" +#define HEADER_LEN (sizeof(HEADER) - 1) + bool header_found = false; + + size_t idx_line = 0; + while (str[idx_line] != '\0') { + char *line = &str[idx_line]; + size_t len = strcspn(line, "\n"); + + // The next line starts after the '\n' (replaced by `\0`) + idx_line += len; + + if (str[idx_line] != '\0') { + // The next line starts after the '\n' + ++idx_line; + } + + if (!header_found) { + if (!strncmp(line, HEADER, HEADER_LEN)) { + header_found = true; + } + // Skip everything until the header, there might be garbage lines + // related to daemon starting before + continue; + } + + // The line, but without any trailing '\r' + size_t line_len = sc_str_remove_trailing_cr(line, len); + line[line_len] = '\0'; + + bool ok = sc_adb_parse_device(line, &devices[dev_count]); + if (!ok) { + continue; + } + + ++dev_count; + + assert(dev_count <= devices_len); + if (dev_count == devices_len) { + // Max number of devices reached + break; + } + } + + if (!header_found) { + return -1; + } + + return dev_count; +} + static char * sc_adb_parse_device_ip_from_line(char *line) { // One line from "ip route" looks like: @@ -64,7 +221,7 @@ sc_adb_parse_device_ip_from_output(char *str) { if (str[idx_line] != '\0') { // The next line starts after the '\n' - idx_line += 1; + ++idx_line; } } diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index 7d116713..65493a2e 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -5,6 +5,19 @@ #include +#include "adb_device.h" + +/** + * Parse the available devices from the output of `adb devices` + * + * The parameter must be a NUL-terminated string. + * + * Warning: this function modifies the buffer for optimization purposes. + */ +ssize_t +sc_adb_parse_devices(char *str, struct sc_adb_device *devices, + size_t devices_len); + /** * Parse the ip from the output of `adb shell ip route` * diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 749ce433..990418c0 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -2,8 +2,158 @@ #include +#include "adb/adb_device.h" #include "adb/adb_parser.h" +static void test_adb_devices() { + char output[] = + "List of devices attached\n" + "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice transport_id:1\n" + "192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel " + "device:MyWifiDevice trandport_id:2\n"; + + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 2); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyModel", device->model)); + + device = &devices[1]; + assert(!strcmp("192.168.1.1:5555", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyWifiModel", device->model)); + + sc_adb_devices_destroy_all(devices, count); +} + +static void test_adb_devices_cr() { + char output[] = + "List of devices attached\r\n" + "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice transport_id:1\r\n" + "192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel " + "device:MyWifiDevice trandport_id:2\r\n"; + + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 2); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyModel", device->model)); + + device = &devices[1]; + assert(!strcmp("192.168.1.1:5555", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyWifiModel", device->model)); + + sc_adb_devices_destroy_all(devices, count); +} + +static void test_adb_devices_daemon_start() { + char output[] = + "* daemon not running; starting now at tcp:5037\n" + "* daemon started successfully\n" + "List of devices attached\n" + "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice transport_id:1\n"; + + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 1); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyModel", device->model)); + + sc_adb_device_destroy(device); +} + +static void test_adb_devices_daemon_start_mixed() { + char output[] = + "List of devices attached\n" + "adb server version (41) doesn't match this client (39); killing...\n" + "* daemon started successfully *\n" + "0123456789abcdef unauthorized usb:1-1\n" + "87654321 device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice\n"; + + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 2); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("unauthorized", device->state)); + fprintf(stderr, "==== [%s]\n", device->model); + assert(!device->model); + + device = &devices[1]; + assert(!strcmp("87654321", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyModel", device->model)); + + sc_adb_devices_destroy_all(devices, count); +} + +static void test_adb_devices_without_eol() { + char output[] = + "List of devices attached\n" + "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice transport_id:1"; + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 1); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("device", device->state)); + assert(!strcmp("MyModel", device->model)); + + sc_adb_device_destroy(device); +} + +static void test_adb_devices_without_header() { + char output[] = + "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " + "device:MyDevice transport_id:1\n"; + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == -1); +} + +static void test_adb_devices_corrupted() { + char output[] = + "List of devices attached\n" + "corrupted_garbage\n"; + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 0); +} + +static void test_adb_devices_spaces() { + char output[] = + "List of devices attached\n" + "0123456789abcdef unauthorized usb:1-4 transport_id:3\n"; + + struct sc_adb_device devices[16]; + ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); + assert(count == 1); + + struct sc_adb_device *device = &devices[0]; + assert(!strcmp("0123456789abcdef", device->serial)); + assert(!strcmp("unauthorized", device->state)); + assert(!device->model); + + sc_adb_device_destroy(device); +} + static void test_get_ip_single_line() { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34\r\r\n"; @@ -86,6 +236,15 @@ int main(int argc, char *argv[]) { (void) argc; (void) argv; + test_adb_devices(); + test_adb_devices_cr(); + test_adb_devices_daemon_start(); + test_adb_devices_daemon_start_mixed(); + test_adb_devices_without_eol(); + test_adb_devices_without_header(); + test_adb_devices_corrupted(); + test_adb_devices_spaces(); + test_get_ip_single_line(); test_get_ip_single_line_without_eol(); test_get_ip_single_line_with_trailing_space(); From 02d46b226210e407d940ea95f16047985f883174 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 15:02:29 +0100 Subject: [PATCH 0418/1133] Expose function to test if a serial is TCP/IP In practice, it just tests if the serial contains a ':', which is sufficient to distinguish ip:port from a real USB serial. PR #3005 --- app/src/adb/adb.c | 5 +++++ app/src/adb/adb.h | 9 +++++++++ app/src/server.c | 5 +---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 3cfc5a95..b00bd620 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -482,3 +482,8 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { return sc_adb_parse_device_ip_from_output(buf); } + +bool +sc_adb_is_serial_tcpip(const char *serial) { + return strchr(serial, ':'); +} diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 1a97c203..18a11438 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -93,4 +93,13 @@ sc_adb_get_serialno(struct sc_intr *intr, unsigned flags); char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); +/** + * Indicate if the serial represents an IP address + * + * In practice, it just returns true if and only if it contains a ':', which is + * sufficient to distinguish an ip:port from a real USB serial. + */ +bool +sc_adb_is_serial_tcpip(const char *serial); + #endif diff --git a/app/src/server.c b/app/src/server.c index b0b596ad..3dbda0e9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -659,10 +659,7 @@ sc_server_configure_tcpip_known_address(struct sc_server *server, static bool sc_server_configure_tcpip_unknown_address(struct sc_server *server, const char *serial) { - // The serial is either the real serial when connected via USB, or - // the IP:PORT when connected over TCP/IP. Only the latter contains - // a colon. - bool is_already_tcpip = strchr(serial, ':'); + bool is_already_tcpip = sc_adb_is_serial_tcpip(serial); if (is_already_tcpip) { // Nothing to do LOGI("Device already connected via TCP/IP: %s", serial); From 4692d13179fc244333975769c3236ed5fc8fcbd2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 15:04:00 +0100 Subject: [PATCH 0419/1133] Expose simple API to select a single adb device Select an adb device from the output of `adb device -l`. PR #3005 --- app/src/adb/adb.c | 160 +++++++++++++++++++++++++++++++++++++++ app/src/adb/adb.h | 10 +++ app/src/adb/adb_device.h | 1 + app/src/adb/adb_parser.c | 2 + app/src/server.c | 34 +++------ app/src/usb/usb.c | 1 + 6 files changed, 186 insertions(+), 22 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index b00bd620..ef316884 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -374,6 +374,166 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { return process_check_success_intr(intr, pid, "adb disconnect", flags); } +static ssize_t +sc_adb_list_devices(struct sc_intr *intr, unsigned flags, + struct sc_adb_device *devices, size_t len) { + const char *const argv[] = SC_ADB_COMMAND("devices", "-l"); + + sc_pipe pout; + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGE("Could not execute \"adb devices -l\""); + return -1; + } + + char buf[4096]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags); + if (!ok) { + return -1; + } + + if (r == -1) { + return -1; + } + + assert((size_t) r < sizeof(buf)); + if (r == sizeof(buf) - 1) { + // The implementation assumes that the output of "adb devices -l" fits + // in the buffer in a single pass + LOGW("Result of \"adb devices -l\" does not fit in 4Kb. " + "Please report an issue.\n"); + return -1; + } + + // It is parsed as a NUL-terminated string + buf[r] = '\0'; + + // List all devices to the output list directly + return sc_adb_parse_devices(buf, devices, len); +} + +static bool +sc_adb_accept_device(const struct sc_adb_device *device, const char *serial) { + if (!serial) { + return true; + } + + return !strcmp(serial, device->serial); +} + +static size_t +sc_adb_devices_select(struct sc_adb_device *devices, size_t len, + const char *serial, size_t *idx_out) { + size_t count = 0; + for (size_t i = 0; i < len; ++i) { + struct sc_adb_device *device = &devices[i]; + device->selected = sc_adb_accept_device(device, serial); + if (device->selected) { + if (idx_out && !count) { + *idx_out = i; + } + ++count; + } + } + + return count; +} + +static void +sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices, + size_t count) { + for (size_t i = 0; i < count; ++i) { + struct sc_adb_device *d = &devices[i]; + const char *selection = d->selected ? "-->" : " "; + const char *type = sc_adb_is_serial_tcpip(d->serial) ? "(tcpip)" + : " (usb)"; + LOG(level, " %s %s %-20s %16s %s", + selection, type, d->serial, d->state, d->model ? d->model : ""); + } +} + +static bool +sc_adb_device_check_state(struct sc_adb_device *device, + struct sc_adb_device *devices, size_t count) { + const char *state = device->state; + + if (!strcmp("device", state)) { + return true; + } + + if (!strcmp("unauthorized", state)) { + LOGE("Device is unauthorized:"); + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); + LOGE("A popup should open on the device to request authorization."); + LOGE("Check the FAQ: " + ""); + } + + return false; +} + +bool +sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags, + struct sc_adb_device *out_device) { + struct sc_adb_device devices[16]; + ssize_t count = + sc_adb_list_devices(intr, flags, devices, ARRAY_LEN(devices)); + if (count == -1) { + LOGE("Could not list ADB devices"); + return false; + } + + if (count == 0) { + LOGE("Could not find any ADB device"); + return false; + } + + size_t sel_idx; // index of the single matching device if sel_count == 1 + size_t sel_count = sc_adb_devices_select(devices, count, serial, &sel_idx); + + if (sel_count == 0) { + // if count > 0 && sel_count == 0, then necessarily a serial is provided + assert(serial); + LOGE("Could not find ADB device %s", serial); + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); + sc_adb_devices_destroy_all(devices, count); + return false; + } + + if (sel_count > 1) { + if (serial) { + LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:", + sel_count, serial); + } else { + LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count); + } + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); + LOGE("Select a device via -s (--serial)"); + sc_adb_devices_destroy_all(devices, count); + return false; + } + + assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 + struct sc_adb_device *device = &devices[sel_idx]; + + bool ok = sc_adb_device_check_state(device, devices, count); + if (!ok) { + sc_adb_devices_destroy_all(devices, count); + return false; + } + + LOGD("ADB device found:"); + sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, devices, count); + + // Move devics into out_device (do not destroy device) + sc_adb_device_move(out_device, device); + sc_adb_devices_destroy_all(devices, count); + return true; +} + char * sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, unsigned flags) { diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 18a11438..04e2c985 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -6,6 +6,7 @@ #include #include +#include "adb_device.h" #include "util/intr.h" #define SC_ADB_NO_STDOUT (1 << 0) @@ -69,6 +70,15 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags); bool sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); +/** + * Execute `adb devices` and parse the result to select a device + * + * Return true if a single matching device is found, and write it to out_device. + */ +bool +sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags, + struct sc_adb_device *out_device); + /** * Execute `adb getprop ` */ diff --git a/app/src/adb/adb_device.h b/app/src/adb/adb_device.h index 11b46c0c..ed8362e9 100644 --- a/app/src/adb/adb_device.h +++ b/app/src/adb/adb_device.h @@ -10,6 +10,7 @@ struct sc_adb_device { char *serial; char *state; char *model; + bool selected; }; void diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index d41e1bcd..85e8ffaf 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -104,6 +104,8 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) { device->model = NULL; } + device->selected = false; + return true; } diff --git a/app/src/server.c b/app/src/server.c index 3dbda0e9..3264c4ee 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -503,22 +503,6 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } -static char * -sc_server_read_serial(struct sc_server *server) { - char *serial; - if (server->params.req_serial) { - // The serial is already known - serial = strdup(server->params.req_serial); - if (!serial) { - LOG_OOM(); - } - } else { - serial = sc_adb_get_serialno(&server->intr, 0); - } - - return serial; -} - static bool is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { struct sc_intr *intr = &server->intr; @@ -695,22 +679,28 @@ run_server(void *data) { bool ok; if (need_initial_serial) { - char *serial = sc_server_read_serial(server); - if (!serial) { - LOGE("Could not get device serial"); + struct sc_adb_device device; + ok = sc_adb_select_device(&server->intr, params->req_serial, 0, + &device); + if (!ok) { goto error_connection_failed; } if (params->tcpip) { assert(!params->tcpip_dst); - ok = sc_server_configure_tcpip_unknown_address(server, serial); - free(serial); + ok = sc_server_configure_tcpip_unknown_address(server, + device.serial); + sc_adb_device_destroy(&device); if (!ok) { goto error_connection_failed; } assert(server->serial); } else { - server->serial = serial; + // "move" the device.serial without copy + server->serial = device.serial; + // the serial must not be freed by the destructor + device.serial = NULL; + sc_adb_device_destroy(&device); } } else { ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index aec263cd..9edb1866 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -183,6 +183,7 @@ sc_usb_select_device(struct sc_usb *usb, const char *serial, } else { LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count); } + sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count); LOGE("Select a device via -s (--serial)"); sc_usb_devices_destroy_all(usb_devices, count); return false; From 0a619dc9efa358c03372e08192bda075ecbeb0a6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 15:11:35 +0100 Subject: [PATCH 0420/1133] Allow selecting a device from IP without port Since the previous commit, if a serial is given via -s/--serial (either a real USB serial or an IP:port), a device is selected if its serial matches exactly. In addition, if the user pass an IP without a port, then select any device with this IP, regardless of the port (so that "192.168.1.1" matches any "192.168.1.1:port"). This is also the default behavior of adb. PR #3005 --- app/src/adb/adb.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index ef316884..baa0e406 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -421,6 +421,24 @@ sc_adb_accept_device(const struct sc_adb_device *device, const char *serial) { return true; } + char *device_serial_colon = strchr(device->serial, ':'); + if (device_serial_colon) { + // The device serial is an IP:port... + char *serial_colon = strchr(serial, ':'); + if (!serial_colon) { + // But the requested serial has no ':', so only consider the IP part + // of the device serial. This allows to use "192.168.1.1" to match + // any "192.168.1.1:port". + size_t serial_len = strlen(serial); + size_t device_ip_len = device_serial_colon - device->serial; + if (serial_len != device_ip_len) { + // They are not equal, they don't even have the same length + return false; + } + return !strncmp(serial, device->serial, device_ip_len); + } + } + return !strcmp(serial, device->serial); } From 9c545e8c29b743d908285ca17fa34187717936a3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 15:22:01 +0100 Subject: [PATCH 0421/1133] Remove sc_adb_get_serialno() The device serial is now retrieved from `adb devices -l`, `adb get-serialno` is not called anymore. PR #3005 --- app/src/adb/adb.c | 32 -------------------------------- app/src/adb/adb.h | 8 -------- 2 files changed, 40 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index baa0e406..2afe11c5 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -587,38 +587,6 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, return strdup(buf); } -char * -sc_adb_get_serialno(struct sc_intr *intr, unsigned flags) { - const char *const argv[] = SC_ADB_COMMAND("get-serialno"); - - sc_pipe pout; - sc_pid pid = sc_adb_execute_p(argv, flags, &pout); - if (pid == SC_PROCESS_NONE) { - LOGE("Could not execute \"adb get-serialno\""); - return NULL; - } - - char buf[128]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); - sc_pipe_close(pout); - - bool ok = process_check_success_intr(intr, pid, "adb get-serialno", flags); - if (!ok) { - return NULL; - } - - if (r == -1) { - return NULL; - } - - assert((size_t) r < sizeof(buf)); - buf[r] = '\0'; - size_t len = strcspn(buf, " \r\n"); - buf[len] = '\0'; - - return strdup(buf); -} - char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { assert(serial); diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 04e2c985..ac9e15b0 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -86,14 +86,6 @@ char * sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, unsigned flags); -/** - * Execute `adb get-serialno` - * - * Return the result, to be freed by the caller, or NULL on error. - */ -char * -sc_adb_get_serialno(struct sc_intr *intr, unsigned flags); - /** * Attempt to retrieve the device IP * From 5ed13ef4776f41afd98df4cc22e27832d9c9bd3f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 15:28:23 +0100 Subject: [PATCH 0422/1133] Execute adb start-server This does nothing if the adb daemon is already started, but allows to print any output/errors to the console. Otherwise, the daemon starting would occur during `adb devices`, which does not output to the console because the result is parsed. PR #3005 --- app/src/adb/adb.c | 8 ++++++++ app/src/adb/adb.h | 3 +++ app/src/server.c | 10 +++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 2afe11c5..5b61788a 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -194,6 +194,14 @@ sc_adb_execute(const char *const argv[], unsigned flags) { return sc_adb_execute_p(argv, flags, NULL); } +bool +sc_adb_start_server(struct sc_intr *intr, unsigned flags) { + const char *const argv[] = SC_ADB_COMMAND("start-server"); + + sc_pid pid = sc_adb_execute(argv, flags); + return process_check_success_intr(intr, pid, "adb start-server", flags); +} + bool sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name, unsigned flags) { diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index ac9e15b0..1381031f 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -21,6 +21,9 @@ sc_adb_get_executable(void); sc_pid sc_adb_execute(const char *const argv[], unsigned flags); +bool +sc_adb_start_server(struct sc_intr *intr, unsigned flags); + bool sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name, unsigned flags); diff --git a/app/src/server.c b/app/src/server.c index 3264c4ee..f6e8b6df 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -665,6 +665,15 @@ run_server(void *data) { const struct sc_server_params *params = &server->params; + // Execute "adb start-server" before "adb devices" so that daemon starting + // output/errors is correctly printed in the console ("adb devices" output + // is parsed, so it is not output) + bool ok = sc_adb_start_server(&server->intr, 0); + if (!ok) { + LOGE("Could not start adb daemon"); + goto error_connection_failed; + } + // params->tcpip_dst implies params->tcpip assert(!params->tcpip_dst || params->tcpip); @@ -677,7 +686,6 @@ run_server(void *data) { // exist, and scrcpy will execute "adb connect"). bool need_initial_serial = !params->tcpip_dst; - bool ok; if (need_initial_serial) { struct sc_adb_device device; ok = sc_adb_select_device(&server->intr, params->req_serial, 0, From 146f65d7b2b046c2d9a218116b4483f1633b0322 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 16:06:46 +0100 Subject: [PATCH 0423/1133] Introduce adb device selector Currently, a device is selected either from a specific serial, or if it is the only one connected. In order to support selecting the only device connected via USB or via TCP/IP separately, introduce a new selection structure. PR #3005 --- app/src/adb/adb.c | 98 +++++++++++++++++++++++++++++++---------------- app/src/adb/adb.h | 15 +++++++- app/src/server.c | 10 ++++- 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 5b61788a..2bfa9f1e 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -424,39 +424,49 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, } static bool -sc_adb_accept_device(const struct sc_adb_device *device, const char *serial) { - if (!serial) { - return true; - } - - char *device_serial_colon = strchr(device->serial, ':'); - if (device_serial_colon) { - // The device serial is an IP:port... - char *serial_colon = strchr(serial, ':'); - if (!serial_colon) { - // But the requested serial has no ':', so only consider the IP part - // of the device serial. This allows to use "192.168.1.1" to match - // any "192.168.1.1:port". - size_t serial_len = strlen(serial); - size_t device_ip_len = device_serial_colon - device->serial; - if (serial_len != device_ip_len) { - // They are not equal, they don't even have the same length - return false; +sc_adb_accept_device(const struct sc_adb_device *device, + const struct sc_adb_device_selector *selector) { + switch (selector->type) { + case SC_ADB_DEVICE_SELECT_ALL: + return true; + case SC_ADB_DEVICE_SELECT_SERIAL: + assert(selector->serial); + char *device_serial_colon = strchr(device->serial, ':'); + if (device_serial_colon) { + // The device serial is an IP:port... + char *serial_colon = strchr(selector->serial, ':'); + if (!serial_colon) { + // But the requested serial has no ':', so only consider + // the IP part of the device serial. This allows to use + // "192.168.1.1" to match any "192.168.1.1:port". + size_t serial_len = strlen(selector->serial); + size_t device_ip_len = device_serial_colon - device->serial; + if (serial_len != device_ip_len) { + // They are not equal, they don't even have the same + // length + return false; + } + return !strncmp(selector->serial, device->serial, + device_ip_len); + } } - return !strncmp(serial, device->serial, device_ip_len); - } + return !strcmp(selector->serial, device->serial); + default: + assert(!"Missing SC_ADB_DEVICE_SELECT_* handling"); + break; } - return !strcmp(serial, device->serial); + return false; } static size_t sc_adb_devices_select(struct sc_adb_device *devices, size_t len, - const char *serial, size_t *idx_out) { + const struct sc_adb_device_selector *selector, + size_t *idx_out) { size_t count = 0; for (size_t i = 0; i < len; ++i) { struct sc_adb_device *device = &devices[i]; - device->selected = sc_adb_accept_device(device, serial); + device->selected = sc_adb_accept_device(device, selector); if (device->selected) { if (idx_out && !count) { *idx_out = i; @@ -502,8 +512,9 @@ sc_adb_device_check_state(struct sc_adb_device *device, } bool -sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags, - struct sc_adb_device *out_device) { +sc_adb_select_device(struct sc_intr *intr, + const struct sc_adb_device_selector *selector, + unsigned flags, struct sc_adb_device *out_device) { struct sc_adb_device devices[16]; ssize_t count = sc_adb_list_devices(intr, flags, devices, ARRAY_LEN(devices)); @@ -518,23 +529,42 @@ sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags, } size_t sel_idx; // index of the single matching device if sel_count == 1 - size_t sel_count = sc_adb_devices_select(devices, count, serial, &sel_idx); + size_t sel_count = + sc_adb_devices_select(devices, count, selector, &sel_idx); if (sel_count == 0) { - // if count > 0 && sel_count == 0, then necessarily a serial is provided - assert(serial); - LOGE("Could not find ADB device %s", serial); + // if count > 0 && sel_count == 0, then necessarily a selection is + // requested + assert(selector->type != SC_ADB_DEVICE_SELECT_ALL); + + switch (selector->type) { + case SC_ADB_DEVICE_SELECT_SERIAL: + assert(selector->serial); + LOGE("Could not find ADB device %s:", selector->serial); + break; + default: + assert(!"Unexpected selector type"); + break; + } + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); sc_adb_devices_destroy_all(devices, count); return false; } if (sel_count > 1) { - if (serial) { - LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:", - sel_count, serial); - } else { - LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count); + switch (selector->type) { + case SC_ADB_DEVICE_SELECT_ALL: + LOGE("Multiple (%" SC_PRIsizet ") ADB devices:", sel_count); + break; + case SC_ADB_DEVICE_SELECT_SERIAL: + assert(selector->serial); + LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:", + sel_count, selector->serial); + break; + default: + assert(!"Unexpected selector type"); + break; } sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); LOGE("Select a device via -s (--serial)"); diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 1381031f..dc302a85 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -18,6 +18,16 @@ const char * sc_adb_get_executable(void); +enum sc_adb_device_selector_type { + SC_ADB_DEVICE_SELECT_ALL, + SC_ADB_DEVICE_SELECT_SERIAL, +}; + +struct sc_adb_device_selector { + enum sc_adb_device_selector_type type; + const char *serial; +}; + sc_pid sc_adb_execute(const char *const argv[], unsigned flags); @@ -79,8 +89,9 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags); * Return true if a single matching device is found, and write it to out_device. */ bool -sc_adb_select_device(struct sc_intr *intr, const char *serial, unsigned flags, - struct sc_adb_device *out_device); +sc_adb_select_device(struct sc_intr *intr, + const struct sc_adb_device_selector *selector, + unsigned flags, struct sc_adb_device *out_device); /** * Execute `adb getprop ` diff --git a/app/src/server.c b/app/src/server.c index f6e8b6df..c0c33a2c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -687,9 +687,15 @@ run_server(void *data) { bool need_initial_serial = !params->tcpip_dst; if (need_initial_serial) { + struct sc_adb_device_selector selector; + if (params->req_serial) { + selector.type = SC_ADB_DEVICE_SELECT_SERIAL; + selector.serial = params->req_serial; + } else { + selector.type = SC_ADB_DEVICE_SELECT_ALL; + } struct sc_adb_device device; - ok = sc_adb_select_device(&server->intr, params->req_serial, 0, - &device); + ok = sc_adb_select_device(&server->intr, &selector, 0, &device); if (!ok) { goto error_connection_failed; } From 582161607e8cb2d6496b1962a5f2efa091ab3ccb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 18:40:18 +0100 Subject: [PATCH 0424/1133] Add option to select USB or TCP/IP devices If several devices are connected (as listed by `adb devices`), it was necessary to provide the explicit serial via -s/--serial. If only one device is connected via USB (respectively, via TCP/IP), it might be convenient to select it automatically. For this purpose, two new options are introduced: - -d/--select-usb: select the single device connected over USB - -e/--select-tcpip: select the single device connected over TCP/IP PR #3005 --- app/scrcpy.1 | 12 ++++++++++++ app/src/adb/adb.c | 21 ++++++++++++++++++++- app/src/adb/adb.h | 2 ++ app/src/cli.c | 30 ++++++++++++++++++++++++++++-- app/src/options.c | 2 ++ app/src/options.h | 2 ++ app/src/scrcpy.c | 2 ++ app/src/server.c | 9 +++++++++ app/src/server.h | 2 ++ 9 files changed, 79 insertions(+), 3 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0f618cb4..317cec05 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -43,6 +43,12 @@ The values are expressed in the device natural orientation (typically, portrait .B \-\-max\-size value is computed on the cropped size. +.TP +.B \-d, \-\-select\-usb +Use USB device (if there is exactly one, like adb -d). + +Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). + .TP .BI "\-\-disable-screensaver" Disable screensaver while scrcpy is running. @@ -62,6 +68,12 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc Default is 0 (no buffering). +.TP +.B \-e, \-\-select\-tcpip +Use TCP/IP device (if there is exactly one, like adb -e). + +Also see \fB\-d\fR (\fB\-\-select\-usb\fR). + .TP .BI "\-\-encoder " name Use a specific MediaCodec encoder (must be a H.264 encoder). diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 2bfa9f1e..e415eb4f 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -451,6 +451,10 @@ sc_adb_accept_device(const struct sc_adb_device *device, } } return !strcmp(selector->serial, device->serial); + case SC_ADB_DEVICE_SELECT_USB: + return !sc_adb_is_serial_tcpip(device->serial); + case SC_ADB_DEVICE_SELECT_TCPIP: + return sc_adb_is_serial_tcpip(device->serial); default: assert(!"Missing SC_ADB_DEVICE_SELECT_* handling"); break; @@ -542,6 +546,12 @@ sc_adb_select_device(struct sc_intr *intr, assert(selector->serial); LOGE("Could not find ADB device %s:", selector->serial); break; + case SC_ADB_DEVICE_SELECT_USB: + LOGE("Could not find any ADB device over USB:"); + break; + case SC_ADB_DEVICE_SELECT_TCPIP: + LOGE("Could not find any ADB device over TCP/IP:"); + break; default: assert(!"Unexpected selector type"); break; @@ -562,12 +572,21 @@ sc_adb_select_device(struct sc_intr *intr, LOGE("Multiple (%" SC_PRIsizet ") ADB devices with serial %s:", sel_count, selector->serial); break; + case SC_ADB_DEVICE_SELECT_USB: + LOGE("Multiple (%" SC_PRIsizet ") ADB devices over USB:", + sel_count); + break; + case SC_ADB_DEVICE_SELECT_TCPIP: + LOGE("Multiple (%" SC_PRIsizet ") ADB devices over TCP/IP:", + sel_count); + break; default: assert(!"Unexpected selector type"); break; } sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); - LOGE("Select a device via -s (--serial)"); + LOGE("Select a device via -s (--serial), -d (--select-usb) or -e " + "(--select-tcpip)"); sc_adb_devices_destroy_all(devices, count); return false; } diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index dc302a85..6ea6e897 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -21,6 +21,8 @@ sc_adb_get_executable(void); enum sc_adb_device_selector_type { SC_ADB_DEVICE_SELECT_ALL, SC_ADB_DEVICE_SELECT_SERIAL, + SC_ADB_DEVICE_SELECT_USB, + SC_ADB_DEVICE_SELECT_TCPIP, }; struct sc_adb_device_selector { diff --git a/app/src/cli.c b/app/src/cli.c index 7567a10b..6e58feff 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -118,6 +118,12 @@ static const struct sc_option options[] = { "(typically, portrait for a phone, landscape for a tablet). " "Any --max-size value is cmoputed on the cropped size.", }, + { + .shortopt = 'd', + .longopt = "select-usb", + .text = "Use USB device (if there is exactly one, like adb -d).\n" + "Also see -e (--select-tcpip).", + }, { .longopt_id = OPT_DISABLE_SCREENSAVER, .longopt = "disable-screensaver", @@ -141,6 +147,12 @@ static const struct sc_option options[] = { "This increases latency to compensate for jitter.\n" "Default is 0 (no buffering).", }, + { + .shortopt = 'e', + .longopt = "select-tcpip", + .text = "Use TCP/IP device (if there is exactly one, like adb -e).\n" + "Also see -d (--select-usb).", + }, { .longopt_id = OPT_ENCODER_NAME, .longopt = "encoder", @@ -1320,6 +1332,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case 'd': + opts->select_usb = true; + break; + case 'e': + opts->select_tcpip = true; + break; case 'f': opts->fullscreen = true; break; @@ -1559,8 +1577,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], // If a TCP/IP address is provided, then tcpip must be enabled assert(opts->tcpip || !opts->tcpip_dst); - if (opts->serial && opts->tcpip_dst) { - LOGE("Incompatible options: -s/--serial and --tcpip with an argument"); + unsigned selectors = !!opts->serial + + !!opts->tcpip_dst + + opts->select_tcpip + + opts->select_usb; + if (selectors > 1) { + LOGE("At most one device selector option may be passed, among:\n" + " --serial (-s)\n" + " --select-usb (-d)\n" + " --select-tcpip (-e)\n" + " --tcpip= (with an argument)"); return false; } diff --git a/app/src/options.c b/app/src/options.c index c94f798d..377c5590 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -60,4 +60,6 @@ const struct scrcpy_options scrcpy_options_default = { .downsize_on_error = true, .tcpip = false, .tcpip_dst = NULL, + .select_tcpip = false, + .select_usb = false, }; diff --git a/app/src/options.h b/app/src/options.h index 1591a065..44104734 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -135,6 +135,8 @@ struct scrcpy_options { bool downsize_on_error; bool tcpip; const char *tcpip_dst; + bool select_usb; + bool select_tcpip; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d0aa2627..c3a481d0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -297,6 +297,8 @@ scrcpy(struct scrcpy_options *options) { struct sc_server_params params = { .req_serial = options->serial, + .select_usb = options->select_usb, + .select_tcpip = options->select_tcpip, .log_level = options->log_level, .crop = options->crop, .port_range = options->port_range, diff --git a/app/src/server.c b/app/src/server.c index c0c33a2c..0de51c7e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -687,10 +687,19 @@ run_server(void *data) { bool need_initial_serial = !params->tcpip_dst; if (need_initial_serial) { + // At most one of the 3 following parameters may be set + assert(!!params->req_serial + + params->select_usb + + params->select_tcpip <= 1); + struct sc_adb_device_selector selector; if (params->req_serial) { selector.type = SC_ADB_DEVICE_SELECT_SERIAL; selector.serial = params->req_serial; + } else if (params->select_usb) { + selector.type = SC_ADB_DEVICE_SELECT_USB; + } else if (params->select_tcpip) { + selector.type = SC_ADB_DEVICE_SELECT_TCPIP; } else { selector.type = SC_ADB_DEVICE_SELECT_ALL; } diff --git a/app/src/server.h b/app/src/server.h index c2293118..5818b43c 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,6 +44,8 @@ struct sc_server_params { bool downsize_on_error; bool tcpip; const char *tcpip_dst; + bool select_usb; + bool select_tcpip; }; struct sc_server { From dc5276b0e17cf7acd80726f3a31a8ac911f35e5e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Feb 2022 18:45:02 +0100 Subject: [PATCH 0425/1133] Mention --select-usb and --select-tcpip in README PR #3005 --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8ddf907..dbbc4d71 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ scrcpy -b2M -m800 # short version #### Multi-devices -If several devices are listed in `adb devices`, you must specify the _serial_: +If several devices are listed in `adb devices`, you can specify the _serial_: ```bash scrcpy --serial 0123456789abcdef @@ -436,6 +436,19 @@ scrcpy --serial 192.168.0.1:5555 scrcpy -s 192.168.0.1:5555 # short version ``` +If only one device is connected via either USB or TCP/IP, it is possible to +select it automatically: + +```bash +# Select the only device connected via USB +scrcpy -d # like adb -d +scrcpy --select-usb # long version + +# Select the only device connected via TCP/IP +scrcpy -e # like adb -e +scrcpy --select-tcpip # long version +``` + You can start several instances of _scrcpy_ for several devices. #### Autostart on device connection From 29828aa3307c78f54a3169db156222ae91182468 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 9 Feb 2022 09:51:22 +0100 Subject: [PATCH 0426/1133] Log device opening errors during listing Without this log, the user would have no way to know that a USB device is rejected because it could not be opened (typically due to insufficient permissions). --- app/src/usb/usb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 9edb1866..c7963726 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -38,6 +38,10 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) { libusb_device_handle *handle; result = libusb_open(device, &handle); if (result < 0) { + // Log at debug level because it is expected that some non-Android USB + // devices present on the computer require special permissions + LOGD("Open USB device %04" PRIx16 ":%04" PRIx16 ": libusb error: %s", + desc.idVendor, desc.idProduct, libusb_strerror(result)); return false; } From 9477594f80d0851423d842993b7a658f33ace03b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 8 Feb 2022 20:59:38 +0100 Subject: [PATCH 0427/1133] Move version handling to a separate file This will avoid to include all dependencies headers from main.c. --- app/meson.build | 1 + app/src/main.c | 24 ++---------------------- app/src/version.c | 29 +++++++++++++++++++++++++++++ app/src/version.h | 9 +++++++++ 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 app/src/version.c create mode 100644 app/src/version.h diff --git a/app/meson.build b/app/meson.build index a9b39b1e..6557c3b8 100644 --- a/app/meson.build +++ b/app/meson.build @@ -26,6 +26,7 @@ src = [ 'src/scrcpy.c', 'src/screen.c', 'src/server.c', + 'src/version.c', 'src/video_buffer.c', 'src/util/acksync.c', 'src/util/file.c', diff --git a/app/src/main.c b/app/src/main.c index 8a8c029c..2d1575f8 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -15,27 +15,7 @@ #include "scrcpy.h" #include "usb/scrcpy_otg.h" #include "util/log.h" - -static void -print_version(void) { - printf("\ndependencies:\n"); - printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, - SDL_PATCHLEVEL); - printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, - LIBAVCODEC_VERSION_MINOR, - LIBAVCODEC_VERSION_MICRO); - printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, - LIBAVFORMAT_VERSION_MINOR, - LIBAVFORMAT_VERSION_MICRO); - printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, - LIBAVUTIL_VERSION_MINOR, - LIBAVUTIL_VERSION_MICRO); -#ifdef HAVE_V4L2 - printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, - LIBAVDEVICE_VERSION_MINOR, - LIBAVDEVICE_VERSION_MICRO); -#endif -} +#include "version.h" int main(int argc, char *argv[]) { @@ -71,7 +51,7 @@ main(int argc, char *argv[]) { } if (args.version) { - print_version(); + scrcpy_print_version(); return 0; } diff --git a/app/src/version.c b/app/src/version.c new file mode 100644 index 00000000..d0e93481 --- /dev/null +++ b/app/src/version.c @@ -0,0 +1,29 @@ +#include "version.h" + +#include +#include +#include +#ifdef HAVE_V4L2 +# include +#endif + +void +scrcpy_print_version(void) { + printf("\ndependencies:\n"); + printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, + SDL_PATCHLEVEL); + printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, + LIBAVCODEC_VERSION_MINOR, + LIBAVCODEC_VERSION_MICRO); + printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, + LIBAVFORMAT_VERSION_MINOR, + LIBAVFORMAT_VERSION_MICRO); + printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, + LIBAVUTIL_VERSION_MINOR, + LIBAVUTIL_VERSION_MICRO); +#ifdef HAVE_V4L2 + printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, + LIBAVDEVICE_VERSION_MINOR, + LIBAVDEVICE_VERSION_MICRO); +#endif +} diff --git a/app/src/version.h b/app/src/version.h new file mode 100644 index 00000000..920360e8 --- /dev/null +++ b/app/src/version.h @@ -0,0 +1,9 @@ +#ifndef SC_VERSION_H +#define SC_VERSION_H + +#include "common.h" + +void +scrcpy_print_version(void); + +#endif From 9a546ef1afccb7457fea29ad8b51d8ae24137e46 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 8 Feb 2022 21:06:49 +0100 Subject: [PATCH 0428/1133] Print both compiled and linked versions of libs On --version, print both the version scrcpy had been compiled against, and the version linked at runtime. --- app/src/version.c | 58 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/app/src/version.c b/app/src/version.c index d0e93481..40688aae 100644 --- a/app/src/version.c +++ b/app/src/version.c @@ -9,21 +9,49 @@ void scrcpy_print_version(void) { - printf("\ndependencies:\n"); - printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, - SDL_PATCHLEVEL); - printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, - LIBAVCODEC_VERSION_MINOR, - LIBAVCODEC_VERSION_MICRO); - printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, - LIBAVFORMAT_VERSION_MINOR, - LIBAVFORMAT_VERSION_MICRO); - printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, - LIBAVUTIL_VERSION_MINOR, - LIBAVUTIL_VERSION_MICRO); + printf("\nDependencies (compiled / linked):\n"); + + SDL_version sdl; + SDL_GetVersion(&sdl); + printf(" - SDL: %u.%u.%u / %u.%u.%u\n", + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, + (unsigned) sdl.major, (unsigned) sdl.minor, (unsigned) sdl.patch); + + unsigned avcodec = avcodec_version(); + printf(" - libavcodec: %u.%u.%u / %u.%u.%u\n", + LIBAVCODEC_VERSION_MAJOR, + LIBAVCODEC_VERSION_MINOR, + LIBAVCODEC_VERSION_MICRO, + AV_VERSION_MAJOR(avcodec), + AV_VERSION_MINOR(avcodec), + AV_VERSION_MICRO(avcodec)); + + unsigned avformat = avformat_version(); + printf(" - libavformat: %u.%u.%u / %u.%u.%u\n", + LIBAVFORMAT_VERSION_MAJOR, + LIBAVFORMAT_VERSION_MINOR, + LIBAVFORMAT_VERSION_MICRO, + AV_VERSION_MAJOR(avformat), + AV_VERSION_MINOR(avformat), + AV_VERSION_MICRO(avformat)); + + unsigned avutil = avutil_version(); + printf(" - libavutil: %u.%u.%u / %u.%u.%u\n", + LIBAVUTIL_VERSION_MAJOR, + LIBAVUTIL_VERSION_MINOR, + LIBAVUTIL_VERSION_MICRO, + AV_VERSION_MAJOR(avutil), + AV_VERSION_MINOR(avutil), + AV_VERSION_MICRO(avutil)); + #ifdef HAVE_V4L2 - printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR, - LIBAVDEVICE_VERSION_MINOR, - LIBAVDEVICE_VERSION_MICRO); + unsigned avdevice = avdevice_version(); + printf(" - libavdevice: %u.%u.%u / %u.%u.%u\n", + LIBAVDEVICE_VERSION_MAJOR, + LIBAVDEVICE_VERSION_MINOR, + LIBAVDEVICE_VERSION_MICRO, + AV_VERSION_MAJOR(avdevice), + AV_VERSION_MINOR(avdevice), + AV_VERSION_MICRO(avdevice)); #endif } From f86df817f908a5fd5e7e6eaf9565ec76b5ead092 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 8 Feb 2022 21:28:55 +0100 Subject: [PATCH 0429/1133] Print libusb version on --version --- app/src/version.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/version.c b/app/src/version.c index 40688aae..90ea3334 100644 --- a/app/src/version.c +++ b/app/src/version.c @@ -6,6 +6,9 @@ #ifdef HAVE_V4L2 # include #endif +#ifdef HAVE_USB +# include +#endif void scrcpy_print_version(void) { @@ -54,4 +57,11 @@ scrcpy_print_version(void) { AV_VERSION_MINOR(avdevice), AV_VERSION_MICRO(avdevice)); #endif + +#ifdef HAVE_USB + const struct libusb_version *usb = libusb_get_version(); + // The compiled version may not be known + printf(" - libusb: - / %u.%u.%u\n", + (unsigned) usb->major, (unsigned) usb->minor, (unsigned) usb->micro); +#endif } From c00a31f1b063280a763b44b61a7cf4aab6576ff6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 9 Feb 2022 18:16:34 +0100 Subject: [PATCH 0430/1133] Pass --buildtype=release as a single meson arg For consistency with the other arguments --- BUILD.md | 4 ++-- install_release.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index 1e713b93..f683be95 100644 --- a/BUILD.md +++ b/BUILD.md @@ -258,7 +258,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk Then, build: ```bash -meson x --buildtype release --strip -Db_lto=true +meson x --buildtype=release --strip -Db_lto=true ninja -Cx # DO NOT RUN AS ROOT ``` @@ -279,7 +279,7 @@ Download the prebuilt server somewhere, and specify its path during the Meson configuration: ```bash -meson x --buildtype release --strip -Db_lto=true \ +meson x --buildtype=release --strip -Db_lto=true \ -Dprebuilt_server=/path/to/scrcpy-server ninja -Cx # DO NOT RUN AS ROOT ``` diff --git a/install_release.sh b/install_release.sh index 69dfefdc..6dd71d25 100755 --- a/install_release.sh +++ b/install_release.sh @@ -12,7 +12,7 @@ echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check echo "[scrcpy] Building client..." rm -rf "$BUILDDIR" -meson "$BUILDDIR" --buildtype release --strip -Db_lto=true \ +meson "$BUILDDIR" --buildtype=release --strip -Db_lto=true \ -Dprebuilt_server=scrcpy-server cd "$BUILDDIR" ninja From 8498a2e8a669a28fb1078575a09b32e9f020658d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 9 Feb 2022 23:01:49 +0100 Subject: [PATCH 0431/1133] Reorder release.mk recipes Group prepare-deps for win32 and win64. --- release.mk | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/release.mk b/release.mk index 37b8d5c5..755e94d4 100644 --- a/release.mk +++ b/release.mk @@ -67,6 +67,11 @@ prepare-deps-win32: @prebuilt-deps/prepare-sdl.sh @prebuilt-deps/prepare-ffmpeg-win32.sh +prepare-deps-win64: + @prebuilt-deps/prepare-adb.sh + @prebuilt-deps/prepare-sdl.sh + @prebuilt-deps/prepare-ffmpeg-win64.sh + build-win32: prepare-deps-win32 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ meson "$(WIN32_BUILD_DIR)" \ @@ -76,11 +81,6 @@ build-win32: prepare-deps-win32 -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" -prepare-deps-win64: - @prebuilt-deps/prepare-adb.sh - @prebuilt-deps/prepare-sdl.sh - @prebuilt-deps/prepare-ffmpeg-win64.sh - build-win64: prepare-deps-win64 [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ meson "$(WIN64_BUILD_DIR)" \ From 8d583d36e259ba7f5f21d7a703cca73184200aa9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 9 Feb 2022 23:10:38 +0100 Subject: [PATCH 0432/1133] Move prebuilt-deps/ to app/ The prebuilt dependencies are specific to the client app (not the server). This also avoids to reference the parent directory (../) from app/meson.build. --- app/meson.build | 10 ++-- .../prebuilt-deps}/.gitignore | 0 {prebuilt-deps => app/prebuilt-deps}/common | 0 .../prebuilt-deps}/prepare-adb.sh | 0 .../prebuilt-deps}/prepare-ffmpeg-win32.sh | 0 .../prebuilt-deps}/prepare-ffmpeg-win64.sh | 0 .../prebuilt-deps}/prepare-sdl.sh | 0 release.mk | 48 +++++++++---------- 8 files changed, 29 insertions(+), 29 deletions(-) rename {prebuilt-deps => app/prebuilt-deps}/.gitignore (100%) rename {prebuilt-deps => app/prebuilt-deps}/common (100%) rename {prebuilt-deps => app/prebuilt-deps}/prepare-adb.sh (100%) rename {prebuilt-deps => app/prebuilt-deps}/prepare-ffmpeg-win32.sh (100%) rename {prebuilt-deps => app/prebuilt-deps}/prepare-ffmpeg-win64.sh (100%) rename {prebuilt-deps => app/prebuilt-deps}/prepare-sdl.sh (100%) diff --git a/app/meson.build b/app/meson.build index 6557c3b8..123bcf62 100644 --- a/app/meson.build +++ b/app/meson.build @@ -111,9 +111,9 @@ if not crossbuild_windows else # cross-compile mingw32 build (from Linux to Windows) prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') - sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/bin' - sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/lib' - sdl2_include_dir = '../prebuilt-deps/data/' + prebuilt_sdl2 + '/include' + sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin' + sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib' + sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include' sdl2 = declare_dependency( dependencies: [ @@ -124,8 +124,8 @@ else ) prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') - ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' - ffmpeg_include_dir = '../prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' + ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' + ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' # ffmpeg versions are different for win32 and win64 builds ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec') diff --git a/prebuilt-deps/.gitignore b/app/prebuilt-deps/.gitignore similarity index 100% rename from prebuilt-deps/.gitignore rename to app/prebuilt-deps/.gitignore diff --git a/prebuilt-deps/common b/app/prebuilt-deps/common similarity index 100% rename from prebuilt-deps/common rename to app/prebuilt-deps/common diff --git a/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh similarity index 100% rename from prebuilt-deps/prepare-adb.sh rename to app/prebuilt-deps/prepare-adb.sh diff --git a/prebuilt-deps/prepare-ffmpeg-win32.sh b/app/prebuilt-deps/prepare-ffmpeg-win32.sh similarity index 100% rename from prebuilt-deps/prepare-ffmpeg-win32.sh rename to app/prebuilt-deps/prepare-ffmpeg-win32.sh diff --git a/prebuilt-deps/prepare-ffmpeg-win64.sh b/app/prebuilt-deps/prepare-ffmpeg-win64.sh similarity index 100% rename from prebuilt-deps/prepare-ffmpeg-win64.sh rename to app/prebuilt-deps/prepare-ffmpeg-win64.sh diff --git a/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh similarity index 100% rename from prebuilt-deps/prepare-sdl.sh rename to app/prebuilt-deps/prepare-sdl.sh diff --git a/release.mk b/release.mk index 755e94d4..aff9bd89 100644 --- a/release.mk +++ b/release.mk @@ -63,14 +63,14 @@ build-server: ninja -C "$(SERVER_BUILD_DIR)" prepare-deps-win32: - @prebuilt-deps/prepare-adb.sh - @prebuilt-deps/prepare-sdl.sh - @prebuilt-deps/prepare-ffmpeg-win32.sh + @app/prebuilt-deps/prepare-adb.sh + @app/prebuilt-deps/prepare-sdl.sh + @app/prebuilt-deps/prepare-ffmpeg-win32.sh prepare-deps-win64: - @prebuilt-deps/prepare-adb.sh - @prebuilt-deps/prepare-sdl.sh - @prebuilt-deps/prepare-ffmpeg-win64.sh + @app/prebuilt-deps/prepare-adb.sh + @app/prebuilt-deps/prepare-sdl.sh + @app/prebuilt-deps/prepare-ffmpeg-win64.sh build-win32: prepare-deps-win32 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ @@ -98,15 +98,15 @@ dist-win32: build-server build-win32 cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -116,15 +116,15 @@ dist-win64: build-server build-win64 cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From 43ae418752b30738a7bd3fd5beb55b9476697022 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 08:46:14 +0100 Subject: [PATCH 0433/1133] Fix USB device leak on connection error If sc_usb_connect() failed, then the sc_usb_device was never destroyed. The assignment was mistakenly removed by commit 61969aeb80093d0777c7716a61698cbdaf9ddd71. --- app/src/usb/scrcpy_otg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 9a7e3fe6..d3a45679 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -89,6 +89,8 @@ scrcpy_otg(struct scrcpy_options *options) { goto end; } + usb_device_initialized = true; + LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", usb_device.serial, usb_device.vid, usb_device.pid, usb_device.manufacturer, usb_device.product); From 7848a387c8281cb156f0e0da6bbbb05cda31db22 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 9 Feb 2022 21:06:16 +0100 Subject: [PATCH 0434/1133] Do not duplicate relative mouse mode state The relative mouse mode is tracked by SDL, and accessible via SDL_GetRelativeMouseMode(). This is more robust in case SDL changes the relative mouse mode on its own. --- app/src/screen.c | 27 ++++++++++++++++----------- app/src/screen.h | 1 - app/src/usb/screen_otg.c | 32 ++++++++++++++++++-------------- app/src/usb/screen_otg.h | 1 - 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 98626909..9e9ff3ff 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -163,14 +163,21 @@ sc_screen_is_relative_mode(struct sc_screen *screen) { } static void -sc_screen_capture_mouse(struct sc_screen *screen, bool capture) { +sc_screen_set_mouse_capture(bool capture) { if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); - return; } +} + +static inline bool +sc_screen_get_mouse_capture(void) { + return SDL_GetRelativeMouseMode(); +} - screen->mouse_captured = capture; +static inline void +sc_screen_toggle_mouse_capture(void) { + sc_screen_set_mouse_capture(!sc_screen_get_mouse_capture()); } static void @@ -372,7 +379,6 @@ sc_screen_init(struct sc_screen *screen, screen->fullscreen = false; screen->maximized = false; screen->event_failed = false; - screen->mouse_captured = false; screen->mouse_capture_key_pressed = 0; screen->req.x = params->window_x; @@ -710,7 +716,7 @@ sc_screen_update_frame(struct sc_screen *screen) { if (sc_screen_is_relative_mode(screen)) { // Capture mouse on start - sc_screen_capture_mouse(screen, true); + sc_screen_set_mouse_capture(true); } } @@ -823,7 +829,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { break; case SDL_WINDOWEVENT_FOCUS_LOST: if (relative_mode) { - sc_screen_capture_mouse(screen, false); + sc_screen_set_mouse_capture(false); } break; } @@ -853,8 +859,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (key == cap) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode - sc_screen_capture_mouse(screen, - !screen->mouse_captured); + sc_screen_toggle_mouse_capture(); } // Mouse capture keys are never forwarded to the device return; @@ -864,7 +869,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { case SDL_MOUSEWHEEL: case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: - if (relative_mode && !screen->mouse_captured) { + if (relative_mode && !sc_screen_get_mouse_capture()) { // Do not forward to input manager, the mouse will be captured // on SDL_MOUSEBUTTONUP return; @@ -880,8 +885,8 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } break; case SDL_MOUSEBUTTONUP: - if (relative_mode && !screen->mouse_captured) { - sc_screen_capture_mouse(screen, true); + if (relative_mode && !sc_screen_get_mouse_capture()) { + sc_screen_set_mouse_capture(true); return; } break; diff --git a/app/src/screen.h b/app/src/screen.h index 13bd4d99..0ddefc43 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -60,7 +60,6 @@ struct sc_screen { bool event_failed; // in case SDL_PushEvent() returned an error - bool mouse_captured; // only relevant in relative mouse mode // To enable/disable mouse capture, a mouse capture key (LALT, LGUI or // RGUI) must be pressed. This variable tracks the pressed capture key. SDL_Keycode mouse_capture_key_pressed; diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index cda0da5e..f32bc946 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -5,15 +5,21 @@ #include "util/log.h" static void -sc_screen_otg_capture_mouse(struct sc_screen_otg *screen, bool capture) { - assert(screen->mouse); +sc_screen_otg_set_mouse_capture(bool capture) { if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); - return; } +} + +static inline bool +sc_screen_otg_get_mouse_capture(void) { + return SDL_GetRelativeMouseMode(); +} - screen->mouse_captured = capture; +static inline void +sc_screen_otg_toggle_mouse_capture(void) { + sc_screen_otg_set_mouse_capture(!sc_screen_otg_get_mouse_capture()); } static void @@ -31,7 +37,6 @@ sc_screen_otg_init(struct sc_screen_otg *screen, screen->keyboard = params->keyboard; screen->mouse = params->mouse; - screen->mouse_captured = false; screen->mouse_capture_key_pressed = 0; const char *title = params->window_title; @@ -81,7 +86,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen, if (screen->mouse) { // Capture mouse on start - sc_screen_otg_capture_mouse(screen, true); + sc_screen_otg_set_mouse_capture(true); } return true; @@ -193,7 +198,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { break; case SDL_WINDOWEVENT_FOCUS_LOST: if (screen->mouse) { - sc_screen_otg_capture_mouse(screen, false); + sc_screen_otg_set_mouse_capture(false); } break; } @@ -227,8 +232,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { if (key == cap) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode - sc_screen_otg_capture_mouse(screen, - !screen->mouse_captured); + sc_screen_otg_toggle_mouse_capture(); } // Mouse capture keys are never forwarded to the device return; @@ -240,26 +244,26 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { } break; case SDL_MOUSEMOTION: - if (screen->mouse && screen->mouse_captured) { + if (screen->mouse && sc_screen_otg_get_mouse_capture()) { sc_screen_otg_process_mouse_motion(screen, &event->motion); } break; case SDL_MOUSEBUTTONDOWN: - if (screen->mouse && screen->mouse_captured) { + if (screen->mouse && sc_screen_otg_get_mouse_capture()) { sc_screen_otg_process_mouse_button(screen, &event->button); } break; case SDL_MOUSEBUTTONUP: if (screen->mouse) { - if (screen->mouse_captured) { + if (sc_screen_otg_get_mouse_capture()) { sc_screen_otg_process_mouse_button(screen, &event->button); } else { - sc_screen_otg_capture_mouse(screen, true); + sc_screen_otg_set_mouse_capture(true); } } break; case SDL_MOUSEWHEEL: - if (screen->mouse && screen->mouse_captured) { + if (screen->mouse && sc_screen_otg_get_mouse_capture()) { sc_screen_otg_process_mouse_wheel(screen, &event->wheel); } break; diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index 3fa1c4ad..0973ce59 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -18,7 +18,6 @@ struct sc_screen_otg { SDL_Texture *texture; // See equivalent mechanism in screen.h - bool mouse_captured; SDL_Keycode mouse_capture_key_pressed; }; From 4a95c08d56063db10a0456dcdafb1f683cc6be52 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 07:37:33 +0100 Subject: [PATCH 0435/1133] Improve error message for unsupported usb hotplug --- app/src/usb/usb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index c7963726..07fb9619 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -254,7 +254,8 @@ run_libusb_event_handler(void *data) { static bool sc_usb_register_callback(struct sc_usb *usb) { if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { - LOGW("libusb does not have hotplug capability"); + LOGW("On this platform, libusb does not have hotplug capability; " + "device disconnection will not be detected properly"); return false; } From 29c163959cfe9b25a4f431e7a504905da5309cb7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 09:06:29 +0100 Subject: [PATCH 0436/1133] Indent ifdef for clarity Make it explicit that the ifdef is an inner block. --- app/src/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 6e58feff..5251c47c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1690,12 +1690,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("OTG mode: could not select display"); return false; } -#ifdef HAVE_V4L2 +# ifdef HAVE_V4L2 if (opts->v4l2_device) { LOGE("OTG mode: could not sink to V4L2 device"); return false; } -#endif +# endif } #endif From e3c2398aa208d9f85e6cd5474e3201b8b5f203f9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Feb 2022 19:31:01 +0100 Subject: [PATCH 0437/1133] Store packet flags in PTS most significant bits A special PTS value was used to encode a config packet. To prepare for adding more flags, use the most significant bits of the PTS field to store flags. --- app/src/demuxer.c | 22 +++++++++++++++---- .../com/genymobile/scrcpy/ScreenEncoder.java | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index bebdb6d6..c2f4a636 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -13,7 +13,10 @@ #define BUFSIZE 0x10000 #define HEADER_SIZE 12 -#define NO_PTS UINT64_C(-1) + +#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63) + +#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_CONFIG - 1) static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { @@ -28,6 +31,14 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // size // // It is followed by bytes containing the packet/frame. + // + // The most significant bits of the PTS are used for packet flags: + // + // byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0 + // C....... ........ ........ ........ ........ ........ ........ ........ + // ^<--------------------------------------------------------------------> + // | PTS + // `- config packet uint8_t header[HEADER_SIZE]; ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE); @@ -35,9 +46,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { return false; } - uint64_t pts = buffer_read64be(header); + uint64_t pts_flags = buffer_read64be(header); uint32_t len = buffer_read32be(&header[8]); - assert(pts == NO_PTS || (pts & 0x8000000000000000) == 0); assert(len); if (av_new_packet(packet, len)) { @@ -51,7 +61,11 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { return false; } - packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE; + if (pts_flags & SC_PACKET_FLAG_CONFIG) { + packet->pts = AV_NOPTS_VALUE; + } else { + packet->pts = pts_flags & SC_PACKET_PTS_MASK; + } return true; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 79efc17c..a51a8a32 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -28,7 +28,7 @@ public class ScreenEncoder implements Device.RotationListener { // Keep the values in descending order private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; - private static final int NO_PTS = -1; + private static final long PACKET_FLAG_CONFIG = 1L << 63; private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); @@ -183,7 +183,7 @@ public class ScreenEncoder implements Device.RotationListener { long pts; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - pts = NO_PTS; // non-media data packet + pts = PACKET_FLAG_CONFIG; // non-media data packet } else { if (ptsOrigin == 0) { ptsOrigin = bufferInfo.presentationTimeUs; From 67068e4e3d7ab17de425651848eecea530ac8b88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Feb 2022 19:33:35 +0100 Subject: [PATCH 0438/1133] Pass key frame flag from the device MediaCodec indicates when a packet is a key frame. Transmit it to the client. --- app/src/demuxer.c | 16 +++++++++++----- .../com/genymobile/scrcpy/ScreenEncoder.java | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c2f4a636..417abd27 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -15,8 +15,9 @@ #define HEADER_SIZE 12 #define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63) +#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62) -#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_CONFIG - 1) +#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1) static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { @@ -35,10 +36,11 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // The most significant bits of the PTS are used for packet flags: // // byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0 - // C....... ........ ........ ........ ........ ........ ........ ........ - // ^<--------------------------------------------------------------------> - // | PTS - // `- config packet + // CK...... ........ ........ ........ ........ ........ ........ ........ + // ^^<-------------------------------------------------------------------> + // || PTS + // | `- config packet + // `-- key frame uint8_t header[HEADER_SIZE]; ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE); @@ -67,6 +69,10 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { packet->pts = pts_flags & SC_PACKET_PTS_MASK; } + if (pts_flags & SC_PACKET_FLAG_KEY_FRAME) { + packet->flags |= AV_PKT_FLAG_KEY; + } + return true; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index a51a8a32..f97206ec 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -29,6 +29,7 @@ public class ScreenEncoder implements Device.RotationListener { private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final long PACKET_FLAG_CONFIG = 1L << 63; + private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); @@ -189,6 +190,9 @@ public class ScreenEncoder implements Device.RotationListener { ptsOrigin = bufferInfo.presentationTimeUs; } pts = bufferInfo.presentationTimeUs - ptsOrigin; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { + pts |= PACKET_FLAG_KEY_FRAME; + } } headerBuffer.putLong(pts); From 1c02b58412db2646ba6e57a7e305d26482b9bc0d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Feb 2022 19:35:47 +0100 Subject: [PATCH 0439/1133] Remove sc_demuxer_parse() Now that the key frame flag is known, parsing the packet is useless. --- app/src/demuxer.c | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 417abd27..96c0192a 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -73,6 +73,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { packet->flags |= AV_PKT_FLAG_KEY; } + packet->dts = packet->pts; return true; } @@ -89,28 +90,6 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { return true; } -static void -sc_demuxer_parse(struct sc_demuxer *demuxer, AVPacket *packet) { - uint8_t *in_data = packet->data; - int in_len = packet->size; - uint8_t *out_data = NULL; - int out_len = 0; - int r = av_parser_parse2(demuxer->parser, demuxer->codec_ctx, - &out_data, &out_len, in_data, in_len, - AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); - - // PARSER_FLAG_COMPLETE_FRAMES is set - assert(r == in_len); - (void) r; - assert(out_len == in_len); - - if (demuxer->parser->key_frame == 1) { - packet->flags |= AV_PKT_FLAG_KEY; - } - - packet->dts = packet->pts; -} - static bool sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; @@ -150,11 +129,6 @@ sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { } } - if (!is_config) { - // data packet - sc_demuxer_parse(demuxer, packet); - } - bool ok = push_packet_to_sinks(demuxer, packet); if (!is_config && demuxer->pending) { From 6fc388f3691ce888331eb04a60014df51df31923 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Feb 2022 21:34:12 +0100 Subject: [PATCH 0440/1133] Remove unused BUFSIZE --- app/src/demuxer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 96c0192a..30f76a99 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -10,8 +10,6 @@ #include "util/buffer_util.h" #include "util/log.h" -#define BUFSIZE 0x10000 - #define HEADER_SIZE 12 #define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63) From 044acc2259f2898730ef1ccd4d780e69e3705614 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Feb 2022 21:34:58 +0100 Subject: [PATCH 0441/1133] Rename HEADER_SIZE to SC_PACKET_HEADER_SIZE Prefix the constant for consistency. --- app/src/demuxer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 30f76a99..2d0449c3 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -10,7 +10,7 @@ #include "util/buffer_util.h" #include "util/log.h" -#define HEADER_SIZE 12 +#define SC_PACKET_HEADER_SIZE 12 #define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63) #define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62) @@ -40,9 +40,9 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // | `- config packet // `-- key frame - uint8_t header[HEADER_SIZE]; - ssize_t r = net_recv_all(demuxer->socket, header, HEADER_SIZE); - if (r < HEADER_SIZE) { + uint8_t header[SC_PACKET_HEADER_SIZE]; + ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE); + if (r < SC_PACKET_HEADER_SIZE) { return false; } From 5c62f3419d252d10cd8c9cbb7c918b358b81f2d0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Feb 2022 09:12:46 +0100 Subject: [PATCH 0442/1133] Rename buffer util functions with sc_ prefix --- app/src/control_msg.c | 30 +++++++++++++++--------------- app/src/demuxer.c | 4 ++-- app/src/device_msg.c | 4 ++-- app/src/util/buffer_util.h | 24 ++++++++++++------------ app/tests/test_buffer_util.c | 12 ++++++------ 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index e8ae4681..a57d8cc2 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -63,17 +63,17 @@ static const char *const copy_key_labels[] = { static void write_position(uint8_t *buf, const struct sc_position *position) { - buffer_write32be(&buf[0], position->point.x); - buffer_write32be(&buf[4], position->point.y); - buffer_write16be(&buf[8], position->screen_size.width); - buffer_write16be(&buf[10], position->screen_size.height); + sc_write32be(&buf[0], position->point.x); + sc_write32be(&buf[4], position->point.y); + sc_write16be(&buf[8], position->screen_size.width); + sc_write16be(&buf[10], position->screen_size.height); } // write length (4 bytes) + string (non null-terminated) static size_t write_string(const char *utf8, size_t max_len, unsigned char *buf) { size_t len = sc_str_utf8_truncation_index(utf8, max_len); - buffer_write32be(buf, len); + sc_write32be(buf, len); memcpy(&buf[4], utf8, len); return 4 + len; } @@ -94,9 +94,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { switch (msg->type) { case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: buf[1] = msg->inject_keycode.action; - buffer_write32be(&buf[2], msg->inject_keycode.keycode); - buffer_write32be(&buf[6], msg->inject_keycode.repeat); - buffer_write32be(&buf[10], msg->inject_keycode.metastate); + sc_write32be(&buf[2], msg->inject_keycode.keycode); + sc_write32be(&buf[6], msg->inject_keycode.repeat); + sc_write32be(&buf[10], msg->inject_keycode.metastate); return 14; case SC_CONTROL_MSG_TYPE_INJECT_TEXT: { size_t len = @@ -106,20 +106,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { } case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: buf[1] = msg->inject_touch_event.action; - buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id); + sc_write64be(&buf[2], msg->inject_touch_event.pointer_id); write_position(&buf[10], &msg->inject_touch_event.position); uint16_t pressure = to_fixed_point_16(msg->inject_touch_event.pressure); - buffer_write16be(&buf[22], pressure); - buffer_write32be(&buf[24], msg->inject_touch_event.buttons); + sc_write16be(&buf[22], pressure); + sc_write32be(&buf[24], msg->inject_touch_event.buttons); return 28; case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: write_position(&buf[1], &msg->inject_scroll_event.position); - buffer_write32be(&buf[13], + sc_write32be(&buf[13], (uint32_t) msg->inject_scroll_event.hscroll); - buffer_write32be(&buf[17], + sc_write32be(&buf[17], (uint32_t) msg->inject_scroll_event.vscroll); - buffer_write32be(&buf[21], msg->inject_scroll_event.buttons); + sc_write32be(&buf[21], msg->inject_scroll_event.buttons); return 25; case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: buf[1] = msg->inject_keycode.action; @@ -128,7 +128,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { buf[1] = msg->get_clipboard.copy_key; return 2; case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: - buffer_write64be(&buf[1], msg->set_clipboard.sequence); + sc_write64be(&buf[1], msg->set_clipboard.sequence); buf[9] = !!msg->set_clipboard.paste; size_t len = write_string(msg->set_clipboard.text, SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 2d0449c3..35ea4c06 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -46,8 +46,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { return false; } - uint64_t pts_flags = buffer_read64be(header); - uint32_t len = buffer_read32be(&header[8]); + uint64_t pts_flags = sc_read64be(header); + uint32_t len = sc_read32be(&header[8]); assert(len); if (av_new_packet(packet, len)) { diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 9a93036b..4b4a4974 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -18,7 +18,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len, msg->type = buf[0]; switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { - size_t clipboard_len = buffer_read32be(&buf[1]); + size_t clipboard_len = sc_read32be(&buf[1]); if (clipboard_len > len - 5) { return 0; // not available } @@ -36,7 +36,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len, return 5 + clipboard_len; } case DEVICE_MSG_TYPE_ACK_CLIPBOARD: { - uint64_t sequence = buffer_read64be(&buf[1]); + uint64_t sequence = sc_read64be(&buf[1]); msg->ack_clipboard.sequence = sequence; return 9; } diff --git a/app/src/util/buffer_util.h b/app/src/util/buffer_util.h index 337bb262..a5456abf 100644 --- a/app/src/util/buffer_util.h +++ b/app/src/util/buffer_util.h @@ -1,5 +1,5 @@ -#ifndef BUFFER_UTIL_H -#define BUFFER_UTIL_H +#ifndef SC_BUFFER_UTIL_H +#define SC_BUFFER_UTIL_H #include "common.h" @@ -7,13 +7,13 @@ #include static inline void -buffer_write16be(uint8_t *buf, uint16_t value) { +sc_write16be(uint8_t *buf, uint16_t value) { buf[0] = value >> 8; buf[1] = value; } static inline void -buffer_write32be(uint8_t *buf, uint32_t value) { +sc_write32be(uint8_t *buf, uint32_t value) { buf[0] = value >> 24; buf[1] = value >> 16; buf[2] = value >> 8; @@ -21,25 +21,25 @@ buffer_write32be(uint8_t *buf, uint32_t value) { } static inline void -buffer_write64be(uint8_t *buf, uint64_t value) { - buffer_write32be(buf, value >> 32); - buffer_write32be(&buf[4], (uint32_t) value); +sc_write64be(uint8_t *buf, uint64_t value) { + sc_write32be(buf, value >> 32); + sc_write32be(&buf[4], (uint32_t) value); } static inline uint16_t -buffer_read16be(const uint8_t *buf) { +sc_read16be(const uint8_t *buf) { return (buf[0] << 8) | buf[1]; } static inline uint32_t -buffer_read32be(const uint8_t *buf) { +sc_read32be(const uint8_t *buf) { return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } static inline uint64_t -buffer_read64be(const uint8_t *buf) { - uint32_t msb = buffer_read32be(buf); - uint32_t lsb = buffer_read32be(&buf[4]); +sc_read64be(const uint8_t *buf) { + uint32_t msb = sc_read32be(buf); + uint32_t lsb = sc_read32be(&buf[4]); return ((uint64_t) msb << 32) | lsb; } diff --git a/app/tests/test_buffer_util.c b/app/tests/test_buffer_util.c index c7c13bdd..a9fec896 100644 --- a/app/tests/test_buffer_util.c +++ b/app/tests/test_buffer_util.c @@ -8,7 +8,7 @@ static void test_buffer_write16be(void) { uint16_t val = 0xABCD; uint8_t buf[2]; - buffer_write16be(buf, val); + sc_write16be(buf, val); assert(buf[0] == 0xAB); assert(buf[1] == 0xCD); @@ -18,7 +18,7 @@ static void test_buffer_write32be(void) { uint32_t val = 0xABCD1234; uint8_t buf[4]; - buffer_write32be(buf, val); + sc_write32be(buf, val); assert(buf[0] == 0xAB); assert(buf[1] == 0xCD); @@ -30,7 +30,7 @@ static void test_buffer_write64be(void) { uint64_t val = 0xABCD1234567890EF; uint8_t buf[8]; - buffer_write64be(buf, val); + sc_write64be(buf, val); assert(buf[0] == 0xAB); assert(buf[1] == 0xCD); @@ -45,7 +45,7 @@ static void test_buffer_write64be(void) { static void test_buffer_read16be(void) { uint8_t buf[2] = {0xAB, 0xCD}; - uint16_t val = buffer_read16be(buf); + uint16_t val = sc_read16be(buf); assert(val == 0xABCD); } @@ -53,7 +53,7 @@ static void test_buffer_read16be(void) { static void test_buffer_read32be(void) { uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34}; - uint32_t val = buffer_read32be(buf); + uint32_t val = sc_read32be(buf); assert(val == 0xABCD1234); } @@ -62,7 +62,7 @@ static void test_buffer_read64be(void) { uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78, 0x90, 0xEF}; - uint64_t val = buffer_read64be(buf); + uint64_t val = sc_read64be(buf); assert(val == 0xABCD1234567890EF); } From d0ab8c0e7beb33c3a2f31c735a56a58277038be1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Feb 2022 12:44:02 +0100 Subject: [PATCH 0443/1133] Fix double adb tunnel closing On error, close the adb tunnel only if it has not already been closed beforehand. --- app/src/server.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 0de51c7e..a4090f00 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -482,8 +482,10 @@ fail: } } - // Always leave this function with tunnel disabled - sc_adb_tunnel_close(tunnel, &server->intr, serial); + if (tunnel->enabled) { + // Always leave this function with tunnel disabled + sc_adb_tunnel_close(tunnel, &server->intr, serial); + } return false; } From cc27771dd18529fafc450529615c72582104991f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Feb 2022 12:33:40 +0100 Subject: [PATCH 0444/1133] Add compilation flag for V4L2 feature This allows to disable V4L2 support on Linux to build without libavdevice. --- app/meson.build | 2 +- app/src/cli.c | 3 ++- meson_options.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/meson.build b/app/meson.build index 123bcf62..ef7a467e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -69,7 +69,7 @@ else endif endif -v4l2_support = host_machine.system() == 'linux' +v4l2_support = get_option('v4l2') and host_machine.system() == 'linux' if v4l2_support src += [ 'src/v4l2_sink.c' ] endif diff --git a/app/src/cli.c b/app/src/cli.c index 5251c47c..19e21324 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1549,7 +1549,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->v4l2_device = optarg; break; #else - LOGE("V4L2 (--v4l2-sink) is only available on Linux."); + LOGE("V4L2 (--v4l2-sink) is disabled (or unsupported on this " + "platform)."); return false; #endif case OPT_V4L2_BUFFER: diff --git a/meson_options.txt b/meson_options.txt index d64e357f..13227d8a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,3 +4,4 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') +option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported') From ca9e1a05148d46793de0118a8cbcef62877df534 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 12 Feb 2022 12:38:40 +0100 Subject: [PATCH 0445/1133] Add compilation flag for USB features This allows to disable HID/OTG features on Linux to build without libusb. --- app/meson.build | 2 +- app/src/cli.c | 12 ++++++------ meson_options.txt | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/meson.build b/app/meson.build index ef7a467e..9ee38d5f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -74,7 +74,7 @@ if v4l2_support src += [ 'src/v4l2_sink.c' ] endif -usb_support = host_machine.system() == 'linux' +usb_support = get_option('usb') and host_machine.system() == 'linux' if usb_support src += [ 'src/usb/aoa_hid.c', diff --git a/app/src/cli.c b/app/src/cli.c index 19e21324..63f4a4ed 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1357,8 +1357,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; break; #else - LOGE("HID over AOA (-K/--hid-keyboard) is not supported on " - "this platform. It is only available on Linux."); + LOGE("HID over AOA (-K/--hid-keyboard) is disabled (or " + "unsupported on this platform)."); return false; #endif case OPT_MAX_FPS: @@ -1376,8 +1376,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; break; #else - LOGE("HID over AOA (-M/--hid-mouse) is not supported on this" - "platform. It is only available on Linux."); + LOGE("HID over AOA (-M/--hid-mouse) is disabled (or " + "unsupported on this platform)."); return false; #endif case OPT_LOCK_VIDEO_ORIENTATION: @@ -1540,8 +1540,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->otg = true; break; #else - LOGE("OTG mode (--otg) is not supported on this platform. It " - "is only available on Linux."); + LOGE("OTG mode (--otg) is disabled (or unsupported on this " + "platform)."); return false; #endif case OPT_V4L2_SINK: diff --git a/meson_options.txt b/meson_options.txt index 13227d8a..d1030694 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,3 +5,4 @@ option('portable', type: 'boolean', value: false, description: 'Use scrcpy-serve option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported') +option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported') From bb991f829cda06e024caa613ae91c34f4cc908b7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 13 Feb 2022 17:17:39 +0100 Subject: [PATCH 0446/1133] Fix order of options In alphabetic order, "no-clipboard-autosync" is before "no-downsize-on-error". --- app/src/cli.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 63f4a4ed..d2a991d9 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -250,13 +250,6 @@ static const struct sc_option options[] = { "is preserved.\n" "Default is 0 (unlimited).", }, - { - .longopt_id = OPT_NO_DOWNSIZE_ON_ERROR, - .longopt = "no-downsize-on-error", - .text = "By default, on MediaCodec error, scrcpy automatically tries " - "again with a lower definition.\n" - "This option disables this behavior.", - }, { .longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC, .longopt = "no-clipboard-autosync", @@ -266,6 +259,13 @@ static const struct sc_option options[] = { "it changes.\n" "This option disables this automatic synchronization." }, + { + .longopt_id = OPT_NO_DOWNSIZE_ON_ERROR, + .longopt = "no-downsize-on-error", + .text = "By default, on MediaCodec error, scrcpy automatically tries " + "again with a lower definition.\n" + "This option disables this behavior.", + }, { .shortopt = 'n', .longopt = "no-control", From ccbe370cc52c5cd4315aec0d153b4108e6237498 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 13 Feb 2022 17:17:01 +0100 Subject: [PATCH 0447/1133] Add --no-cleanup option It might be useful not to cleanup on exit, for example to leave the screen turned off, or keep the server binary on the device (via the server option "cleanup=false"). Fixes #1764 PR #3020 --- app/scrcpy.1 | 6 ++++++ app/src/cli.c | 12 ++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 4 ++++ app/src/server.h | 1 + .../main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../main/java/com/genymobile/scrcpy/Server.java | 16 +++++++++++----- 9 files changed, 46 insertions(+), 5 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 317cec05..13a9a97a 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -146,6 +146,12 @@ It may only work over USB, and is currently only supported on Linux. Also see \fB\-\-hid\-keyboard\fR. +.TP +.B \-\-no\-cleanup +By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit. + +This option disables this cleanup. + .TP .B \-\-no\-clipboard\-autosync By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes. diff --git a/app/src/cli.c b/app/src/cli.c index d2a991d9..eb28b1d7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -54,6 +54,7 @@ #define OPT_RAW_KEY_EVENTS 1034 #define OPT_NO_DOWNSIZE_ON_ERROR 1035 #define OPT_OTG 1036 +#define OPT_NO_CLEANUP 1037 struct sc_option { char shortopt; @@ -250,6 +251,14 @@ static const struct sc_option options[] = { "is preserved.\n" "Default is 0 (unlimited).", }, + { + .longopt_id = OPT_NO_CLEANUP, + .longopt = "no-cleanup", + .text = "By default, scrcpy removes the server binary from the device " + "and restores the device state (show touches, stay awake and " + "power mode) on exit.\n" + "This option disables this cleanup." + }, { .longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC, .longopt = "no-clipboard-autosync", @@ -1535,6 +1544,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_DOWNSIZE_ON_ERROR: opts->downsize_on_error = false; break; + case OPT_NO_CLEANUP: + opts->cleanup = false; + break; case OPT_OTG: #ifdef HAVE_USB opts->otg = true; diff --git a/app/src/options.c b/app/src/options.c index 377c5590..5a717df5 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -62,4 +62,5 @@ const struct scrcpy_options scrcpy_options_default = { .tcpip_dst = NULL, .select_tcpip = false, .select_usb = false, + .cleanup = true, }; diff --git a/app/src/options.h b/app/src/options.h index 44104734..f96edb22 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -137,6 +137,7 @@ struct scrcpy_options { const char *tcpip_dst; bool select_usb; bool select_tcpip; + bool cleanup; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c3a481d0..8c4920d6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -320,6 +320,7 @@ scrcpy(struct scrcpy_options *options) { .downsize_on_error = options->downsize_on_error, .tcpip = options->tcpip, .tcpip_dst = options->tcpip_dst, + .cleanup = options->cleanup, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index a4090f00..c12b03d4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -244,6 +244,10 @@ execute_server(struct sc_server *server, // By default, downsize_on_error is true ADD_PARAM("downsize_on_error=false"); } + if (!params->cleanup) { + // By default, cleanup is true + ADD_PARAM("cleanup=false"); + } #undef ADD_PARAM diff --git a/app/src/server.h b/app/src/server.h index 5818b43c..5f630ca8 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -46,6 +46,7 @@ struct sc_server_params { const char *tcpip_dst; bool select_usb; bool select_tcpip; + bool cleanup; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 92cf1e7a..4842b635 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -21,6 +21,7 @@ public class Options { private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; private boolean downsizeOnError = true; + private boolean cleanup = true; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -155,6 +156,14 @@ public class Options { this.downsizeOnError = downsizeOnError; } + public boolean getCleanup() { + return cleanup; + } + + public void setCleanup(boolean cleanup) { + this.cleanup = cleanup; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 8cf289cd..d7e522c7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -51,11 +51,13 @@ public final class Server { } } - try { - CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, - options.getPowerOffScreenOnClose()); - } catch (IOException e) { - Ln.e("Could not configure cleanup", e); + if (options.getCleanup()) { + try { + CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, + options.getPowerOffScreenOnClose()); + } catch (IOException e) { + Ln.e("Could not configure cleanup", e); + } } } @@ -243,6 +245,10 @@ public final class Server { boolean downsizeOnError = Boolean.parseBoolean(value); options.setDownsizeOnError(downsizeOnError); break; + case "cleanup": + boolean cleanup = Boolean.parseBoolean(value); + options.setCleanup(cleanup); + break; case "send_device_meta": boolean sendDeviceMeta = Boolean.parseBoolean(value); options.setSendDeviceMeta(sendDeviceMeta); From b58b566fa5c1d7b5c5386399b592d203d7dd9bbf Mon Sep 17 00:00:00 2001 From: Firq Date: Mon, 14 Feb 2022 23:58:12 +0100 Subject: [PATCH 0448/1133] Add German translation of README.md PR #3023 Signed-off-by: Romain Vimont --- README.de.md | 1016 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 1017 insertions(+) create mode 100644 README.de.md diff --git a/README.de.md b/README.de.md new file mode 100644 index 00000000..ca080cd3 --- /dev/null +++ b/README.de.md @@ -0,0 +1,1016 @@ +_Only the original [README](README.md) is guaranteed to be up-to-date._ + +# scrcpy (v1.22) + +scrcpy + +_ausgesprochen "**scr**een **c**o**py**"_ + +Diese Anwendung liefert sowohl Anzeige als auch Steuerung eines Android-Gerätes über USB (oder [über TCP/IP](#tcpip-kabellos)). Dabei wird kein _root_ Zugriff benötigt. +Die Anwendung funktioniert unter _GNU/Linux_, _Windows_ und _macOS_. + +![screenshot](assets/screenshot-debian-600.jpg) + +Dabei liegt der Fokus auf: + + - **Leichtigkeit**: native, nur Anzeige des Gerätedisplays + - **Leistung**: 30~120fps, abhängig vom Gerät + - **Qualität**: 1920×1080 oder mehr + - **Geringe Latenz**: [35~70ms][lowlatency] + - **Kurze Startzeit**: ~1 Sekunde um das erste Bild anzuzeigen + - **Keine Aufdringlichkeit**: Es wird keine installierte Software auf dem Gerät zurückgelassen + - **Nutzervorteile**: kein Account, keine Werbung, kein Internetzugriff notwendig + - **Freiheit**: gratis und open-source + +[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 + +Die Features beinhalten: + - [Aufnahme](#Aufnahme) + - Spiegeln mit [ausgeschaltetem Bildschirm](#bildschirm-ausschalten) + - [Copy&Paste](#copy-paste) in beide Richtungen + - [Einstellbare Qualität](#Aufnahmekonfiguration) + - Gerätebildschirm [als Webcam (V4L2)](#v4l2loopback) (nur Linux) + - [Simulation einer physischen Tastatur (HID)](#simulation-einer-physischen-tastatur-mit-hid) + (nur Linux) + - [Simulation einer physischen Maus (HID)](#simulation-einer-physischen-maus-mit-hid) + (nur Linux) + - [OTG Modus](#otg) (nur Linux) + - und mehr… + +## Voraussetzungen + +Das Android-Gerät benötigt mindestens API 21 (Android 5.0). + +Es muss sichergestellt sein, dass [adb debugging][enable-adb] auf dem Gerät aktiv ist. + +[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling + +Auf manchen Geräten müssen zudem [weitere Optionen][control] aktiv sein um das Gerät mit Maus und Tastatur steuern zu können. + +[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 + + +## Installation der App + +Packaging status + +### Zusammenfassung + + - Linux: `apt install scrcpy` + - Windows: [download][direct-win64] + - macOS: `brew install scrcpy` + +Direkt von der Source bauen: [BUILD] ([vereinfachter Prozess (englisch)][BUILD_simple]) + +[BUILD]: BUILD.md +[BUILD_simple]: BUILD.md#simple + + +### Linux + +Auf Debian und Ubuntu: + +``` +apt install scrcpy +``` + +Auf Arch Linux: + +``` +pacman -S scrcpy +``` + +Ein [Snap] package ist verfügbar: [`scrcpy`][snap-link]. + +[snap-link]: https://snapstats.org/snaps/scrcpy + +[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) + +Für Fedora ist ein [COPR] package verfügbar: [`scrcpy`][copr-link]. + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + + +Für Gentoo ist ein [Ebuild] verfügbar: [`scrcpy/`][ebuild-link]. + +[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +Die App kann zudem [manuell gebaut werden][BUILD] ([vereinfachter Prozess (englisch)][BUILD_simple]). + + +### Windows + +Für Windows ist der Einfachheit halber ein vorgebautes Archiv mit allen Abhängigkeiten (inklusive `adb`) vorhanden. + + - [README](README.md#windows) + +Es ist zudem in [Chocolatey] vorhanden: + +[Chocolatey]: https://chocolatey.org/ + +```bash +choco install scrcpy +choco install adb # falls noch nicht vorhanden +``` + +Und in [Scoop]: + +```bash +scoop install scrcpy +scoop install adb # falls noch nicht vorhanden +``` + +[Scoop]: https://scoop.sh + +Die App kann zudem [manuell gebaut werden][BUILD]. + + +### macOS + +Die Anwendung ist in [Homebrew] verfügbar. Installation: + +[Homebrew]: https://brew.sh/ + +```bash +brew install scrcpy +``` + +Es wird `adb` benötigt, auf welches über `PATH` zugegriffen werden kann. Falls noch nicht vorhanden: + +```bash +brew install android-platform-tools +``` + +Es ist außerdem in [MacPorts] vorhanden, welches adb bereits aufsetzt: + +```bash +sudo port install scrcpy +``` + +[MacPorts]: https://www.macports.org/ + + +Die Anwendung kann zudem [manuell gebaut werden][BUILD]. + + +## Ausführen + +Ein Android-Gerät anschließen und diese Befehle ausführen: + +```bash +scrcpy +``` + +Dabei werden Kommandozeilenargumente akzeptiert, aufgelistet per: + +```bash +scrcpy --help +``` + +## Funktionalitäten + +### Aufnahmekonfiguration + +#### Größe reduzieren + +Manchmal ist es sinnvoll, das Android-Gerät mit einer geringeren Auflösung zu spiegeln, um die Leistung zu erhöhen. + +Um die Höhe und Breite auf einen Wert zu limitieren (z.B. 1024): + +```bash +scrcpy --max-size 1024 +scrcpy -m 1024 # short version +``` + +Die andere Größe wird dabei so berechnet, dass das Seitenverhältnis des Gerätes erhalten bleibt. +In diesem Fall wird ein Gerät mit einer 1920×1080-Auflösung mit 1024×576 gespiegelt. + + +#### Ändern der Bit-Rate + +Die Standard-Bitrate ist 8 Mbps. Um die Bitrate zu ändern (z.B. zu 2 Mbps): + +```bash +scrcpy --bit-rate 2M +scrcpy -b 2M # Kurzversion +``` + +#### Limitieren der Bildwiederholrate + +Die Aufnahme-Bildwiederholrate kann begrenzt werden: + +```bash +scrcpy --max-fps 15 +``` + +Dies wird offiziell seit Android 10 unterstützt, kann jedoch bereits auf früheren Versionen funktionieren. + +#### Zuschneiden + +Der Geräte-Bildschirm kann zugeschnitten werden, sodass nur ein Teil gespiegelt wird. + +Dies ist beispielsweise nützlich, um nur ein Auge der Oculus Go zu spiegeln: + +```bash +scrcpy --crop 1224:1440:0:0 # 1224x1440 am Versatz (0,0) +``` + +Falls `--max-size` auch festgelegt ist, wird das Ändern der Größe nach dem Zuschneiden angewandt. + + +#### Feststellen der Videoorientierung + + +Um die Orientierung während dem Spiegeln festzustellen: + +```bash +scrcpy --lock-video-orientation # ursprüngliche (momentane) Orientierung +scrcpy --lock-video-orientation=0 # normale Orientierung +scrcpy --lock-video-orientation=1 # 90° gegen den Uhrzeigersinn +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 90° mit dem Uhrzeigersinn +``` + +Dies beeinflusst die Aufnahmeausrichtung. + +Das [Fenster kann auch unabhängig rotiert](#Rotation) werden. + + +#### Encoder + +Manche Geräte besitzen mehr als einen Encoder. Manche dieser Encoder können dabei sogar zu Problemen oder Abstürzen führen. +Die Auswahl eines anderen Encoders ist möglich: + +```bash +scrcpy --encoder OMX.qcom.video.encoder.avc +``` + +Um eine Liste aller verfügbaren Encoder zu erhalten (eine Fehlermeldung gibt alle verfügbaren Encoder aus): + +```bash +scrcpy --encoder _ +``` + +### Aufnahme + +#### Aufnehmen von Videos + +Es ist möglich, das Display während des Spiegelns aufzunehmen: + +```bash +scrcpy --record file.mp4 +scrcpy -r file.mkv +``` + +Um das Spiegeln während des Aufnehmens zu deaktivieren: + +```bash +scrcpy --no-display --record file.mp4 +scrcpy -Nr file.mkv +# Unterbrechen der Aufnahme mit Strg+C +``` + +"Übersprungene Bilder" werden aufgenommen, selbst wenn sie in Echtzeit (aufgrund von Performancegründen) nicht dargestellt werden. Die Einzelbilder sind mit _Zeitstempeln_ des Gerätes versehen are, sodass eine [Paketverzögerungsvariation] nicht die Aufnahmedatei beeinträchtigt. + +[Paketverzögerungs-Variation]: https://www.wikide.wiki/wiki/en/Packet_delay_variation + + +#### v4l2loopback + +Auf Linux ist es möglich, den Video-Stream zu einem v4l2 loopback Gerät zu senden, sodass das Android-Gerät von jedem v4l2-fähigen Tool wie eine Webcam verwendet werden kann. + +Das Modul `v4l2loopback` muss dazu installiert werden: + +```bash +sudo apt install v4l2loopback-dkms +``` + +Um ein v4l2 Gerät zu erzeugen: + +```bash +sudo modprobe v4l2loopback +``` + +Dies erzeugt ein neues Video-Gerät in `/dev/videoN`, wobei `N` ein Integer ist (mehr [Optionen](https://github.com/umlaeute/v4l2loopback#options) sind verfügbar um mehrere Geräte oder Geräte mit spezifischen Nummern zu erzeugen). + +Um die aktivierten Geräte aufzulisten: + +```bash +# benötigt das v4l-utils package +v4l2-ctl --list-devices + +# simpel, kann aber ausreichend +ls /dev/video* +``` + +Um scrcpy mithilfe eines v4l2 sink zu starten: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # Fenster mit Spiegelung ausschalten +scrcpy --v4l2-sink=/dev/videoN -N # kurze Version +``` + +(`N` muss mit der Geräte-ID ersetzt werden, welche mit `ls /dev/video*` überprüft werden kann) + +Einmal aktiv, kann der Stream mit einem v4l2-fähigen Tool verwendet werden: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC kann eine gewisse Bufferverzögerung herbeiführen +``` + +Beispielsweise kann das Video mithilfe von [OBS] aufgenommen werden. + +[OBS]: https://obsproject.com/ + + +#### Buffering + +Es ist möglich, Buffering hinzuzufügen. Dies erhöht die Latenz, reduziert aber etwaigen Jitter (see [#2464]). + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +Diese Option ist sowohl für Video-Buffering: + +```bash +scrcpy --display-buffer=50 # fügt 50ms Buffering zum Display hinzu +``` + +als auch V4L2 sink verfügbar: + +```bash +scrcpy --v4l2-buffer=500 # fügt 500ms Buffering für v4l2 sink hinzu +``` + + +### Verbindung + +#### TCP/IP Kabellos + +_Scrcpy_ verwendet `adb`, um mit dem Gerät zu kommunizieren. `adb` kann sich per TCP/IP mit einem Gerät [verbinden]. Das Gerät muss dabei mit demselben Netzwerk wie der Computer verbunden sein. + +##### Automatisch + +Die Option `--tcpip` erlaubt es, die Verbindung automatisch zu konfigurieren. Dabei gibt es zwei Varianten. + +Falls das Gerät (verfügbar unter 192.168.1.1 in diesem Beispiel) bereit an einem Port (typically 5555) nach einkommenden adb-Verbindungen hört, dann führe diesen Befehl aus: + +```bash +scrcpy --tcpip=192.168.1.1 # Standard-Port ist 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +Falls adb TCP/IP auf dem Gerät deaktiviert ist (oder falls die IP-Adresse des Gerätes nicht bekannt ist): Gerät per USB verbinden, anschließend diesen Befehl ausführen: + +```bash +scrcpy --tcpip # ohne weitere Argumente +``` + +Dies finden automatisch das Gerät und aktiviert den TCP/IP-Modus. Anschließend verbindet sich der Befehl mit dem Gerät bevor die Verbindung startet. + +##### Manuell + +Alternativ kann die TCP/IP-Verbindung auch manuell per `adb` aktiviert werden: + +1. Gerät mit demselben Wifi wie den Computer verbinden. +2. IP-Adresse des Gerätes herausfinden, entweder über Einstellungen → Über das Telefon → Status, oder indem dieser Befehl ausgeführt wird: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +3. Aktivieren von adb über TCP/IP auf dem Gerät: `adb tcpip 5555`. +4. Ausstecken des Geräts. +5. Verbinden zum Gerät: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` ersetzen)_. +6. `scrcpy` wie normal ausführen. + +Es kann sinnvoll sein, die Bit-Rate sowie dei Auflösung zu reduzieren: + +```bash +scrcpy --bit-rate 2M --max-size 800 +scrcpy -b2M -m800 # kurze Version +``` + +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless + + +#### Mehrere Geräte + +Falls mehrere Geräte unter `adb devices` aufgelistet werden, muss die _Seriennummer_ angegeben werden: + +```bash +scrcpy --serial 0123456789abcdef +scrcpy -s 0123456789abcdef # kurze Version +``` + +Falls das Gerät über TCP/IP verbunden ist: + +```bash +scrcpy --serial 192.168.0.1:5555 +scrcpy -s 192.168.0.1:5555 # kurze Version +``` + +Es können mehrere Instanzen von _scrcpy_ für mehrere Geräte gestartet werden. + +#### Autostart beim Verbinden eines Gerätes + +Hierfür kann [AutoAdb] verwendet werden: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + +#### Tunnel + +Um sich zu einem entfernten Gerät zu verbinden, kann der `adb` Client mit einem remote-`adb`-Server verbunden werden (Voraussetzung: Gleiche Version des `adb`-Protokolls). + +##### Remote ADB Server + +Um sich zu einem Remote-`adb`-Server zu verbinden: Der Server muss auf allen Ports hören + +```bash +adb kill-server +adb -a nodaemon server start +# Diesen Dialog offen halten +``` + +**Warnung: Die gesamte Kommunikation zwischen adb und den Geräten ist unverschlüsselt.** + +Angenommen, der Server ist unter 192.168.1.2 verfügbar. Dann kann von einer anderen Kommandozeile scrcpy aufgeführt werden: + +```bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +Standardmäßig verwendet scrcpy den lokalen Port für die Einrichtung des `adb forward`-Tunnels (typischerweise `27183`, siehe `--port`). +Es ist zudem möglich, einen anderen Tunnel-Port zuzuweisen (sinnvoll in Situationen, bei welchen viele Weiterleitungen erfolgen): + +``` +scrcpy --tunnel-port=1234 +``` + + +##### SSH Tunnel + +Um mit einem Remote-`adb`-Server sicher zu kommunizieren, wird ein SSH-Tunnel empfohlen. + +Sicherstellen, dass der Remote-`adb`-Server läuft: + +```bash +adb start-server +``` + +Erzeugung eines SSH-Tunnels: + +```bash +# local 5038 --> remote 5037 +# local 27183 <-- remote 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer +# Diesen Dialog geöffnet halten +``` + +Von einer anderen Kommandozeile aus scrcpy ausführen: + +```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy +``` + +Um das Aktivieren von Remote-Weiterleitung zu verhindern, kann eine Vorwärts-Verbindung verwendet werden (`-L` anstatt von `-R`): + +```bash +# local 5038 --> remote 5037 +# local 27183 --> remote 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer +# Diesen Dialog geöffnet halten +``` + +Von einer anderen Kommandozeile aus scrcpy ausführen: + +```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy --force-adb-forward +``` + + +Wie für kabellose Verbindungen kann es sinnvoll sein, die Qualität zu reduzieren: + +``` +scrcpy -b2M -m800 --max-fps 15 +``` + +### Fensterkonfiguration + +#### Titel + +Standardmäßig ist der Fenstertitel das Gerätemodell. Der Titel kann jedoch geändert werden: + +```bash +scrcpy --window-title 'Mein Gerät' +``` + +#### Position und Größe + +Die anfängliche Fensterposition und Größe können festgelegt werden: + +```bash +scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +``` + +#### Rahmenlos + +Um den Rahmen des Fensters zu deaktivieren: + +```bash +scrcpy --window-borderless +``` + +#### Immer im Vordergrund + +Um das Fenster immer im Vordergrund zu halten: + +```bash +scrcpy --always-on-top +``` + +#### Vollbild + +Die Anwendung kann direkt im Vollbildmodus gestartet werden: + +```bash +scrcpy --fullscreen +scrcpy -f # kurze Version +``` + +Das Vollbild kann dynamisch mit MOD+f gewechselt werden. + +#### Rotation + +Das Fenster kann rotiert werden: + +```bash +scrcpy --rotation 1 +``` + +Mögliche Werte sind: + - `0`: keine Rotation + - `1`: 90 grad gegen den Uhrzeigersinn + - `2`: 180 grad + - `3`: 90 grad mit dem Uhrzeigersinn + +die Rotation kann zudem dynamisch mit MOD+ +_(links)_ and MOD+ _(rechts)_ angepasst werden. + +_scrcpy_ schafft 3 verschiedene Rotationen: + - MOD+r erfordert von Gerät den Wechsel zwischen Hochformat und Querformat (die momentane App kann dies verweigern, wenn die geforderte Ausrichtung nicht unterstützt wird). + - [`--lock-video-orientation`](#feststellen-der-videoorientierung) ändert die Ausrichtung der Spiegelung (die Ausrichtung des an den Computer gesendeten Videos). Dies beeinflusst eventuelle Aufnahmen. + - `--rotation` (or MOD+/MOD+) rotiert nur das Fenster, eventuelle Aufnahmen sind hiervon nicht beeinflusst. + + +### Andere Spiegel-Optionen + +#### Lesezugriff + +Um die Steuerung (alles, was mit dem Gerät interagieren kann: Tasten, Mausklicks, Drag-and-drop von Dateien) zu deaktivieren: + +```bash +scrcpy --no-control +scrcpy -n +``` + +#### Anzeige + +Falls mehrere Displays vorhanden sind, kann das zu spiegelnde Display gewählt werden: + +```bash +scrcpy --display 1 +``` + +Die Liste an verfügbaren Displays kann mit diesem Befehl ausgegeben werden: + +```bash +adb shell dumpsys display # Nach "mDisplayId=" in der Ausgabe suchen +``` + +Das zweite Display kann nur gesteuert werden, wenn das Gerät Android 10 oder höher besitzt. Ansonsten wird das Display nur mit Lesezugriff gespiegelt. + + +#### Wach bleiben + +Um zu verhindern, dass das Gerät nach einer Weile in den Ruhezustand übergeht (solange es eingesteckt ist): + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +Der ursprüngliche Zustand wird beim Schließen von scrcpy wiederhergestellt. + + +#### Bildschirm ausschalten + +Es ist möglich, beim Starten des Spiegelns mithilfe eines Kommandozeilenarguments den Bildschirm des Gerätes auszuschalten: + +```bash +scrcpy --turn-screen-off +scrcpy -S +``` + +Oder durch das Drücken von MOD+o jederzeit. + +Um das Display wieder einzuschalten muss MOD+Shift+o gedrückt werden. + +Auf Android aktiviert der `POWER` Knopf das Display immer. +Für den Komfort wird, wenn `POWER` via scrcpy gesendet wird (über Rechtsklick oder MOD+p), wird versucht, das Display nach einer kurzen Zeit wieder auszuschalten (falls es möglich ist). +Der physische `POWER` Button aktiviert das Display jedoch immer. + +Dies kann zudem nützlich sein, um das Gerät vom Ruhezustand abzuhalten: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw +``` + +#### Ausschalten beim Schließen + +Um den Gerätebildschirm abzuschalten, wenn scrcpy geschlossen wird: + +```bash +scrcpy --power-off-on-close +``` + + +#### Anzeigen von Berührungen + +Für Präsentationen kann es sinnvoll sein, die physischen Berührungen anzuzeigen (auf dem physischen Gerät). + +Android stellt dieses Feature in den _Entwickleroptionen_ zur Verfügung. + +_Scrcpy_ stellt die Option zur Verfügung, dies beim Start zu aktivieren und beim Schließen auf den Ursprungszustand zurückzusetzen: + +```bash +scrcpy --show-touches +scrcpy -t +``` + +Anmerkung: Nur _physische Berührungen_ werden angezeigt (mit dem Finger auf dem Gerät). + + +#### Bildschirmschoner deaktivieren + +Standardmäßig unterbindet scrcpy nicht den Bildschirmschoner des Computers. + +Um den Bildschirmschoner zu unterbinden: + +```bash +scrcpy --disable-screensaver +``` + + +### Eingabesteuerung + +#### Geräte-Bildschirm drehen + +MOD+r drücken, um zwischen Hoch- und Querformat zu wechseln. + +Anmerkung: Dis funktioniert nur, wenn die momentan geöffnete App beide Rotationen unterstützt. + +#### Copy-paste + +Immer, wenn sich die Zwischenablage von Android ändert wird dies mit dem Computer synchronisiert. + +Jedes Strg wird an das Gerät weitergegeben. Insbesonders: + - Strg+c kopiert typischerweise + - Strg+x schneidet typischerweise aus + - Strg+v fügt typischerweise ein (nach der Computer-zu-Gerät-Synchronisation) + +Dies funktioniert typischerweise wie erwartet. + +Die wirkliche Funktionsweise hängt jedoch von der jeweiligen Anwendung ab. Beispielhaft sendet _Termux_ SIGINT bei Strg+c, und _K-9 Mail_ erzeugt eine neue Nachricht. + +Um kopieren, ausschneiden und einfügen in diesen Fällen zu verwenden (nur bei Android >= 7 unterstützt): + - MOD+c gibt `COPY` ein + - MOD+x gibt `CUT` ein + - MOD+v gibt `PASTE` ein (nach der Computer-zu-Gerät-Synchronisation) + +Zusätzlich erlaubt es MOD+Shift+v den momentanen Inhalt der Zwischenablage als eine Serie von Tastenevents einzugeben. +Dies ist nützlich, fall die Applikation kein Einfügen unterstützt (z.B. _Termux_). Jedoch kann nicht-ASCII-Inhalt dabei zerstört werden. + +**WARNUNG:** Das Einfügen der Computer-Zwischenablage in das Gerät (entweder mit Strg+v oder MOD+v) kopiert den Inhalt in die Zwischenablage des Gerätes. +Als Konsequenz kann somit jede Android-Applikation diesen Inhalt lesen. Das Einfügen von sensiblen Informationen wie Passwörtern sollte aus diesem Grund vermieden werden. + +Mache Geräte verhalten sich nicht wie erwartet, wenn die Zwischenablage per Programm verändert wird. +Die Option `--legacy-paste` wird bereitgestellt, welche das Verhalten von Strg+v und MOD+v so ändert, dass die Zwischenablage wie bei MOD+Shift+v als eine Serie von Tastenevents ausgeführt wird. + +Um die automatische Synchronisierung der Zwischenablage zu deaktivieren: +`--no-clipboard-autosync`. + +#### Ziehen zum Zoomen + +Um "Ziehen-zum-Zoomen" zu simulieren: Strg+_klicken-und-bewegen_. + +Genauer: Strg halten, während Linksklick gehalten wird. Solange Linksklick gehalten wird, skalieren und rotieren die Mausbewegungen den Inhalt (soweit von der jeweiligen App unterstützt). + +Konkret erzeugt scrcpy einen am Mittelpunkt des Displays gespiegelten, "virtuellen" Finger. + +#### Simulation einer physischen Tastatur mit HID + +Standardmäßig verwendet scrcpy Android-Tasten oder Textinjektion. Dies funktioniert zwar immer, jedoch nur mit ASCII. + +Auf Linux kann scrcpy mithilfe von HID eine physische Tastatur simulieren, um eine bessere Eingabeerfahrung zu gewährleisten (dies nutzt [USB HID over AOAv2][hid-aoav2]): Die virtuelle Tastatur wird deaktiviert, es funktioniert für alle Zeichen und mit IME. + +[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support + +Dies funktioniert jedoch nur, wenn das Gerät über USB verbunden ist. Zudem wird dies momentan nur unter Linux unterstützt. + +Um diesen Modus zu aktivieren: + +```bash +scrcpy --hid-keyboard +scrcpy -K # kurze Version +``` + +Falls dies auf gewissen Gründen fehlschlägt (z.B. Gerät ist nicht über USB verbunden), so fällt scrcpy auf den Standardmodus zurück (mit einer Ausgabe in der Konsole). +Dies erlaubt es, dieselben Kommandozeilenargumente zu verwenden, egal ob das Gerät per USB oder TCP/IP verbunden ist. + +In diesem Modus werden rohe Tastenevents (scancodes) an das Gerät gesendet. +Aus diesem Grund muss ein nicht passenden Tastaturformat in den Einstellungen des Android-Gerätes unter Einstellungen → System → Sprache und Eingabe → [Physical keyboard] konfiguriert werden. + +Diese Einstellungsseite kann direkt mit diesem Befehl geöffnet werden: + +```bash +adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS +``` + +Diese Option ist jedoch nur verfügbar, wenn eine HID-Tastatur oder eine physische Tastatur verbunden sind. + +[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 + +#### Simulation einer physischen Maus mit HID + +Ähnlich zu einer Tastatur kann auch eine Maus mithilfe von HID simuliert werden. +Wie zuvor funktioniert dies jedoch nur, wenn das Gerät über USB verbunden ist. Zudem wird dies momentan nur unter Linux unterstützt. + +Standardmäßig verwendet scrcpy Android Maus Injektionen mit absoluten Koordinaten. +Durch die Simulation einer physischen Maus erscheint auf dem Display des Geräts ein Mauszeiger, zu welchem die Bewegungen, Klicks und Scrollbewegungen relativ eingegeben werden. + +Um diesen Modus zu aktivieren: + +```bash +scrcpy --hid-mouse +scrcpy -M # kurze Version +``` + +Es kann zudem`--forward-all-clicks` übergeben werden, um [alle Mausklicks an das Gerät weiterzugeben](#rechtsklick-und-mittelklick). + +Wenn dieser Modus aktiv ist, ist der Mauszeiger des Computers auf dem Fenster gefangen (Zeiger verschwindet von Computer und erscheint auf dem Android-Gerät). + +Spezielle Tasteneingaben wie Alt oder Super ändern den Zustand des Mauszeigers (geben diesen wieder frei/fangen ihn wieder ein). +Eine dieser Tasten kann verwendet werden, um die Kontrolle der Maus wieder zurück an den Computer zu geben. + + +#### OTG + +Es ist möglich, _scrcpy_ so auszuführen, dass nur Maus und Tastatur, wie wenn diese direkt über ein OTG-Kabel verbunden wären, simuliert werden. + +In diesem Modus ist _adb_ nicht nötig, ebenso ist das Spiegeln der Anzeige deaktiviert. + +Um den OTG-Modus zu aktivieren: + +```bash +scrcpy --otg +# Seriennummer übergeben, falls mehrere Geräte vorhanden sind +scrcpy --otg -s 0123456789abcdef +``` + +Es ist möglich, nur HID-Tastatur oder HID-Maus zu aktivieren: + +```bash +scrcpy --otg --hid-keyboard # nur Tastatur +scrcpy --otg --hid-mouse # nur Maus +scrcpy --otg --hid-keyboard --hid-mouse # Tastatur und Maus +# Der Einfachheit halber sind standardmäßig beide aktiv +scrcpy --otg # Tastatur und Maus +``` + +Wie `--hid-keyboard` und `--hid-mouse` funktioniert dies nur, wenn das Gerät per USB verbunden ist. +Zudem wird dies momentan nur unter Linux unterstützt. + + +#### Textinjektions-Vorliebe + +Beim Tippen von Text werden zwei verschiedene [Events][textevents] generiert: + - _key events_, welche signalisieren, ob eine Taste gedrückt oder losgelassen wurde; + - _text events_, welche signalisieren, dass Text eingegeben wurde. + +Standardmäßig werden key events verwendet, da sich bei diesen die Tastatur in Spielen wie erwartet verhält (typischerweise für WASD). + +Dies kann jedoch [Probleme verursachen][prefertext]. Trifft man auf ein solches Problem, so kann dies mit diesem Befehl umgangen werden: + +```bash +scrcpy --prefer-text +``` + +Dies kann jedoch das Tastaturverhalten in Spielen beeinträchtigen/zerstören. + +Auf der anderen Seite kann jedoch auch die Nutzung von key events erzwungen werden: + +```bash +scrcpy --raw-key-events +``` + +Diese Optionen haben jedoch keinen Einfluss auf eine etwaige HID-Tastatur, da in diesem modus alle key events als scancodes gesendet werden. + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +#### Wiederholen von Tasten + +Standardmäßig löst das gedrückt halten einer Taste das jeweilige Event mehrfach aus. Dies kann jedoch zu Performanceproblemen in manchen Spielen führen. + +Um das Weitergeben von sich wiederholenden Tasteneingaben zu verhindern: + +```bash +scrcpy --no-key-repeat +``` + +This option has no effect on HID keyboard (key repeat is handled by Android +directly in this mode). + + +#### Rechtsklick und Mittelklick + +Standardmäßig löst Rechtsklick BACK (wenn Bildschirm aus: POWER) und Mittelklick BACK aus. Um diese Kürzel abzuschalten und stattdessen die Eingaben direkt an das Gerät weiterzugeben: + +```bash +scrcpy --forward-all-clicks +``` + + +### Dateien ablegen + +#### APK installieren + +Um eine AKP zu installieren, kann diese per Drag-and-drop auf das _scrcpy_-Fenster gezogen werden. + +Dabei erfolgt kein visuelles Feedback, ein Log wird in die Konsole ausgegeben. + + +#### Datei auf Gerät schieben + +Um eine Datei nach `/sdcard/Download/` auf dem Gerät zu schieben, Drag-and-drop die (nicht-APK)-Datei auf das _scrcpy_-Fenster. + +Dabei erfolgt kein visuelles Feedback, ein Log wird in die Konsole ausgegeben. + +Das Zielverzeichnis kann beim Start geändert werden: + +```bash +scrcpy --push-target=/sdcard/Movies/ +``` + + +### Audioweitergabe + +Audio wird von _scrcpy_ nicht übertragen. Hierfür kann [sndcpy] verwendet werden. + +Siehe zudem [issue #14]. + +[sndcpy]: https://github.com/rom1v/sndcpy +[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 + + +## Tastenkürzel + +In der folgenden Liste ist MOD der Kürzel-Auslöser. Standardmäßig ist dies (links) Alt oder (links) Super. + +Dies kann mithilfe von `--shortcut-mod` geändert werden. Mögliche Tasten sind `lstrg`, `rstrg`, +`lalt`, `ralt`, `lsuper` und `rsuper`. Beispielhaft: + +```bash +# Nutze rStrg als Auslöser +scrcpy --shortcut-mod=rctrl + +# Nutze entweder LStrg+LAlt oder LSuper für Tastenkürzel +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] ist typischerweise die Windows oder Cmd Taste._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + +| Aktion | Tastenkürzel | | +|--------------------------------------------------------|-----------------------------------------------------------|:-------------------------| +| Vollbild wechseln | MOD+f | | +| Display nach links rotieren | MOD+ _(links)_ | | +| Display nach links rotieren | MOD+ _(rechts)_ | | +| Fenstergröße 1:1 replizieren (pixel-perfect) | MOD+g | | +| Fenstergröße zum entfernen der schwarzen Balken ändern | MOD+w | _Doppel-Linksklick¹_ | +| Klick auf `HOME` | MOD+h | _Mittelklick_ | +| Klick auf `BACK` | MOD+b | _Rechtsklick²_ | +| Klick auf `APP_SWITCH` | MOD+s | _4.-Taste-Klick³_ | +| Klick auf `MENU` (Bildschirm entsperren)⁴ | MOD+m | | +| Klick auf `VOLUME_UP` | MOD+ _(hoch)_ | | +| CKlick auf `VOLUME_DOWN` | MOD+ _(runter)_ | | +| Klick auf `POWER` | MOD+p | | +| Power an | _Rechtsklick²_ | | +| Gerätebildschirm ausschalten (weiterhin spiegeln) | MOD+o | | +| Gerätebildschirm einschalten | MOD+Shift+o | | +| Gerätebildschirm drehen | MOD+r | | +| Benachrichtigungs-Bereich anzeigen | MOD+n | _5.-Taste-Klick³_ | +| Erweitertes Einstellungs-Menü anzeigen | MOD+n+n | _Doppel-5.-Taste-Klick³_ | +| Bedienfelder einklappen | MOD+Shift+n | | +| In die Zwischenablage kopieren⁵ | MOD+c | | +| In die Zwischenablage kopieren⁵ | MOD+x | | +| Zwischenablage synchronisieren und einfügen⁵ | MOD+v | | +| Computer-Zwischenablage einfügen (per Tastenevents) | MOD+Shift+v | | +| FPS-Zähler aktivieren/deaktivieren (ing stdout) | MOD+i | | +| Ziehen zum Zoomen | Strg+_Klicken-und-Bewegen_ | | +| Drag-and-drop mit APK-Datei | APK von Computer installieren | | +| Drag-and-drop mit Nicht-APK Datei | [Datei auf das Gerät schieben](#datei-auf-gerät-schieben) | | + + +_¹Doppelklick auf die schwarzen Balken, um diese zu entfernen._ +_²Rechtsklick aktiviert den Bildschirm, falls dieser aus war, ansonsten ZURÜCK._ +_³4. und 5. Maustasten, wenn diese an der jeweiligen Maus vorhanden sind._ +_⁴Für react-native Applikationen in Entwicklung, `MENU` öffnet das Entwickler-Menü._ +_⁵Nur für Android >= 7._ + +Abkürzungen mit mehreren Tastenanschlägen werden durch das Loslassen und erneute Drücken der Taste erreicht. +Beispielhaft, um "Erweitere das Einstellungs-Menü" auszuführen: + + 1. Drücke und halte MOD. + 2. Doppelklicke n. + 3. Lasse MOD los. + +Alle Strg+_Taste_ Tastenkürzel werden an das Gerät übergeben, sodass sie von der jeweiligen Applikation ausgeführt werden können. + + +## Personalisierte Pfade + +Um eine spezifische _adb_ Binary zu verwenden, muss deren Pfad als Umgebungsvariable `ADB` deklariert werden: + +```bash +ADB=/path/to/adb scrcpy +``` + +Um den Pfad der `scrcpy-server` Datei zu bearbeiten, muss deren Pfad in `SCRCPY_SERVER_PATH` bearbeitet werden. + +Um das Icon von scrcpy zu ändern, muss `SCRCPY_ICON_PATH` geändert werden. + + +## Warum _scrcpy_? + +Ein Kollege hat mich dazu herausgefordert, einen Namen so unaussprechbar wie [gnirehtet] zu finden. + +[`strcpy`] kopiert einen **str**ing; `scrcpy` kopiert einen **scr**een. + +[gnirehtet]: https://github.com/Genymobile/gnirehtet +[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html + + +## Selbst bauen? + +Siehe [BUILD]. + + +## Typische Fehler + +Siehe [FAQ](FAQ.md). + + +## Entwickler + +[Entwicklerseite](DEVELOP.md). + + +## Licence + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2022 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## Artikel (auf Englisch) + +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] + +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.md b/README.md index e8ddf907..3d935e73 100644 --- a/README.md +++ b/README.md @@ -1110,6 +1110,7 @@ Read the [developers page]. This README is available in other languages: +- [Deutsch (German, `de`) - v1.22](README.de.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Italiano (Italiano, `it`) - v1.19](README.it.md) - [日本語 (Japanese, `jp`) - v1.19](README.jp.md) From 2a872c3865ad3446cbf64ed72dbfa846110d675d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 16 Feb 2022 18:11:40 +0100 Subject: [PATCH 0449/1133] Fix fps_counter tick type The type uint32_t is not sufficient to store the result of sc_tick_now(). As a consequence, the FPS counter entered a live loop and caused a lock starvation (deadlock in practice). Refs ec871dd3f596a8183e37982821645ac5a5791fe0 Refs 682a6911735cb8f6dccd9653ce30b72f267235c6 --- app/src/fps_counter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 25ee00eb..158e60ed 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -57,7 +57,7 @@ display_fps(struct fps_counter *counter) { // must be called with mutex locked static void -check_interval_expired(struct fps_counter *counter, uint32_t now) { +check_interval_expired(struct fps_counter *counter, sc_tick now) { if (now < counter->next_timestamp) { return; } From 85edba20e79cdbde5295696ecc4950f584819899 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 16 Feb 2022 18:29:30 +0100 Subject: [PATCH 0450/1133] Enforce deadline reached on timeout The value of sc_tick_now() has microsecond precision, but sc_cond_timedwait() has only millisecond precision. To guarantee that sc_tick_now() >= deadline when sc_cond_timedwait() returns due to timeout, round up to the next millisecond. This avoids to call a non-blocking sc_cond_timedwait() in a loop for no reason until a target deadline during up to 1 millisecond. Refs 682a6911735cb8f6dccd9653ce30b72f267235c6 --- app/src/util/thread.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/util/thread.c b/app/src/util/thread.c index 0d098b3f..478867b6 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -136,7 +136,9 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { return false; // timeout } - uint32_t ms = SC_TICK_TO_MS(deadline - now); + // Round up to the next millisecond to guarantee that the deadline is + // reached when returning due to timeout + uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); #ifndef NDEBUG if (r < 0) { @@ -148,6 +150,8 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { memory_order_relaxed); #endif assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); + // The deadline is reached on timeout + assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline); return r == 0; } From 7a138c6929026b8dee103027ab91182533c6d8d0 Mon Sep 17 00:00:00 2001 From: Firq <75133068+Firq-ow@users.noreply.github.com> Date: Wed, 16 Feb 2022 08:42:46 +0100 Subject: [PATCH 0451/1133] Fix links in German README There were three links that weren't displayed correctly due to incorrect references: - the Windows release link to `README.md#windows` - 2 links that expected a German reference but got an English reference PR #3026 Signed-off-by: Romain Vimont --- README.de.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.de.md b/README.de.md index ca080cd3..92d00df2 100644 --- a/README.de.md +++ b/README.de.md @@ -57,7 +57,7 @@ Auf manchen Geräten müssen zudem [weitere Optionen][control] aktiv sein um das ### Zusammenfassung - Linux: `apt install scrcpy` - - Windows: [download][direct-win64] + - Windows: [download (siehe README)](README.md#windows) - macOS: `brew install scrcpy` Direkt von der Source bauen: [BUILD] ([vereinfachter Prozess (englisch)][BUILD_simple]) @@ -274,7 +274,7 @@ scrcpy -Nr file.mkv "Übersprungene Bilder" werden aufgenommen, selbst wenn sie in Echtzeit (aufgrund von Performancegründen) nicht dargestellt werden. Die Einzelbilder sind mit _Zeitstempeln_ des Gerätes versehen are, sodass eine [Paketverzögerungsvariation] nicht die Aufnahmedatei beeinträchtigt. -[Paketverzögerungs-Variation]: https://www.wikide.wiki/wiki/en/Packet_delay_variation +[Paketverzögerungsvariation]: https://www.wikide.wiki/wiki/en/Packet_delay_variation #### v4l2loopback @@ -394,7 +394,7 @@ scrcpy --bit-rate 2M --max-size 800 scrcpy -b2M -m800 # kurze Version ``` -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless +[verbinden]: https://developer.android.com/studio/command-line/adb.html#wireless #### Mehrere Geräte From 03705b828b0a03ce982ac890bd595d4c43b71f7b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 19:55:14 +0100 Subject: [PATCH 0452/1133] Use sc_prefix for fps counter --- app/src/fps_counter.c | 38 +++++++++++++++++++------------------- app/src/fps_counter.h | 22 +++++++++++----------- app/src/input_manager.c | 8 ++++---- app/src/screen.c | 14 +++++++------- app/src/screen.h | 2 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 158e60ed..41ffeaaf 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -4,10 +4,10 @@ #include "util/log.h" -#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1) +#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1) bool -fps_counter_init(struct fps_counter *counter) { +sc_fps_counter_init(struct sc_fps_counter *counter) { bool ok = sc_mutex_init(&counter->mutex); if (!ok) { return false; @@ -27,26 +27,26 @@ fps_counter_init(struct fps_counter *counter) { } void -fps_counter_destroy(struct fps_counter *counter) { +sc_fps_counter_destroy(struct sc_fps_counter *counter) { sc_cond_destroy(&counter->state_cond); sc_mutex_destroy(&counter->mutex); } static inline bool -is_started(struct fps_counter *counter) { +is_started(struct sc_fps_counter *counter) { return atomic_load_explicit(&counter->started, memory_order_acquire); } static inline void -set_started(struct fps_counter *counter, bool started) { +set_started(struct sc_fps_counter *counter, bool started) { atomic_store_explicit(&counter->started, started, memory_order_release); } // must be called with mutex locked static void -display_fps(struct fps_counter *counter) { +display_fps(struct sc_fps_counter *counter) { unsigned rendered_per_second = - counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL; + counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL; if (counter->nr_skipped) { LOGI("%u fps (+%u frames skipped)", rendered_per_second, counter->nr_skipped); @@ -57,7 +57,7 @@ display_fps(struct fps_counter *counter) { // must be called with mutex locked static void -check_interval_expired(struct fps_counter *counter, sc_tick now) { +check_interval_expired(struct sc_fps_counter *counter, sc_tick now) { if (now < counter->next_timestamp) { return; } @@ -67,13 +67,13 @@ check_interval_expired(struct fps_counter *counter, sc_tick now) { counter->nr_skipped = 0; // add a multiple of the interval uint32_t elapsed_slices = - (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1; - counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices; + (now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1; + counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices; } static int run_fps_counter(void *data) { - struct fps_counter *counter = data; + struct sc_fps_counter *counter = data; sc_mutex_lock(&counter->mutex); while (!counter->interrupted) { @@ -94,9 +94,9 @@ run_fps_counter(void *data) { } bool -fps_counter_start(struct fps_counter *counter) { +sc_fps_counter_start(struct sc_fps_counter *counter) { sc_mutex_lock(&counter->mutex); - counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL; + counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL; counter->nr_rendered = 0; counter->nr_skipped = 0; sc_mutex_unlock(&counter->mutex); @@ -121,18 +121,18 @@ fps_counter_start(struct fps_counter *counter) { } void -fps_counter_stop(struct fps_counter *counter) { +sc_fps_counter_stop(struct sc_fps_counter *counter) { set_started(counter, false); sc_cond_signal(&counter->state_cond); } bool -fps_counter_is_started(struct fps_counter *counter) { +sc_fps_counter_is_started(struct sc_fps_counter *counter) { return is_started(counter); } void -fps_counter_interrupt(struct fps_counter *counter) { +sc_fps_counter_interrupt(struct sc_fps_counter *counter) { if (!counter->thread_started) { return; } @@ -145,7 +145,7 @@ fps_counter_interrupt(struct fps_counter *counter) { } void -fps_counter_join(struct fps_counter *counter) { +sc_fps_counter_join(struct sc_fps_counter *counter) { if (counter->thread_started) { // interrupted must be set by the thread calling join(), so no need to // lock for the assertion @@ -156,7 +156,7 @@ fps_counter_join(struct fps_counter *counter) { } void -fps_counter_add_rendered_frame(struct fps_counter *counter) { +sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) { if (!is_started(counter)) { return; } @@ -169,7 +169,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) { } void -fps_counter_add_skipped_frame(struct fps_counter *counter) { +sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) { if (!is_started(counter)) { return; } diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index 9609c814..e21c49c4 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -9,7 +9,7 @@ #include "util/thread.h" -struct fps_counter { +struct sc_fps_counter { sc_thread thread; sc_mutex mutex; sc_cond state_cond; @@ -28,32 +28,32 @@ struct fps_counter { }; bool -fps_counter_init(struct fps_counter *counter); +sc_fps_counter_init(struct sc_fps_counter *counter); void -fps_counter_destroy(struct fps_counter *counter); +sc_fps_counter_destroy(struct sc_fps_counter *counter); bool -fps_counter_start(struct fps_counter *counter); +sc_fps_counter_start(struct sc_fps_counter *counter); void -fps_counter_stop(struct fps_counter *counter); +sc_fps_counter_stop(struct sc_fps_counter *counter); bool -fps_counter_is_started(struct fps_counter *counter); +sc_fps_counter_is_started(struct sc_fps_counter *counter); // request to stop the thread (on quit) -// must be called before fps_counter_join() +// must be called before sc_fps_counter_join() void -fps_counter_interrupt(struct fps_counter *counter); +sc_fps_counter_interrupt(struct sc_fps_counter *counter); void -fps_counter_join(struct fps_counter *counter); +sc_fps_counter_join(struct sc_fps_counter *counter); void -fps_counter_add_rendered_frame(struct fps_counter *counter); +sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter); void -fps_counter_add_skipped_frame(struct fps_counter *counter); +sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter); #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 63842d35..89ac450d 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -242,14 +242,14 @@ set_screen_power_mode(struct sc_controller *controller, } static void -switch_fps_counter_state(struct fps_counter *fps_counter) { +switch_fps_counter_state(struct sc_fps_counter *fps_counter) { // the started state can only be written from the current thread, so there // is no ToCToU issue - if (fps_counter_is_started(fps_counter)) { - fps_counter_stop(fps_counter); + if (sc_fps_counter_is_started(fps_counter)) { + sc_fps_counter_stop(fps_counter); LOGI("FPS counter stopped"); } else { - if (fps_counter_start(fps_counter)) { + if (sc_fps_counter_start(fps_counter)) { LOGI("FPS counter started"); } else { LOGE("FPS counter starting failed"); diff --git a/app/src/screen.c b/app/src/screen.c index 9e9ff3ff..2b1c5299 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -347,7 +347,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, bool need_new_event; if (previous_skipped) { - fps_counter_add_skipped_frame(&screen->fps_counter); + sc_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; @@ -402,7 +402,7 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_video_buffer; } - if (!fps_counter_init(&screen->fps_counter)) { + if (!sc_fps_counter_init(&screen->fps_counter)) { goto error_stop_and_join_video_buffer; } @@ -534,7 +534,7 @@ error_destroy_renderer: error_destroy_window: SDL_DestroyWindow(screen->window); error_destroy_fps_counter: - fps_counter_destroy(&screen->fps_counter); + sc_fps_counter_destroy(&screen->fps_counter); error_stop_and_join_video_buffer: sc_video_buffer_stop(&screen->vb); sc_video_buffer_join(&screen->vb); @@ -573,13 +573,13 @@ sc_screen_hide_window(struct sc_screen *screen) { void sc_screen_interrupt(struct sc_screen *screen) { sc_video_buffer_stop(&screen->vb); - fps_counter_interrupt(&screen->fps_counter); + sc_fps_counter_interrupt(&screen->fps_counter); } void sc_screen_join(struct sc_screen *screen) { sc_video_buffer_join(&screen->vb); - fps_counter_join(&screen->fps_counter); + sc_fps_counter_join(&screen->fps_counter); } void @@ -591,7 +591,7 @@ sc_screen_destroy(struct sc_screen *screen) { SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); - fps_counter_destroy(&screen->fps_counter); + sc_fps_counter_destroy(&screen->fps_counter); sc_video_buffer_destroy(&screen->vb); } @@ -701,7 +701,7 @@ sc_screen_update_frame(struct sc_screen *screen) { sc_video_buffer_consume(&screen->vb, screen->frame); AVFrame *frame = screen->frame; - fps_counter_add_rendered_frame(&screen->fps_counter); + sc_fps_counter_add_rendered_frame(&screen->fps_counter); struct sc_size new_frame_size = {frame->width, frame->height}; if (!prepare_for_frame(screen, new_frame_size)) { diff --git a/app/src/screen.h b/app/src/screen.h index 0ddefc43..12b8816c 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -26,7 +26,7 @@ struct sc_screen { struct sc_input_manager im; struct sc_video_buffer vb; - struct fps_counter fps_counter; + struct sc_fps_counter fps_counter; // The initial requested window properties struct { From 0e22032710728baf3c78d20a53e89cba202138ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 21:59:35 +0100 Subject: [PATCH 0453/1133] Update FAQ about Windows scaling behavior Recommend to update to v1.22 before suggesting manual configuration. Fixes #3028 PR #3032 --- FAQ.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/FAQ.md b/FAQ.md index 5829136d..49b91e89 100644 --- a/FAQ.md +++ b/FAQ.md @@ -150,22 +150,24 @@ screen, then you might get poor quality, especially visible on text (see [#40]). [#40]: https://github.com/Genymobile/scrcpy/issues/40 -To improve downscaling quality, trilinear filtering is enabled automatically -if the renderer is OpenGL and if it supports mipmapping. +This problem should be fixed in scrcpy v1.22: **update to the latest version**. -On Windows, you might want to force OpenGL: - -``` -scrcpy --render-driver=opengl -``` - -You may also need to configure the [scaling behavior]: +On older versions, you must configure the [scaling behavior]: > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > > Override high DPI scaling behavior > Scaling performed by: _Application_. [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 +Also, to improve downscaling quality, trilinear filtering is enabled +automatically if the renderer is OpenGL and if it supports mipmapping. + +On Windows, you might want to force OpenGL to enable mipmapping: + +``` +scrcpy --render-driver=opengl +``` + ### Issue with Wayland From fa93c8a91b6ea6ce82e61b05c0c3b96da9c589c7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 20:10:09 +0100 Subject: [PATCH 0454/1133] Move FPS counter start/stop logs This will allow to print the same logs for every start/stop call. PR #3030 --- app/src/fps_counter.c | 2 ++ app/src/input_manager.c | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 41ffeaaf..85312821 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -117,6 +117,7 @@ sc_fps_counter_start(struct sc_fps_counter *counter) { counter->thread_started = true; } + LOGI("FPS counter started"); return true; } @@ -124,6 +125,7 @@ void sc_fps_counter_stop(struct sc_fps_counter *counter) { set_started(counter, false); sc_cond_signal(&counter->state_cond); + LOGI("FPS counter stopped"); } bool diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 89ac450d..bba3665c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -247,13 +247,9 @@ switch_fps_counter_state(struct sc_fps_counter *fps_counter) { // is no ToCToU issue if (sc_fps_counter_is_started(fps_counter)) { sc_fps_counter_stop(fps_counter); - LOGI("FPS counter stopped"); } else { - if (sc_fps_counter_start(fps_counter)) { - LOGI("FPS counter started"); - } else { - LOGE("FPS counter starting failed"); - } + sc_fps_counter_start(fps_counter); + // Any error is already logged } } From 4b018be78907eec4d85e24e64402e1fbb5860aea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 20:08:41 +0100 Subject: [PATCH 0455/1133] Add --print-fps to enable FPS counter on start The FPS counter could be enabled/disabled via MOD+i. Add a command line option to enable it on start. This is consistent with other features like --turn-screen-off or --fullscreen. Fixes #468 PR #3030 --- README.md | 9 +++++++++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 10 ++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/screen.c | 5 +++++ app/src/screen.h | 2 ++ 8 files changed, 33 insertions(+) diff --git a/README.md b/README.md index dbbc4d71..1139092f 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,15 @@ scrcpy --max-fps 15 This is officially supported since Android 10, but may work on earlier versions. +The actual capture framerate may be printed to the console: + +``` +scrcpy --print-fps +``` + +It may also be enabled or disabled at any time with MOD+i. + + #### Crop The device screen may be cropped to mirror only part of the screen. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 13a9a97a..62cfe004 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -211,6 +211,10 @@ Inject alpha characters and space as text events instead of key events. This avoids issues when combining multiple keys to enter special characters, but breaks the expected behavior of alpha keys in games (typically WASD). +.TP +.B "\-\-print\-fps +Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i. + .TP .BI "\-\-push\-target " path Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". diff --git a/app/src/cli.c b/app/src/cli.c index eb28b1d7..ab2d7d10 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -55,6 +55,7 @@ #define OPT_NO_DOWNSIZE_ON_ERROR 1035 #define OPT_OTG 1036 #define OPT_NO_CLEANUP 1037 +#define OPT_PRINT_FPS 1038 struct sc_option { char shortopt; @@ -336,6 +337,12 @@ static const struct sc_option options[] = { "special character, but breaks the expected behavior of alpha " "keys in games (typically WASD).", }, + { + .longopt_id = OPT_PRINT_FPS, + .longopt = "print-fps", + .text = "Start FPS counter, to print framerate logs to the console. " + "It can be started or stopped at any time with MOD+i.", + }, { .longopt_id = OPT_PUSH_TARGET, .longopt = "push-target", @@ -1547,6 +1554,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_CLEANUP: opts->cleanup = false; break; + case OPT_PRINT_FPS: + opts->start_fps_counter = true; + break; case OPT_OTG: #ifdef HAVE_USB opts->otg = true; diff --git a/app/src/options.c b/app/src/options.c index 5a717df5..6651020f 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -63,4 +63,5 @@ const struct scrcpy_options scrcpy_options_default = { .select_tcpip = false, .select_usb = false, .cleanup = true, + .start_fps_counter = false, }; diff --git a/app/src/options.h b/app/src/options.h index f96edb22..f63e5c42 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -138,6 +138,7 @@ struct scrcpy_options { bool select_usb; bool select_tcpip; bool cleanup; + bool start_fps_counter; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8c4920d6..3ed1d249 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -588,6 +588,7 @@ aoa_hid_end: .rotation = options->rotation, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, + .start_fps_counter = options->start_fps_counter, .buffering_time = options->display_buffer, }; diff --git a/app/src/screen.c b/app/src/screen.c index 2b1c5299..c233cf6e 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -386,6 +386,7 @@ sc_screen_init(struct sc_screen *screen, screen->req.width = params->window_width; screen->req.height = params->window_height; screen->req.fullscreen = params->fullscreen; + screen->req.start_fps_counter = params->start_fps_counter; static const struct sc_video_buffer_callbacks cbs = { .on_new_frame = sc_video_buffer_on_new_frame, @@ -562,6 +563,10 @@ sc_screen_show_initial_window(struct sc_screen *screen) { sc_screen_switch_fullscreen(screen); } + if (screen->req.start_fps_counter) { + sc_fps_counter_start(&screen->fps_counter); + } + SDL_ShowWindow(screen->window); } diff --git a/app/src/screen.h b/app/src/screen.h index 12b8816c..222e418f 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -35,6 +35,7 @@ struct sc_screen { uint16_t width; uint16_t height; bool fullscreen; + bool start_fps_counter; } req; SDL_Window *window; @@ -93,6 +94,7 @@ struct sc_screen_params { bool mipmaps; bool fullscreen; + bool start_fps_counter; sc_tick buffering_time; }; From 6edf50d447874f3bb93cbccff52c92f528d7aa9a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 19:10:20 +0100 Subject: [PATCH 0456/1133] Remove fprintf() in tests It was committed by mistake. --- app/tests/test_adb_parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 990418c0..c4b18a8d 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -91,7 +91,6 @@ static void test_adb_devices_daemon_start_mixed() { struct sc_adb_device *device = &devices[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("unauthorized", device->state)); - fprintf(stderr, "==== [%s]\n", device->model); assert(!device->model); device = &devices[1]; From c4ab65eb790b8b88d561f1cbcbda5afbabd93bfe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 21:18:36 +0100 Subject: [PATCH 0457/1133] Remove useless '\n' in log --- app/src/adb/adb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index e415eb4f..8ce9cc6f 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -412,7 +412,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, // The implementation assumes that the output of "adb devices -l" fits // in the buffer in a single pass LOGW("Result of \"adb devices -l\" does not fit in 4Kb. " - "Please report an issue.\n"); + "Please report an issue."); return -1; } @@ -676,7 +676,7 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { // The implementation assumes that the output of "ip route" fits in the // buffer in a single pass LOGW("Result of \"ip route\" does not fit in 1Kb. " - "Please report an issue.\n"); + "Please report an issue."); return NULL; } From b4fd882ece2e673f084b7820b7a8925a86c9bfce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Feb 2022 17:17:37 +0100 Subject: [PATCH 0458/1133] Fix typo --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index ab2d7d10..ae88ba40 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -118,7 +118,7 @@ static const struct sc_option options[] = { .text = "Crop the device screen on the server.\n" "The values are expressed in the device natural orientation " "(typically, portrait for a phone, landscape for a tablet). " - "Any --max-size value is cmoputed on the cropped size.", + "Any --max-size value is computed on the cropped size.", }, { .shortopt = 'd', From 33202491e13fa44f894a5f4256c7be1f3eaf7551 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 21:34:48 +0100 Subject: [PATCH 0459/1133] Build on macOS with libusb support Fixes #2774 PR #3031 --- BUILD.md | 2 +- app/meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index f683be95..e32ab684 100644 --- a/BUILD.md +++ b/BUILD.md @@ -199,7 +199,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 diff --git a/app/meson.build b/app/meson.build index 9ee38d5f..9cd009fe 100644 --- a/app/meson.build +++ b/app/meson.build @@ -74,7 +74,7 @@ if v4l2_support src += [ 'src/v4l2_sink.c' ] endif -usb_support = get_option('usb') and host_machine.system() == 'linux' +usb_support = get_option('usb') and host_machine.system() != 'windows' if usb_support src += [ 'src/usb/aoa_hid.c', From 82a99f69ec464a0637a16bdccfe5ff806777e942 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 21:40:01 +0100 Subject: [PATCH 0460/1133] Remove "linux-only" mentions for HID/OTG features HID/OTG features are not limited to Linux anymore. PR #3031 --- app/scrcpy.1 | 6 +++--- app/src/cli.c | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 62cfe004..f9d4ba24 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -100,7 +100,7 @@ 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. +It may only work over USB. The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: @@ -142,7 +142,7 @@ In this mode, the computer mouse is captured to control the device directly (rel LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. -It may only work over USB, and is currently only supported on Linux. +It may only work over USB. Also see \fB\-\-hid\-keyboard\fR. @@ -190,7 +190,7 @@ LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mou If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both. -It may only work over USB, and is currently only supported on Linux. +It may only work over USB. See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR. diff --git a/app/src/cli.c b/app/src/cli.c index ae88ba40..f930142e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -186,8 +186,7 @@ static const struct sc_option options[] = { "It provides a better experience for IME users, and allows to " "generate non-ASCII characters, contrary to the default " "injection method.\n" - "It may only work over USB, and is currently only supported " - "on Linux.\n" + "It may only work over USB.\n" "The keyboard layout must be configured (once and for all) on " "the device, via Settings -> System -> Languages and input -> " "Physical keyboard. This settings page can be started " @@ -239,8 +238,7 @@ static const struct sc_option options[] = { "device directly (relative mouse mode).\n" "LAlt, LSuper or RSuper toggle the capture mode, to give " "control of the mouse back to the computer.\n" - "It may only work over USB, and is currently only supported " - "on Linux.\n" + "It may only work over USB.\n" "Also see --hid-keyboard.", }, { @@ -311,8 +309,7 @@ static const struct sc_option options[] = { "control of the mouse back to the computer.\n" "If any of --hid-keyboard or --hid-mouse is set, only enable " "keyboard or mouse respectively, otherwise enable both.\n" - "It may only work over USB, and is currently only supported " - "on Linux.\n" + "It may only work over USB.\n" "See --hid-keyboard and --hid-mouse.", }, { From 9db42341e4934989921099c60e307c847eb625b6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 21:37:14 +0100 Subject: [PATCH 0461/1133] Pass screen instance to mouse capture functions Using the screen instance or not in these functions is an implementation detail. Further changes will require the screen instance. Refs 7848a387c8281cb156f0e0da6bbbb05cda31db22 PR #3031 --- app/src/screen.c | 24 ++++++++++++++---------- app/src/usb/screen_otg.c | 28 ++++++++++++++++------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index c233cf6e..7ca9bfb7 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -163,7 +163,8 @@ sc_screen_is_relative_mode(struct sc_screen *screen) { } static void -sc_screen_set_mouse_capture(bool capture) { +sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) { + (void) screen; if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); @@ -171,13 +172,16 @@ sc_screen_set_mouse_capture(bool capture) { } static inline bool -sc_screen_get_mouse_capture(void) { +sc_screen_get_mouse_capture(struct sc_screen *screen) { + (void) screen; return SDL_GetRelativeMouseMode(); } static inline void -sc_screen_toggle_mouse_capture(void) { - sc_screen_set_mouse_capture(!sc_screen_get_mouse_capture()); +sc_screen_toggle_mouse_capture(struct sc_screen *screen) { + (void) screen; + bool new_value = !sc_screen_get_mouse_capture(screen); + sc_screen_set_mouse_capture(screen, new_value); } static void @@ -721,7 +725,7 @@ sc_screen_update_frame(struct sc_screen *screen) { if (sc_screen_is_relative_mode(screen)) { // Capture mouse on start - sc_screen_set_mouse_capture(true); + sc_screen_set_mouse_capture(screen, true); } } @@ -834,7 +838,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { break; case SDL_WINDOWEVENT_FOCUS_LOST: if (relative_mode) { - sc_screen_set_mouse_capture(false); + sc_screen_set_mouse_capture(screen, false); } break; } @@ -864,7 +868,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (key == cap) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode - sc_screen_toggle_mouse_capture(); + sc_screen_toggle_mouse_capture(screen); } // Mouse capture keys are never forwarded to the device return; @@ -874,7 +878,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { case SDL_MOUSEWHEEL: case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: - if (relative_mode && !sc_screen_get_mouse_capture()) { + if (relative_mode && !sc_screen_get_mouse_capture(screen)) { // Do not forward to input manager, the mouse will be captured // on SDL_MOUSEBUTTONUP return; @@ -890,8 +894,8 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } break; case SDL_MOUSEBUTTONUP: - if (relative_mode && !sc_screen_get_mouse_capture()) { - sc_screen_set_mouse_capture(true); + if (relative_mode && !sc_screen_get_mouse_capture(screen)) { + sc_screen_set_mouse_capture(screen, true); return; } break; diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index f32bc946..96d7eaff 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -5,7 +5,8 @@ #include "util/log.h" static void -sc_screen_otg_set_mouse_capture(bool capture) { +sc_screen_otg_set_mouse_capture(struct sc_screen_otg *screen, bool capture) { + (void) screen; if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); @@ -13,13 +14,16 @@ sc_screen_otg_set_mouse_capture(bool capture) { } static inline bool -sc_screen_otg_get_mouse_capture(void) { +sc_screen_otg_get_mouse_capture(struct sc_screen_otg *screen) { + (void) screen; return SDL_GetRelativeMouseMode(); } static inline void -sc_screen_otg_toggle_mouse_capture(void) { - sc_screen_otg_set_mouse_capture(!sc_screen_otg_get_mouse_capture()); +sc_screen_otg_toggle_mouse_capture(struct sc_screen_otg *screen) { + (void) screen; + bool new_value = !sc_screen_otg_get_mouse_capture(screen); + sc_screen_otg_set_mouse_capture(screen, new_value); } static void @@ -86,7 +90,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen, if (screen->mouse) { // Capture mouse on start - sc_screen_otg_set_mouse_capture(true); + sc_screen_otg_set_mouse_capture(screen, true); } return true; @@ -198,7 +202,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { break; case SDL_WINDOWEVENT_FOCUS_LOST: if (screen->mouse) { - sc_screen_otg_set_mouse_capture(false); + sc_screen_otg_set_mouse_capture(screen, false); } break; } @@ -232,7 +236,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { if (key == cap) { // A mouse capture key has been pressed then released: // toggle the capture mouse mode - sc_screen_otg_toggle_mouse_capture(); + sc_screen_otg_toggle_mouse_capture(screen); } // Mouse capture keys are never forwarded to the device return; @@ -244,26 +248,26 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { } break; case SDL_MOUSEMOTION: - if (screen->mouse && sc_screen_otg_get_mouse_capture()) { + if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) { sc_screen_otg_process_mouse_motion(screen, &event->motion); } break; case SDL_MOUSEBUTTONDOWN: - if (screen->mouse && sc_screen_otg_get_mouse_capture()) { + if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) { sc_screen_otg_process_mouse_button(screen, &event->button); } break; case SDL_MOUSEBUTTONUP: if (screen->mouse) { - if (sc_screen_otg_get_mouse_capture()) { + if (sc_screen_otg_get_mouse_capture(screen)) { sc_screen_otg_process_mouse_button(screen, &event->button); } else { - sc_screen_otg_set_mouse_capture(true); + sc_screen_otg_set_mouse_capture(screen, true); } } break; case SDL_MOUSEWHEEL: - if (screen->mouse && sc_screen_otg_get_mouse_capture()) { + if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) { sc_screen_otg_process_mouse_wheel(screen, &event->wheel); } break; From 3ee3f8dc027fe4e2d21ef144df4c29acbd709ee7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Feb 2022 21:46:13 +0100 Subject: [PATCH 0462/1133] Work around mouse capture SDL bug on macOS On macOS, SDL relative mouse mode does not work correctly when the cursor is outside the window. As a workaround, move the cursor inside the window before setting the relative mouse mode. Refs SDL/#5340 PR #3031 --- app/src/screen.c | 19 +++++++++++++++++++ app/src/usb/screen_otg.c | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 7ca9bfb7..ae28e6e6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -164,7 +164,26 @@ sc_screen_is_relative_mode(struct sc_screen *screen) { static void sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) { +#ifdef __APPLE__ + // Workaround for SDL bug on macOS: + // + if (capture) { + int mouse_x, mouse_y; + SDL_GetGlobalMouseState(&mouse_x, &mouse_y); + + int x, y, w, h; + SDL_GetWindowPosition(screen->window, &x, &y); + SDL_GetWindowSize(screen->window, &w, &h); + + bool outside_window = mouse_x < x || mouse_x >= x + w + || mouse_y < y || mouse_y >= y + h; + if (outside_window) { + SDL_WarpMouseInWindow(screen->window, w / 2, h / 2); + } + } +#else (void) screen; +#endif if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 96d7eaff..561a84ca 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -6,7 +6,26 @@ static void sc_screen_otg_set_mouse_capture(struct sc_screen_otg *screen, bool capture) { +#ifdef __APPLE__ + // Workaround for SDL bug on macOS: + // + if (capture) { + int mouse_x, mouse_y; + SDL_GetGlobalMouseState(&mouse_x, &mouse_y); + + int x, y, w, h; + SDL_GetWindowPosition(screen->window, &x, &y); + SDL_GetWindowSize(screen->window, &w, &h); + + bool outside_window = mouse_x < x || mouse_x >= x + w + || mouse_y < y || mouse_y >= y + h; + if (outside_window) { + SDL_WarpMouseInWindow(screen->window, w / 2, h / 2); + } + } +#else (void) screen; +#endif if (SDL_SetRelativeMouseMode(capture)) { LOGE("Could not set relative mouse mode to %s: %s", capture ? "true" : "false", SDL_GetError()); From be1936bb85de6a19a805ed320dcabe217e52d656 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 07:57:18 +0100 Subject: [PATCH 0463/1133] Report USB device disconnection when detected USB device disconnection is detected via a hotplug callback when it is supported. In addition, report disconnection on libusb calls returning LIBUSB_ERROR_NO_DEVICE or LIBUSB_ERROR_NOT_FOUND. This allows to detect disconnection after a libusb call when hotplug is not available. PR #3011 --- app/src/usb/aoa_hid.c | 4 ++++ app/src/usb/usb.c | 22 ++++++++++++++++++++-- app/src/usb/usb.h | 6 ++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 57296bfc..0007169d 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -95,6 +95,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, DEFAULT_TIMEOUT); if (result < 0) { LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result)); + sc_usb_check_disconnected(aoa->usb, result); return false; } @@ -131,6 +132,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, DEFAULT_TIMEOUT); if (result < 0) { LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result)); + sc_usb_check_disconnected(aoa->usb, result); return false; } @@ -173,6 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { DEFAULT_TIMEOUT); if (result < 0) { LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result)); + sc_usb_check_disconnected(aoa->usb, result); return false; } @@ -195,6 +198,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { DEFAULT_TIMEOUT); if (result < 0) { LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); + sc_usb_check_disconnected(aoa->usb, result); return false; } diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 07fb9619..7a0e4cfd 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -216,6 +216,24 @@ sc_usb_destroy(struct sc_usb *usb) { libusb_exit(usb->context); } +static void +sc_usb_report_disconnected(struct sc_usb *usb) { + if (usb->cbs && !atomic_flag_test_and_set(&usb->disconnection_notified)) { + assert(usb->cbs && usb->cbs->on_disconnected); + usb->cbs->on_disconnected(usb, usb->cbs_userdata); + } +} + +bool +sc_usb_check_disconnected(struct sc_usb *usb, int result) { + if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_NOT_FOUND) { + sc_usb_report_disconnected(usb); + return false; + } + + return true; +} + static int sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *userdata) { @@ -232,8 +250,7 @@ sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, return 0; } - assert(usb->cbs && usb->cbs->on_disconnected); - usb->cbs->on_disconnected(usb, usb->cbs_userdata); + sc_usb_report_disconnected(usb); // Do not automatically deregister the callback by returning 1. Instead, // manually deregister to interrupt libusb_handle_events() from the libusb @@ -307,6 +324,7 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device, if (cbs) { atomic_init(&usb->stopped, false); + usb->disconnection_notified = (atomic_flag) ATOMIC_FLAG_INIT; if (sc_usb_register_callback(usb)) { // Create a thread to process libusb events, so that device // disconnection could be detected immediately diff --git a/app/src/usb/usb.h b/app/src/usb/usb.h index d264a536..f0ebbd96 100644 --- a/app/src/usb/usb.h +++ b/app/src/usb/usb.h @@ -22,6 +22,7 @@ struct sc_usb { sc_thread libusb_event_thread; atomic_bool stopped; // only used if cbs != NULL + atomic_flag disconnection_notified; }; struct sc_usb_callbacks { @@ -73,6 +74,11 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device, void sc_usb_disconnect(struct sc_usb *usb); +// A client should call this function with the return value of a libusb call +// to detect disconnection immediately +bool +sc_usb_check_disconnected(struct sc_usb *usb, int result); + void sc_usb_stop(struct sc_usb *usb); From b9b28797893c9449b89b44e1c0ac209a5358cca5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 09:29:07 +0100 Subject: [PATCH 0464/1133] Remove USB hotplug callback error log If it fails, the error is already logged by sc_usb_register_callback(). PR #3011 --- app/src/usb/usb.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 7a0e4cfd..799ada94 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -335,8 +335,6 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device, LOGW("Libusb event thread handler could not be created, USB " "device disconnection might not be detected immediately"); } - } else { - LOGW("Could not register USB device disconnection callback"); } } From 06243e7c3cd313e2d4d4b34fe0d2c3f35d9aee6d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 08:57:42 +0100 Subject: [PATCH 0465/1133] Avoid PRIx16 printf format on Windows Convert uint16_t to unsigned to avoid using PRIx16, which may not exist on Windows. PR #3011 --- app/src/usb/scrcpy_otg.c | 4 ++-- app/src/usb/usb.c | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index d3a45679..1eaa2537 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -91,8 +91,8 @@ scrcpy_otg(struct scrcpy_options *options) { usb_device_initialized = true; - LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - usb_device.serial, usb_device.vid, usb_device.pid, + LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial, + (unsigned) usb_device.vid, (unsigned) usb_device.pid, usb_device.manufacturer, usb_device.product); ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL); diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 799ada94..2d3fc3a6 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -40,8 +40,9 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) { if (result < 0) { // Log at debug level because it is expected that some non-Android USB // devices present on the computer require special permissions - LOGD("Open USB device %04" PRIx16 ":%04" PRIx16 ": libusb error: %s", - desc.idVendor, desc.idProduct, libusb_strerror(result)); + LOGD("Open USB device %04x:%04x: libusb error: %s", + (unsigned) desc.idVendor, (unsigned) desc.idProduct, + libusb_strerror(result)); return false; } @@ -146,8 +147,10 @@ sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices, for (size_t i = 0; i < count; ++i) { struct sc_usb_device *d = &devices[i]; const char *selection = d->selected ? "-->" : " "; - LOG(level, " %s %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", - selection, d->serial, d->vid, d->pid, d->manufacturer, d->product); + // Convert uint16_t to unsigned because PRIx16 may not exist on Windows + LOG(level, " %s %-18s (%04x:%04x) %s %s", + selection, d->serial, (unsigned) d->vid, (unsigned) d->pid, + d->manufacturer, d->product); } } From ff3cb31cb4a5d58354e6a7b59c6e2486091ff691 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 09:08:21 +0100 Subject: [PATCH 0466/1133] Fix libusb callback for Windows Add LIBUSB_CALL so that the callback has the correct signature on Windows (including __attribute__((stdcall))). PR #3011 --- app/src/usb/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 2d3fc3a6..276b0067 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -237,7 +237,7 @@ sc_usb_check_disconnected(struct sc_usb *usb, int result) { return true; } -static int +static LIBUSB_CALL int sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *userdata) { (void) ctx; From 6b65cd405a56d09b00a609ef43f2846c2cc349f7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Feb 2022 08:52:17 +0100 Subject: [PATCH 0467/1133] Build for Windows with libusb support Fixes #2773 PR #3011 --- BUILD.md | 6 ++++-- app/meson.build | 15 ++++++++++++++- app/prebuilt-deps/prepare-libusb.sh | 28 ++++++++++++++++++++++++++++ cross_win32.txt | 2 ++ cross_win64.txt | 2 ++ release.mk | 4 ++++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100755 app/prebuilt-deps/prepare-libusb.sh diff --git a/BUILD.md b/BUILD.md index e32ab684..60be752d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -161,7 +161,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 +176,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 \ diff --git a/app/meson.build b/app/meson.build index 9cd009fe..a0086619 100644 --- a/app/meson.build +++ b/app/meson.build @@ -74,7 +74,7 @@ if v4l2_support src += [ 'src/v4l2_sink.c' ] endif -usb_support = get_option('usb') and host_machine.system() != 'windows' +usb_support = get_option('usb') if usb_support src += [ 'src/usb/aoa_hid.c', @@ -141,9 +141,22 @@ else include_directories: include_directories(ffmpeg_include_dir) ) + prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') + prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root') + libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/dll' + libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include' + + libusb = declare_dependency( + dependencies: [ + cc.find_library('libusb-1.0', dirs: libusb_bin_dir), + ], + include_directories: include_directories(libusb_include_dir) + ) + dependencies = [ ffmpeg, sdl2, + libusb, cc.find_library('mingw32') ] diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh new file mode 100755 index 00000000..54ead536 --- /dev/null +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +DEP_DIR=libusb-1.0.25 + +FILENAME=libusb-1.0.25.7z +SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +7z x "../$FILENAME" \ + MinGW32/dll/libusb-1.0.dll \ + MinGW64/dll/libusb-1.0.dll \ + include / diff --git a/cross_win32.txt b/cross_win32.txt index db448a00..750dbd78 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' +prebuilt_libusb_root = 'libusb-1.0.25' +prebuilt_libusb = prebuilt_libusb_root + '/MinGW32' diff --git a/cross_win64.txt b/cross_win64.txt index 9d169a71..114b0c22 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.0' prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' +prebuilt_libusb_root = 'libusb-1.0.25' +prebuilt_libusb = prebuilt_libusb_root + '/MinGW64' diff --git a/release.mk b/release.mk index aff9bd89..00d2389f 100644 --- a/release.mk +++ b/release.mk @@ -66,11 +66,13 @@ prepare-deps-win32: @app/prebuilt-deps/prepare-adb.sh @app/prebuilt-deps/prepare-sdl.sh @app/prebuilt-deps/prepare-ffmpeg-win32.sh + @app/prebuilt-deps/prepare-libusb.sh prepare-deps-win64: @app/prebuilt-deps/prepare-adb.sh @app/prebuilt-deps/prepare-sdl.sh @app/prebuilt-deps/prepare-ffmpeg-win64.sh + @app/prebuilt-deps/prepare-libusb.sh build-win32: prepare-deps-win32 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ @@ -107,6 +109,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -125,6 +128,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.25/MinGW64/dll/libusb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From 6ee75c0cff3eb995486901757e682f960516b6b0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Feb 2022 17:19:11 +0100 Subject: [PATCH 0468/1133] Remove obsolete text in error message The HID/OTG features are now available on all platforms. PR #3011 --- app/src/cli.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index f930142e..d8ae4f53 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1370,8 +1370,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; break; #else - LOGE("HID over AOA (-K/--hid-keyboard) is disabled (or " - "unsupported on this platform)."); + LOGE("HID over AOA (-K/--hid-keyboard) is disabled."); return false; #endif case OPT_MAX_FPS: @@ -1389,8 +1388,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; break; #else - LOGE("HID over AOA (-M/--hid-mouse) is disabled (or " - "unsupported on this platform)."); + LOGE("HID over AOA (-M/--hid-mouse) is disabled."); return false; #endif case OPT_LOCK_VIDEO_ORIENTATION: @@ -1559,8 +1557,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->otg = true; break; #else - LOGE("OTG mode (--otg) is disabled (or unsupported on this " - "platform)."); + LOGE("OTG mode (--otg) is disabled."); return false; #endif case OPT_V4L2_SINK: From 3bb24b3926a2db0fa38393c829b4e703471c0fcc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 08:05:54 +0100 Subject: [PATCH 0469/1133] Make intr optional for adb commands All adb commands are executed with an "interruptor", so that they can be interrupted on Ctrl+C. Make this interruptor optional, so that we could call "adb kill-server" in OTG mode. This command always returns almost immediately anyway. Ideally, we should make all blocking calls interruptible (including libusb calls, by using the asynchronous API), but it's a lot of work, and in practice it works well enough. PR #3011 --- app/src/adb/adb.c | 6 ++++-- app/src/util/process_intr.c | 14 ++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 8ce9cc6f..8742c4d0 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -150,7 +150,7 @@ process_check_success_internal(sc_pid pid, const char *name, bool close, static bool process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, unsigned flags) { - if (!sc_intr_set_process(intr, pid)) { + if (intr && !sc_intr_set_process(intr, pid)) { // Already interrupted return false; } @@ -158,7 +158,9 @@ process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, // Always pass close=false, interrupting would be racy otherwise bool ret = process_check_success_internal(pid, name, false, flags); - sc_intr_set_process(intr, SC_PROCESS_NONE); + if (intr) { + sc_intr_set_process(intr, SC_PROCESS_NONE); + } // Close separately sc_process_close(pid); diff --git a/app/src/util/process_intr.c b/app/src/util/process_intr.c index 940fe89f..d37bd5a5 100644 --- a/app/src/util/process_intr.c +++ b/app/src/util/process_intr.c @@ -3,27 +3,33 @@ ssize_t sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, size_t len) { - if (!sc_intr_set_process(intr, pid)) { + if (intr && !sc_intr_set_process(intr, pid)) { // Already interrupted return false; } ssize_t ret = sc_pipe_read(pipe, data, len); - sc_intr_set_process(intr, SC_PROCESS_NONE); + if (intr) { + sc_intr_set_process(intr, SC_PROCESS_NONE); + } + return ret; } ssize_t sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, size_t len) { - if (!sc_intr_set_process(intr, pid)) { + if (intr && !sc_intr_set_process(intr, pid)) { // Already interrupted return false; } ssize_t ret = sc_pipe_read_all(pipe, data, len); - sc_intr_set_process(intr, SC_PROCESS_NONE); + if (intr) { + sc_intr_set_process(intr, SC_PROCESS_NONE); + } + return ret; } From 25296ae16710c0c7bf69e60f4e1aa952e0443075 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 08:42:15 +0100 Subject: [PATCH 0470/1133] Kill adb daemon in OTG mode on Windows On Windows, it is not possible to open a USB device from several process, so HID events may only work if no adb daemon is running. PR #3011 --- app/src/adb/adb.c | 8 ++++++++ app/src/adb/adb.h | 3 +++ app/src/usb/scrcpy_otg.c | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 8742c4d0..4ddb93f3 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -204,6 +204,14 @@ sc_adb_start_server(struct sc_intr *intr, unsigned flags) { return process_check_success_intr(intr, pid, "adb start-server", flags); } +bool +sc_adb_kill_server(struct sc_intr *intr, unsigned flags) { + const char *const argv[] = SC_ADB_COMMAND("kill-server"); + + sc_pid pid = sc_adb_execute(argv, flags); + return process_check_success_intr(intr, pid, "adb kill-server", flags); +} + bool sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name, unsigned flags) { diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 6ea6e897..10e8f293 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -36,6 +36,9 @@ sc_adb_execute(const char *const argv[], unsigned flags); bool sc_adb_start_server(struct sc_intr *intr, unsigned flags); +bool +sc_adb_kill_server(struct sc_intr *intr, unsigned flags); + bool sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name, unsigned flags); diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 1eaa2537..1c53410e 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -2,6 +2,7 @@ #include +#include "adb/adb.h" #include "events.h" #include "screen_otg.h" #include "util/log.h" @@ -75,6 +76,15 @@ scrcpy_otg(struct scrcpy_options *options) { bool aoa_started = false; bool aoa_initialized = false; +#ifdef _WIN32 + // On Windows, only one process could open a USB device + // + LOGI("Killing adb daemon (if any)..."); + unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; + // uninterruptible (intr == NULL), but in practice it's very quick + sc_adb_kill_server(NULL, flags); +#endif + static const struct sc_usb_callbacks cbs = { .on_disconnected = sc_usb_on_disconnected, }; From 73a5311ac6c6570f1c9d23cd943c8d9aa0b281fe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Feb 2022 09:01:31 +0100 Subject: [PATCH 0471/1133] Forbid HID input without OTG on Windows On Windows, if the adb daemon is running, opening the USB device will necessarily fail, so HID input is not possible. Refs #2773 PR #3011 --- app/src/cli.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index d8ae4f53..48289678 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1680,6 +1680,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #ifdef HAVE_USB + +# ifdef _WIN32 + if (!opts->otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID + || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { + LOGE("On Windows, it is not possible to open a USB device already open " + "by another process (like adb)."); + LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in " + "OTG mode (--otg)."); + return false; + } +# endif + if (opts->otg) { // OTG mode is compatible with only very few options. // Only report obvious errors. From d9bc5082ab24d214928be499f70c2aaed107b0df Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Feb 2022 17:11:07 +0100 Subject: [PATCH 0472/1133] Disable USB features for win32 Currently, there is an issue with the libusb prebuilt dll. Refs libusb/#1049 PR #3011 --- release.mk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release.mk b/release.mk index 00d2389f..98b84a8e 100644 --- a/release.mk +++ b/release.mk @@ -75,10 +75,12 @@ prepare-deps-win64: @app/prebuilt-deps/prepare-libusb.sh build-win32: prepare-deps-win32 + # -Dusb=false because of libusb-win32 build issue, cf #3011 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ meson "$(WIN32_BUILD_DIR)" \ --cross-file cross_win32.txt \ --buildtype release --strip -Db_lto=true \ + -Dusb=false \ -Dcompile_server=false \ -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" @@ -109,7 +111,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + #cp app/prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" From 36c75e15b8e9eeb01bd287a42fa3f1513a728ebb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Feb 2022 17:56:50 +0100 Subject: [PATCH 0473/1133] Move data/ to app/ The files in data/ are specific to the client app (not the server). This also avoids to reference the parent directory (../) from app/meson.build. Refs 8d583d36e259ba7f5f21d7a703cca73184200aa9 --- {data => app/data}/icon.ico | Bin {data => app/data}/icon.png | Bin {data => app/data}/icon.svg | 0 {data => app/data}/open_a_terminal_here.bat | 0 {data => app/data}/scrcpy-console.bat | 0 {data => app/data}/scrcpy-noconsole.vbs | 0 app/meson.build | 2 +- app/scrcpy-windows.rc | 2 +- release.mk | 16 ++++++++-------- 9 files changed, 10 insertions(+), 10 deletions(-) rename {data => app/data}/icon.ico (100%) rename {data => app/data}/icon.png (100%) rename {data => app/data}/icon.svg (100%) rename {data => app/data}/open_a_terminal_here.bat (100%) rename {data => app/data}/scrcpy-console.bat (100%) rename {data => app/data}/scrcpy-noconsole.vbs (100%) diff --git a/data/icon.ico b/app/data/icon.ico similarity index 100% rename from data/icon.ico rename to app/data/icon.ico diff --git a/data/icon.png b/app/data/icon.png similarity index 100% rename from data/icon.png rename to app/data/icon.png diff --git a/data/icon.svg b/app/data/icon.svg similarity index 100% rename from data/icon.svg rename to app/data/icon.svg diff --git a/data/open_a_terminal_here.bat b/app/data/open_a_terminal_here.bat similarity index 100% rename from data/open_a_terminal_here.bat rename to app/data/open_a_terminal_here.bat diff --git a/data/scrcpy-console.bat b/app/data/scrcpy-console.bat similarity index 100% rename from data/scrcpy-console.bat rename to app/data/scrcpy-console.bat diff --git a/data/scrcpy-noconsole.vbs b/app/data/scrcpy-noconsole.vbs similarity index 100% rename from data/scrcpy-noconsole.vbs rename to app/data/scrcpy-noconsole.vbs diff --git a/app/meson.build b/app/meson.build index a0086619..6449439a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -224,7 +224,7 @@ executable('scrcpy', src, c_args: []) install_man('scrcpy.1') -install_data('../data/icon.png', +install_data('data/icon.png', rename: 'scrcpy.png', install_dir: 'share/icons/hicolor/256x256/apps') diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index b130dc6a..dbd6aedb 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -1,6 +1,6 @@ #include -0 ICON "../data/icon.ico" +0 ICON "data/icon.ico" 1 RT_MANIFEST "scrcpy-windows.manifest" 2 VERSIONINFO BEGIN diff --git a/release.mk b/release.mk index 98b84a8e..caf6ab1a 100644 --- a/release.mk +++ b/release.mk @@ -98,10 +98,10 @@ dist-win32: build-server build-win32 mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" - cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" - cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" + cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" + cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" + cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" + cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -117,10 +117,10 @@ dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" - cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" - cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" + cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" + cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" + cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" + cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From c070723bc8b9b60255605c409b7a43329d27b4cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 08:34:50 +0100 Subject: [PATCH 0474/1133] Add sc_vector Adapt vlc_vector [1], that I initially wrote while implementing the VLC playlist [2]. Change the implementation to use "statement expressions" [3], which are forbidden in VLC because "non-standard", but: - they are supported by gcc and clang; - they are already used in the scrcpy codebase; - they avoid implementation hacks (VLC_VECTOR_FAILFLAG_); - they allow a better API (sc_vector_index_of() may return the result without an output parameter). PR #3035 [1]: https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h [2]: https://blog.rom1v.com/2019/05/a-new-core-playlist-for-vlc-4 [3]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html --- app/meson.build | 3 + app/src/util/vector.h | 539 ++++++++++++++++++++++++++++++++++++++++ app/tests/test_vector.c | 421 +++++++++++++++++++++++++++++++ 3 files changed, 963 insertions(+) create mode 100644 app/src/util/vector.h create mode 100644 app/tests/test_vector.c diff --git a/app/meson.build b/app/meson.build index 6449439a..6255bcbc 100644 --- a/app/meson.build +++ b/app/meson.build @@ -282,6 +282,9 @@ if get_option('buildtype') == 'debug' 'src/util/str.c', 'src/util/strbuf.c', ]], + ['test_vector', [ + 'tests/test_vector.c', + ]], ] foreach t : tests diff --git a/app/src/util/vector.h b/app/src/util/vector.h new file mode 100644 index 00000000..a08fa9d6 --- /dev/null +++ b/app/src/util/vector.h @@ -0,0 +1,539 @@ +#ifndef SC_VECTOR_H +#define SC_VECTOR_H + +#include "common.h" + +#include +#include + +// Adapted from vlc_vector: +// + +/** + * Vector struct body + * + * A vector is a dynamic array, managed by the sc_vector_* helpers. + * + * It is generic over the type of its items, so it is implemented as macros. + * + * To use a vector, a new type must be defined: + * + * struct vec_int SC_VECTOR(int); + * + * The struct may be anonymous: + * + * struct SC_VECTOR(const char *) names; + * + * Vector size is accessible via `vec.size`, and items are intended to be + * accessed directly, via `vec.data[i]`. + * + * Functions and macros having name ending with '_' are private. + */ +#define SC_VECTOR(type) \ +{ \ + size_t cap; \ + size_t size; \ + type *data; \ +} + +/** + * Static initializer for a vector + */ +#define SC_VECTOR_INITIALIZER { 0, 0, NULL } + +/** + * Initialize an empty vector + */ +#define sc_vector_init(pv) \ +({ \ + (pv)->cap = 0; \ + (pv)->size = 0; \ + (pv)->data = NULL; \ +}) + +/** + * Destroy a vector + * + * The vector may not be used anymore unless sc_vector_init() is called. + */ +#define sc_vector_destroy(pv) \ + free((pv)->data) + +/** + * Clear a vector + * + * Remove all items from the vector. + */ +#define sc_vector_clear(pv) \ +({ \ + sc_vector_destroy(pv); \ + sc_vector_init(pv);\ +}) + +/** + * The minimal allocation size, in number of items + * + * Private. + */ +#define SC_VECTOR_MINCAP_ ((size_t) 10) + +static inline size_t +sc_vector_min_(size_t a, size_t b) +{ + return a < b ? a : b; +} + +static inline size_t +sc_vector_max_(size_t a, size_t b) +{ + return a > b ? a : b; +} + +static inline size_t +sc_vector_clamp_(size_t x, size_t min, size_t max) +{ + return sc_vector_max_(min, sc_vector_min_(max, x)); +} + +/** + * Realloc data and update vector fields + * + * On reallocation success, update the vector capacity (*pcap) and size + * (*psize), and return the reallocated data. + * + * On reallocation failure, return NULL without any change. + * + * Private. + * + * \param ptr the current `data` field of the vector to realloc + * \param count the requested capacity, in number of items + * \param size the size of one item + * \param pcap a pointer to the `cap` field of the vector [IN/OUT] + * \param psize a pointer to the `size` field of the vector [IN/OUT] + * \return the new ptr on success, NULL on error + */ +static inline void * +sc_vector_reallocdata_(void *ptr, size_t count, size_t size, + size_t *restrict pcap, size_t *restrict psize) +{ + void *p = realloc(ptr, count * size); + if (!p) { + return NULL; + } + + *pcap = count; + *psize = sc_vector_min_(*psize, count); + return p; +} + +#define sc_vector_realloc_(pv, newcap) \ +({ \ + void *p = sc_vector_reallocdata_((pv)->data, newcap, sizeof(*(pv)->data), \ + &(pv)->cap, &(pv)->size); \ + if (p) { \ + (pv)->data = p; \ + } \ + (bool) p; \ +}); + +#define sc_vector_resize_(pv, newcap) \ +({ \ + bool ok; \ + if ((pv)->cap == (newcap)) { \ + ok = true; \ + } else if ((newcap) > 0) { \ + ok = sc_vector_realloc_(pv, (newcap)); \ + } else { \ + sc_vector_clear(pv); \ + ok = true; \ + } \ + ok; \ +}) + +static inline size_t +sc_vector_growsize_(size_t value) +{ + /* integer multiplication by 1.5 */ + return value + (value >> 1); +} + +/* SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow. */ +#define sc_vector_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data)) + +/** + * Increase the capacity of the vector to at least `mincap` + * + * \param pv a pointer to the vector + * \param mincap (size_t) the requested capacity + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_reserve(pv, mincap) \ +({ \ + bool ok; \ + /* avoid to allocate tiny arrays (< SC_VECTOR_MINCAP_) */ \ + size_t mincap_ = sc_vector_max_(mincap, SC_VECTOR_MINCAP_); \ + if (mincap_ <= (pv)->cap) { \ + /* nothing to do */ \ + ok = true; \ + } else if (mincap_ <= sc_vector_max_cap_(pv)) { \ + /* not too big */ \ + size_t newsize = sc_vector_growsize_((pv)->cap); \ + newsize = sc_vector_clamp_(newsize, mincap_, sc_vector_max_cap_(pv)); \ + ok = sc_vector_realloc_(pv, newsize); \ + } else { \ + ok = false; \ + } \ + ok; \ +}) + +#define sc_vector_shrink_to_fit(pv) \ + /* decreasing the size may not fail */ \ + (void) sc_vector_resize_(pv, (pv)->size) + +/** + * Resize the vector down automatically + * + * Shrink only when necessary (in practice when cap > (size+5)*1.5) + * + * \param pv a pointer to the vector + */ +#define sc_vector_autoshrink(pv) \ +({ \ + bool must_shrink = \ + /* do not shrink to tiny size */ \ + (pv)->cap > SC_VECTOR_MINCAP_ && \ + /* no need to shrink */ \ + (pv)->cap >= sc_vector_growsize_((pv)->size + 5); \ + if (must_shrink) { \ + size_t newsize = sc_vector_max_((pv)->size + 5, SC_VECTOR_MINCAP_); \ + sc_vector_resize_(pv, newsize); \ + } \ +}) + +#define sc_vector_check_same_ptr_type_(a, b) \ + (void) ((a) == (b)) /* warn on type mismatch */ + +/** + * Push an item at the end of the vector + * + * The amortized complexity is O(1). + * + * \param pv a pointer to the vector + * \param item the item to append + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_push(pv, item) \ +({ \ + bool ok = sc_vector_reserve(pv, (pv)->size + 1); \ + if (ok) { \ + (pv)->data[(pv)->size++] = (item); \ + } \ + ok; \ +}) + +/** + * Append `count` items at the end of the vector + * + * \param pv a pointer to the vector + * \param items the items array to append + * \param count the number of items in the array + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_push_all(pv, items, count) \ + sc_vector_push_all_(pv, items, (size_t) count) + +#define sc_vector_push_all_(pv, items, count) \ +({ \ + sc_vector_check_same_ptr_type_((pv)->data, items); \ + bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \ + if (ok) { \ + memcpy(&(pv)->data[(pv)->size], items, (count) * sizeof(*(pv)->data)); \ + (pv)->size += count; \ + } \ + ok; \ +}) + +/** + * Insert an hole of size `count` to the given index + * + * The items in range [index; size-1] will be moved. The items in the hole are + * left uninitialized. + * + * \param pv a pointer to the vector + * \param index the index where the hole is to be inserted + * \param count the number of items in the hole + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert_hole(pv, index, count) \ + sc_vector_insert_hole_(pv, (size_t) index, (size_t) count); + +#define sc_vector_insert_hole_(pv, index, count) \ +({ \ + bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \ + if (ok) { \ + if ((index) < (pv)->size) { \ + memmove(&(pv)->data[(index) + (count)], \ + &(pv)->data[(index)], \ + ((pv)->size - (index)) * sizeof(*(pv)->data)); \ + } \ + (pv)->size += count; \ + } \ + ok; \ +}) + +/** + * Insert an item at the given index + * + * The items in range [index; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index where the item is to be inserted + * \param item the item to append + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert(pv, index, item) \ + sc_vector_insert_(pv, (size_t) index, (size_t) item); + +#define sc_vector_insert_(pv, index, item) \ +({ \ + bool ok = sc_vector_insert_hole_(pv, index, 1); \ + if (ok) { \ + (pv)->data[index] = (item); \ + } \ + ok; \ +}) + +/** + * Insert `count` items at the given index + * + * The items in range [index; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index where the items are to be inserted + * \param items the items array to append + * \param count the number of items in the array + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert_all(pv, index, items, count) \ + sc_vector_insert_all_(pv, (size_t) index, items, (size_t) count) + +#define sc_vector_insert_all_(pv, index, items, count) \ +({ \ + sc_vector_check_same_ptr_type_((pv)->data, items); \ + bool ok = sc_vector_insert_hole_(pv, index, count); \ + if (ok) { \ + memcpy(&(pv)->data[index], items, count * sizeof(*(pv)->data)); \ + } \ + ok; \ +}) + +/** Reverse a char array in place */ +static inline void +sc_char_array_reverse(char *array, size_t len) +{ + for (size_t i = 0; i < len / 2; ++i) + { + char c = array[i]; + array[i] = array[len - i - 1]; + array[len - i - 1] = c; + } +} + +/** + * Right-rotate a (char) array in place + * + * For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with + * distance 4 will result in {5, 6, 1, 2, 3, 4}. + * + * Private. + */ +static inline void +sc_char_array_rotate_left(char *array, size_t len, size_t distance) +{ + sc_char_array_reverse(array, distance); + sc_char_array_reverse(&array[distance], len - distance); + sc_char_array_reverse(array, len); +} + +/** + * Right-rotate a (char) array in place + * + * For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with + * distance 2 will result in {5, 6, 1, 2, 3, 4}. + * + * Private. + */ +static inline void +sc_char_array_rotate_right(char *array, size_t len, size_t distance) +{ + sc_char_array_rotate_left(array, len, len - distance); +} + +/** + * Move items in a (char) array in place + * + * Move slice [index, count] to target. + */ +static inline void +sc_char_array_move(char *array, size_t idx, size_t count, size_t target) +{ + if (idx < target) { + sc_char_array_rotate_left(&array[idx], target - idx + count, count); + } else { + sc_char_array_rotate_right(&array[target], idx - target + count, count); + } +} + +/** + * Move a slice of items to a given target index + * + * The items in range [index; count] will be moved so that the *new* position + * of the first item is `target`. + * + * \param pv a pointer to the vector + * \param index the index of the first item to move + * \param count the number of items to move + * \param target the new index of the moved slice + */ +#define sc_vector_move_slice(pv, index, count, target) \ + sc_vector_move_slice_(pv, (size_t) index, count, (size_t) target); + +#define sc_vector_move_slice_(pv, index, count, target) \ +({ \ + sc_char_array_move((char *) (pv)->data, \ + (index) * sizeof(*(pv)->data), \ + (count) * sizeof(*(pv)->data), \ + (target) * sizeof(*(pv)->data)); \ +}) + +/** + * Move an item to a given target index + * + * The items will be moved so that its *new* position is `target`. + * + * \param pv a pointer to the vector + * \param index the index of the item to move + * \param target the new index of the moved item + */ +#define sc_vector_move(pv, index, target) \ + sc_vector_move_slice(pv, index, 1, target) + +/** + * Remove a slice of items, without shrinking the array + * + * If you have no good reason to use the _noshrink() version, use + * sc_vector_remove_slice() instead. + * + * The items in range [index+count; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of the first item to remove + * \param count the number of items to remove + */ +#define sc_vector_remove_slice_noshrink(pv, index, count) \ + sc_vector_remove_slice_noshrink_(pv, (size_t) index, (size_t) count) + +#define sc_vector_remove_slice_noshrink_(pv, index, count) \ +({ \ + if ((index) + (count) < (pv)->size) { \ + memmove(&(pv)->data[index], \ + &(pv)->data[(index) + (count)], \ + ((pv)->size - (index) - (count)) * sizeof(*(pv)->data)); \ + } \ + (pv)->size -= count; \ +}) + +/** + * Remove a slice of items + * + * The items in range [index+count; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of the first item to remove + * \param count the number of items to remove + */ +#define sc_vector_remove_slice(pv, index, count) \ +({ \ + sc_vector_remove_slice_noshrink(pv, index, count); \ + sc_vector_autoshrink(pv); \ +}) + +/** + * Remove an item, without shrinking the array + * + * If you have no good reason to use the _noshrink() version, use + * sc_vector_remove() instead. + * + * The items in range [index+1; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_remove_noshrink(pv, index) \ + sc_vector_remove_slice_noshrink(pv, index, 1) + +/** + * Remove an item + * + * The items in range [index+1; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_remove(pv, index) \ +({ \ + sc_vector_remove_noshrink(pv, index); \ + sc_vector_autoshrink(pv); \ +}) + +/** + * Remove an item + * + * The removed item is replaced by the last item of the vector. + * + * This does not preserve ordering, but is O(1). This is useful when the order + * of items is not meaningful. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_swap_remove(pv, index) \ + sc_vector_swap_remove_(pv, (size_t) index); + +#define sc_vector_swap_remove_(pv, index) \ +({ \ + (pv)->data[index] = (pv)->data[(pv)->size-1]; \ + (pv)->size--; \ +}); + +/** + * Return the index of an item + * + * Iterate over all items to find a given item. + * + * Use only for vectors of primitive types or pointers. + * + * Return the index, or -1 if not found. + * + * \param pv a pointer to the vector + * \param item the item to find (compared with ==) + */ +#define sc_vector_index_of(pv, item) \ +({ \ + ssize_t idx = -1; \ + for (size_t i = 0; i < (pv)->size; ++i) { \ + if ((pv)->data[i] == (item)) { \ + idx = (ssize_t) i; \ + break; \ + } \ + } \ + idx; \ +}) + +#endif diff --git a/app/tests/test_vector.c b/app/tests/test_vector.c new file mode 100644 index 00000000..7ca09989 --- /dev/null +++ b/app/tests/test_vector.c @@ -0,0 +1,421 @@ +#include "common.h" + +#include + +#include "util/vector.h" + +static void test_vector_insert_remove(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_push(&vec, 42); + assert(ok); + assert(vec.data[0] == 42); + assert(vec.size == 1); + + ok = sc_vector_push(&vec, 37); + assert(ok); + assert(vec.size == 2); + assert(vec.data[0] == 42); + assert(vec.data[1] == 37); + + ok = sc_vector_insert(&vec, 1, 100); + assert(ok); + assert(vec.size == 3); + assert(vec.data[0] == 42); + assert(vec.data[1] == 100); + assert(vec.data[2] == 37); + + ok = sc_vector_push(&vec, 77); + assert(ok); + assert(vec.size == 4); + assert(vec.data[0] == 42); + assert(vec.data[1] == 100); + assert(vec.data[2] == 37); + assert(vec.data[3] == 77); + + sc_vector_remove(&vec, 1); + assert(vec.size == 3); + assert(vec.data[0] == 42); + assert(vec.data[1] == 37); + assert(vec.data[2] == 77); + + sc_vector_clear(&vec); + assert(vec.size == 0); + + sc_vector_destroy(&vec); +} + +static void test_vector_push_array(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + ok = sc_vector_push_all(&vec, items, 8); + + assert(ok); + assert(vec.size == 13); + assert(vec.data[0] == 3); + assert(vec.data[1] == 14); + assert(vec.data[2] == 15); + assert(vec.data[3] == 92); + assert(vec.data[4] == 65); + assert(vec.data[5] == 1); + assert(vec.data[6] == 2); + assert(vec.data[7] == 3); + assert(vec.data[8] == 4); + assert(vec.data[9] == 5); + assert(vec.data[10] == 6); + assert(vec.data[11] == 7); + assert(vec.data[12] == 8); + + sc_vector_destroy(&vec); +} + +static void test_vector_insert_array(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + ok = sc_vector_insert_all(&vec, 3, items, 8); + assert(ok); + assert(vec.size == 13); + assert(vec.data[0] == 3); + assert(vec.data[1] == 14); + assert(vec.data[2] == 15); + assert(vec.data[3] == 1); + assert(vec.data[4] == 2); + assert(vec.data[5] == 3); + assert(vec.data[6] == 4); + assert(vec.data[7] == 5); + assert(vec.data[8] == 6); + assert(vec.data[9] == 7); + assert(vec.data[10] == 8); + assert(vec.data[11] == 92); + assert(vec.data[12] == 65); + + sc_vector_destroy(&vec); +} + +static void test_vector_remove_slice(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + for (int i = 0; i < 100; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + assert(vec.size == 100); + + sc_vector_remove_slice(&vec, 32, 60); + assert(vec.size == 40); + assert(vec.data[31] == 31); + assert(vec.data[32] == 92); + + sc_vector_destroy(&vec); +} + +static void test_vector_swap_remove(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + sc_vector_swap_remove(&vec, 1); + assert(vec.size == 4); + assert(vec.data[0] == 3); + assert(vec.data[1] == 65); + assert(vec.data[2] == 15); + assert(vec.data[3] == 92); + + sc_vector_destroy(&vec); +} + +static void test_vector_index_of(void) { + struct SC_VECTOR(int) vec; + sc_vector_init(&vec); + + bool ok; + + for (int i = 0; i < 10; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + ssize_t idx; + + idx = sc_vector_index_of(&vec, 0); + assert(idx == 0); + + idx = sc_vector_index_of(&vec, 1); + assert(idx == 1); + + idx = sc_vector_index_of(&vec, 4); + assert(idx == 4); + + idx = sc_vector_index_of(&vec, 9); + assert(idx == 9); + + idx = sc_vector_index_of(&vec, 12); + assert(idx == -1); + + sc_vector_destroy(&vec); +} + +static void test_vector_grow() { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + for (int i = 0; i < 50; ++i) + { + ok = sc_vector_push(&vec, i); /* append */ + assert(ok); + } + + assert(vec.cap >= 50); + assert(vec.size == 50); + + for (int i = 0; i < 25; ++i) + { + ok = sc_vector_insert(&vec, 20, i); /* insert in the middle */ + assert(ok); + } + + assert(vec.cap >= 75); + assert(vec.size == 75); + + for (int i = 0; i < 25; ++i) + { + ok = sc_vector_insert(&vec, 0, i); /* prepend */ + assert(ok); + } + + assert(vec.cap >= 100); + assert(vec.size == 100); + + for (int i = 0; i < 50; ++i) + sc_vector_remove(&vec, 20); /* remove from the middle */ + + assert(vec.cap >= 50); + assert(vec.size == 50); + + for (int i = 0; i < 25; ++i) + sc_vector_remove(&vec, 0); /* remove from the head */ + + assert(vec.cap >= 25); + assert(vec.size == 25); + + for (int i = 24; i >=0; --i) + sc_vector_remove(&vec, i); /* remove from the tail */ + + assert(vec.size == 0); + + sc_vector_destroy(&vec); +} + +static void test_vector_exp_growth(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + size_t oldcap = vec.cap; + int realloc_count = 0; + bool ok; + for (int i = 0; i < 10000; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + if (vec.cap != oldcap) + { + realloc_count++; + oldcap = vec.cap; + } + } + + /* Test speciically for an expected growth factor of 1.5. In practice, the + * result is even lower (19) due to the first alloc of size 10 */ + assert(realloc_count <= 23); /* ln(10000) / ln(1.5) ~= 23 */ + + realloc_count = 0; + for (int i = 9999; i >= 0; --i) + { + sc_vector_remove(&vec, i); + if (vec.cap != oldcap) + { + realloc_count++; + oldcap = vec.cap; + } + } + + assert(realloc_count <= 23); /* same expectations for removals */ + assert(realloc_count > 0); /* sc_vector_remove() must autoshrink */ + + sc_vector_destroy(&vec); +} + +static void test_vector_reserve(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_reserve(&vec, 800); + assert(ok); + assert(vec.cap >= 800); + assert(vec.size == 0); + + size_t initial_cap = vec.cap; + + for (int i = 0; i < 800; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + assert(vec.cap == initial_cap); /* no realloc */ + } + + sc_vector_destroy(&vec); +} + +static void test_vector_shrink_to_fit(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_reserve(&vec, 800); + assert(ok); + for (int i = 0; i < 250; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + assert(vec.cap >= 800); + assert(vec.size == 250); + + sc_vector_shrink_to_fit(&vec); + assert(vec.cap == 250); + assert(vec.size == 250); + + sc_vector_destroy(&vec); +} + +static void test_vector_move(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 7; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move item at 1 so that its new position is 4 */ + sc_vector_move(&vec, 1, 4); + + assert(vec.size == 7); + assert(vec.data[0] == 0); + assert(vec.data[1] == 2); + assert(vec.data[2] == 3); + assert(vec.data[3] == 4); + assert(vec.data[4] == 1); + assert(vec.data[5] == 5); + assert(vec.data[6] == 6); + + sc_vector_destroy(&vec); +} + +static void test_vector_move_slice_forward(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 10; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move slice {2, 3, 4, 5} so that its new position is 5 */ + sc_vector_move_slice(&vec, 2, 4, 5); + + assert(vec.size == 10); + assert(vec.data[0] == 0); + assert(vec.data[1] == 1); + assert(vec.data[2] == 6); + assert(vec.data[3] == 7); + assert(vec.data[4] == 8); + assert(vec.data[5] == 2); + assert(vec.data[6] == 3); + assert(vec.data[7] == 4); + assert(vec.data[8] == 5); + assert(vec.data[9] == 9); + + sc_vector_destroy(&vec); +} + +static void test_vector_move_slice_backward(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 10; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move slice {5, 6, 7} so that its new position is 2 */ + sc_vector_move_slice(&vec, 5, 3, 2); + + assert(vec.size == 10); + assert(vec.data[0] == 0); + assert(vec.data[1] == 1); + assert(vec.data[2] == 5); + assert(vec.data[3] == 6); + assert(vec.data[4] == 7); + assert(vec.data[5] == 2); + assert(vec.data[6] == 3); + assert(vec.data[7] == 4); + assert(vec.data[8] == 8); + assert(vec.data[9] == 9); + + sc_vector_destroy(&vec); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_vector_insert_remove(); + test_vector_push_array(); + test_vector_insert_array(); + test_vector_remove_slice(); + test_vector_swap_remove(); + test_vector_move(); + test_vector_move_slice_forward(); + test_vector_move_slice_backward(); + test_vector_index_of(); + test_vector_grow(); + test_vector_exp_growth(); + test_vector_reserve(); + test_vector_shrink_to_fit(); + return 0; +} From 1790e88278dee3779073849287f5084c5a275eb6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 19:40:05 +0100 Subject: [PATCH 0475/1133] Use vector for listing USB devices This avoids the hardcoded maximum number of USB devices detected (16). Refs #3029 PR #3035 --- app/src/usb/usb.c | 58 +++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 276b0067..32a66f98 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -3,6 +3,9 @@ #include #include "util/log.h" +#include "util/vector.h" + +struct sc_vec_usb_devices SC_VECTOR(struct sc_usb_device); static char * read_string(libusb_device_handle *handle, uint8_t desc_index) { @@ -85,33 +88,39 @@ sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src) { } void -sc_usb_devices_destroy_all(struct sc_usb_device *usb_devices, size_t count) { - for (size_t i = 0; i < count; ++i) { - sc_usb_device_destroy(&usb_devices[i]); +sc_usb_devices_destroy(struct sc_vec_usb_devices *usb_devices) { + for (size_t i = 0; i < usb_devices->size; ++i) { + sc_usb_device_destroy(&usb_devices->data[i]); } + sc_vector_destroy(usb_devices); } -static ssize_t -sc_usb_list_devices(struct sc_usb *usb, struct sc_usb_device *devices, - size_t len) { +static bool +sc_usb_list_devices(struct sc_usb *usb, struct sc_vec_usb_devices *out_vec) { libusb_device **list; ssize_t count = libusb_get_device_list(usb->context, &list); if (count < 0) { LOGE("List USB devices: libusb error: %s", libusb_strerror(count)); - return -1; + return false; } - size_t idx = 0; - for (size_t i = 0; i < (size_t) count && idx < len; ++i) { + for (size_t i = 0; i < (size_t) count; ++i) { libusb_device *device = list[i]; - if (sc_usb_read_device(device, &devices[idx])) { - ++idx; + struct sc_usb_device usb_device; + if (sc_usb_read_device(device, &usb_device)) { + bool ok = sc_vector_push(out_vec, usb_device); + if (!ok) { + LOG_OOM(); + LOGE("Could not push usb_device to vector"); + sc_usb_device_destroy(&usb_device); + // continue anyway + } } } libusb_free_device_list(list, 1); - return idx; + return true; } static bool @@ -157,29 +166,28 @@ sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices, bool sc_usb_select_device(struct sc_usb *usb, const char *serial, struct sc_usb_device *out_device) { - struct sc_usb_device usb_devices[16]; - ssize_t count = - sc_usb_list_devices(usb, usb_devices, ARRAY_LEN(usb_devices)); - if (count == -1) { + struct sc_vec_usb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_usb_list_devices(usb, &vec); + if (!ok) { LOGE("Could not list USB devices"); return false; } - if (count == 0) { + if (vec.size == 0) { LOGE("Could not find any USB device"); return false; } size_t sel_idx; // index of the single matching device if sel_count == 1 size_t sel_count = - sc_usb_devices_select(usb_devices, count, serial, &sel_idx); + sc_usb_devices_select(vec.data, vec.size, serial, &sel_idx); if (sel_count == 0) { // if count > 0 && sel_count == 0, then necessarily a serial is provided assert(serial); LOGE("Could not find USB device %s", serial); - sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count); - sc_usb_devices_destroy_all(usb_devices, count); + sc_usb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size); + sc_usb_devices_destroy(&vec); return false; } @@ -190,21 +198,21 @@ sc_usb_select_device(struct sc_usb *usb, const char *serial, } else { LOGE("Multiple (%" SC_PRIsizet ") USB devices:", sel_count); } - sc_usb_devices_log(SC_LOG_LEVEL_ERROR, usb_devices, count); + sc_usb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size); LOGE("Select a device via -s (--serial)"); - sc_usb_devices_destroy_all(usb_devices, count); + sc_usb_devices_destroy(&vec); return false; } assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 - struct sc_usb_device *device = &usb_devices[sel_idx]; + struct sc_usb_device *device = &vec.data[sel_idx]; LOGD("USB device found:"); - sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, usb_devices, count); + sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size); // Move device into out_device (do not destroy device) sc_usb_device_move(out_device, device); - sc_usb_devices_destroy_all(usb_devices, count); + sc_usb_devices_destroy(&vec); return true; } From 4b8cb042c41e29002d562e80809a6b3a9f01e49f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 21:16:53 +0100 Subject: [PATCH 0476/1133] Use vector for listing ADB devices This avoids the hardcoded maximum number of ADB devices detected (16). Refs #3029 PR #3035 Co-authored-by: Daniel Ansorregui --- app/src/adb/adb.c | 44 +++++++++---------- app/src/adb/adb_device.c | 7 +-- app/src/adb/adb_device.h | 6 ++- app/src/adb/adb_parser.c | 30 ++++++------- app/src/adb/adb_parser.h | 5 +-- app/tests/test_adb_parser.c | 88 +++++++++++++++++++++---------------- 6 files changed, 95 insertions(+), 85 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 4ddb93f3..c14ded92 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -5,6 +5,7 @@ #include #include +#include "adb_device.h" #include "adb_parser.h" #include "util/file.h" #include "util/log.h" @@ -392,16 +393,16 @@ sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) { return process_check_success_intr(intr, pid, "adb disconnect", flags); } -static ssize_t +static bool sc_adb_list_devices(struct sc_intr *intr, unsigned flags, - struct sc_adb_device *devices, size_t len) { + struct sc_vec_adb_devices *out_vec) { const char *const argv[] = SC_ADB_COMMAND("devices", "-l"); sc_pipe pout; sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb devices -l\""); - return -1; + return false; } char buf[4096]; @@ -410,11 +411,11 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags); if (!ok) { - return -1; + return false; } if (r == -1) { - return -1; + return false; } assert((size_t) r < sizeof(buf)); @@ -423,14 +424,14 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, // in the buffer in a single pass LOGW("Result of \"adb devices -l\" does not fit in 4Kb. " "Please report an issue."); - return -1; + return false; } // It is parsed as a NUL-terminated string buf[r] = '\0'; // List all devices to the output list directly - return sc_adb_parse_devices(buf, devices, len); + return sc_adb_parse_devices(buf, out_vec); } static bool @@ -529,22 +530,21 @@ bool sc_adb_select_device(struct sc_intr *intr, const struct sc_adb_device_selector *selector, unsigned flags, struct sc_adb_device *out_device) { - struct sc_adb_device devices[16]; - ssize_t count = - sc_adb_list_devices(intr, flags, devices, ARRAY_LEN(devices)); - if (count == -1) { + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_list_devices(intr, flags, &vec); + if (!ok) { LOGE("Could not list ADB devices"); return false; } - if (count == 0) { + if (vec.size == 0) { LOGE("Could not find any ADB device"); return false; } size_t sel_idx; // index of the single matching device if sel_count == 1 size_t sel_count = - sc_adb_devices_select(devices, count, selector, &sel_idx); + sc_adb_devices_select(vec.data, vec.size, selector, &sel_idx); if (sel_count == 0) { // if count > 0 && sel_count == 0, then necessarily a selection is @@ -567,8 +567,8 @@ sc_adb_select_device(struct sc_intr *intr, break; } - sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size); + sc_adb_devices_destroy(&vec); return false; } @@ -594,28 +594,28 @@ sc_adb_select_device(struct sc_intr *intr, assert(!"Unexpected selector type"); break; } - sc_adb_devices_log(SC_LOG_LEVEL_ERROR, devices, count); + sc_adb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size); LOGE("Select a device via -s (--serial), -d (--select-usb) or -e " "(--select-tcpip)"); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); return false; } assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 - struct sc_adb_device *device = &devices[sel_idx]; + struct sc_adb_device *device = &vec.data[sel_idx]; - bool ok = sc_adb_device_check_state(device, devices, count); + ok = sc_adb_device_check_state(device, vec.data, vec.size); if (!ok) { - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); return false; } LOGD("ADB device found:"); - sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, devices, count); + sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size); // Move devics into out_device (do not destroy device) sc_adb_device_move(out_device, device); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); return true; } diff --git a/app/src/adb/adb_device.c b/app/src/adb/adb_device.c index b6ff16a7..cfd5dc30 100644 --- a/app/src/adb/adb_device.c +++ b/app/src/adb/adb_device.c @@ -18,9 +18,10 @@ sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) { } void -sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count) { - for (size_t i = 0; i < count; ++i) { - sc_adb_device_destroy(&devices[i]); +sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) { + for (size_t i = 0; i < devices->size; ++i) { + sc_adb_device_destroy(&devices->data[i]); } + sc_vector_destroy(devices); } diff --git a/app/src/adb/adb_device.h b/app/src/adb/adb_device.h index ed8362e9..14d0408c 100644 --- a/app/src/adb/adb_device.h +++ b/app/src/adb/adb_device.h @@ -6,6 +6,8 @@ #include #include +#include "util/vector.h" + struct sc_adb_device { char *serial; char *state; @@ -13,6 +15,8 @@ struct sc_adb_device { bool selected; }; +struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device); + void sc_adb_device_destroy(struct sc_adb_device *device); @@ -29,6 +33,6 @@ void sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src); void -sc_adb_devices_destroy_all(struct sc_adb_device *devices, size_t count); +sc_adb_devices_destroy(struct sc_vec_adb_devices *devices); #endif diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index 85e8ffaf..933eafbb 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -109,11 +109,8 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) { return true; } -ssize_t -sc_adb_parse_devices(char *str, struct sc_adb_device *devices, - size_t devices_len) { - size_t dev_count = 0; - +bool +sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec) { #define HEADER "List of devices attached" #define HEADER_LEN (sizeof(HEADER) - 1) bool header_found = false; @@ -144,25 +141,24 @@ sc_adb_parse_devices(char *str, struct sc_adb_device *devices, size_t line_len = sc_str_remove_trailing_cr(line, len); line[line_len] = '\0'; - bool ok = sc_adb_parse_device(line, &devices[dev_count]); + struct sc_adb_device device; + bool ok = sc_adb_parse_device(line, &device); if (!ok) { continue; } - ++dev_count; - - assert(dev_count <= devices_len); - if (dev_count == devices_len) { - // Max number of devices reached - break; + ok = sc_vector_push(out_vec, device); + if (!ok) { + LOG_OOM(); + LOGE("Could not push adb_device to vector"); + sc_adb_device_destroy(&device); + // continue anyway + continue; } } - if (!header_found) { - return -1; - } - - return dev_count; + assert(header_found || out_vec->size == 0); + return header_found; } static char * diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index 65493a2e..e0cc389b 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -14,9 +14,8 @@ * * Warning: this function modifies the buffer for optimization purposes. */ -ssize_t -sc_adb_parse_devices(char *str, struct sc_adb_device *devices, - size_t devices_len); +bool +sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec); /** * Parse the ip from the output of `adb shell ip route` diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index c4b18a8d..1a127632 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -13,21 +13,22 @@ static void test_adb_devices() { "192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel " "device:MyWifiDevice trandport_id:2\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 2); + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 2); - struct sc_adb_device *device = &devices[0]; + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyModel", device->model)); - device = &devices[1]; + device = &vec.data[1]; assert(!strcmp("192.168.1.1:5555", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyWifiModel", device->model)); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); } static void test_adb_devices_cr() { @@ -38,21 +39,22 @@ static void test_adb_devices_cr() { "192.168.1.1:5555 device product:MyWifiProduct model:MyWifiModel " "device:MyWifiDevice trandport_id:2\r\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 2); + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 2); - struct sc_adb_device *device = &devices[0]; + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyModel", device->model)); - device = &devices[1]; + device = &vec.data[1]; assert(!strcmp("192.168.1.1:5555", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyWifiModel", device->model)); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); } static void test_adb_devices_daemon_start() { @@ -63,16 +65,17 @@ static void test_adb_devices_daemon_start() { "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "device:MyDevice transport_id:1\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 1); + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 1); - struct sc_adb_device *device = &devices[0]; + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyModel", device->model)); - sc_adb_device_destroy(device); + sc_adb_devices_destroy(&vec); } static void test_adb_devices_daemon_start_mixed() { @@ -84,21 +87,22 @@ static void test_adb_devices_daemon_start_mixed() { "87654321 device usb:2-1 product:MyProduct model:MyModel " "device:MyDevice\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 2); + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 2); - struct sc_adb_device *device = &devices[0]; + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("unauthorized", device->state)); assert(!device->model); - device = &devices[1]; + device = &vec.data[1]; assert(!strcmp("87654321", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyModel", device->model)); - sc_adb_devices_destroy_all(devices, count); + sc_adb_devices_destroy(&vec); } static void test_adb_devices_without_eol() { @@ -106,34 +110,39 @@ static void test_adb_devices_without_eol() { "List of devices attached\n" "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "device:MyDevice transport_id:1"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 1); - struct sc_adb_device *device = &devices[0]; + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 1); + + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("device", device->state)); assert(!strcmp("MyModel", device->model)); - sc_adb_device_destroy(device); + sc_adb_devices_destroy(&vec); } static void test_adb_devices_without_header() { char output[] = "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "device:MyDevice transport_id:1\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == -1); + + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(!ok); } static void test_adb_devices_corrupted() { char output[] = "List of devices attached\n" "corrupted_garbage\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 0); + + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 0); } static void test_adb_devices_spaces() { @@ -141,16 +150,17 @@ static void test_adb_devices_spaces() { "List of devices attached\n" "0123456789abcdef unauthorized usb:1-4 transport_id:3\n"; - struct sc_adb_device devices[16]; - ssize_t count = sc_adb_parse_devices(output, devices, ARRAY_LEN(devices)); - assert(count == 1); + struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER; + bool ok = sc_adb_parse_devices(output, &vec); + assert(ok); + assert(vec.size == 1); - struct sc_adb_device *device = &devices[0]; + struct sc_adb_device *device = &vec.data[0]; assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("unauthorized", device->state)); assert(!device->model); - sc_adb_device_destroy(device); + sc_adb_devices_destroy(&vec); } static void test_get_ip_single_line() { From e2e76c5d4858530db691a4d8c2bb7a97eaebdfb3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 21:33:20 +0100 Subject: [PATCH 0477/1133] Increase `adb devices -l` max output size For simplicity, the parsing of `adb devices -l` output is performed in a single pass on the whole output. This output was limited to 4096 bytes. Since there are about 100 chars per device line, this limited the number of connected devices to ~40. Increase to 65536 bytes to avoid a limitation in practice. PR #3035 --- app/src/adb/adb.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index c14ded92..06090b46 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -398,31 +398,39 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, struct sc_vec_adb_devices *out_vec) { const char *const argv[] = SC_ADB_COMMAND("devices", "-l"); +#define BUFSIZE 65536 + char *buf = malloc(BUFSIZE); + if (!buf) { + return false; + } + sc_pipe pout; sc_pid pid = sc_adb_execute_p(argv, flags, &pout); if (pid == SC_PROCESS_NONE) { LOGE("Could not execute \"adb devices -l\""); + free(buf); return false; } - char buf[4096]; - ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, BUFSIZE - 1); sc_pipe_close(pout); bool ok = process_check_success_intr(intr, pid, "adb devices -l", flags); if (!ok) { + free(buf); return false; } if (r == -1) { + free(buf); return false; } - assert((size_t) r < sizeof(buf)); - if (r == sizeof(buf) - 1) { + assert((size_t) r < BUFSIZE); + if (r == BUFSIZE - 1) { // The implementation assumes that the output of "adb devices -l" fits // in the buffer in a single pass - LOGW("Result of \"adb devices -l\" does not fit in 4Kb. " + LOGW("Result of \"adb devices -l\" does not fit in 64Kb. " "Please report an issue."); return false; } @@ -431,7 +439,9 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, buf[r] = '\0'; // List all devices to the output list directly - return sc_adb_parse_devices(buf, out_vec); + ok = sc_adb_parse_devices(buf, out_vec); + free(buf); + return ok; } static bool From 71b41d846fdcae475e3e735adea8491a2663bbc4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Feb 2022 23:37:14 +0100 Subject: [PATCH 0478/1133] Also retry on IllegalArgumentException MediaCodec.configure() may throw an IllegalArgumentException if it does not support the requested size. Also retry on this exception. Fixes #2993 Refs #2947 Refs #2990 PR #3043 --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f97206ec..e95896d3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -102,7 +102,7 @@ public class ScreenEncoder implements Device.RotationListener { alive = encode(codec, fd); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); - } catch (IllegalStateException e) { + } catch (IllegalStateException | IllegalArgumentException e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); if (!downsizeOnError || firstFrameSent) { // Fail immediately From 79ed83ab687a848efaddbfbfd195d4252158fe8d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 18:10:30 +0100 Subject: [PATCH 0479/1133] Reorder --tcpip option in cli To keep options in alphabetic order. --- app/src/cli.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 48289678..a4f79840 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -422,6 +422,20 @@ static const struct sc_option options[] = { "on exit.\n" "It only shows physical touches (not clicks from scrcpy).", }, + { + .longopt_id = OPT_TCPIP, + .longopt = "tcpip", + .argdesc = "ip[:port]", + .optional_arg = true, + .text = "Configure and reconnect the device over TCP/IP.\n" + "If a destination address is provided, then scrcpy connects to " + "this address before starting. The device must listen on the " + "given TCP port (default is 5555).\n" + "If no destination address is provided, then scrcpy attempts " + "to find the IP address of the current device (typically " + "connected over USB), enables TCP/IP mode, then connects to " + "this address before starting.", + }, { .longopt_id = OPT_TUNNEL_HOST, .longopt = "tunnel-host", @@ -483,20 +497,6 @@ static const struct sc_option options[] = { .text = "Keep the device on while scrcpy is running, when the device " "is plugged in.", }, - { - .longopt_id = OPT_TCPIP, - .longopt = "tcpip", - .argdesc = "ip[:port]", - .optional_arg = true, - .text = "Configure and reconnect the device over TCP/IP.\n" - "If a destination address is provided, then scrcpy connects to " - "this address before starting. The device must listen on the " - "given TCP port (default is 5555).\n" - "If no destination address is provided, then scrcpy attempts " - "to find the IP address of the current device (typically " - "connected over USB), enables TCP/IP mode, then connects to " - "this address before starting.", - }, { .longopt_id = OPT_WINDOW_BORDERLESS, .longopt = "window-borderless", From 3e0df6ad0586bc435fc1571e8d7fa3953c900f04 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 18:23:42 +0100 Subject: [PATCH 0480/1133] Update HID/OTG features in README HID/OTG features are not limited to Linux anymore. Refs 82a99f69ec464a0637a16bdccfe5ff806777e942 --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1139092f..3bafd53d 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,8 @@ Its features include: - [configurable quality](#capture-configuration) - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) - (Linux-only) - [physical mouse simulation (HID)](#physical-mouse-simulation-hid) - (Linux-only) - - [OTG mode](#otg) (Linux-only) + - [OTG mode](#otg) - and more… ## Requirements @@ -807,14 +805,17 @@ a location inverted through the center of the screen. By default, scrcpy uses Android key or text injection: it works everywhere, but is limited to ASCII. -On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a -better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual +Alternatively, scrcpy can simulate a physical USB 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. +However, it only works if the device is connected by USB. + +Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it +is not possible to open a USB device if it is already open by another process +like the adb daemon). To enable this mode: @@ -847,8 +848,7 @@ a physical keyboard is connected). #### Physical mouse simulation (HID) Similarly to the physical keyboard simulation, it is possible to simulate a -physical mouse. Likewise, it only works if the device is connected by USB, and -is currently only supported on Linux. +physical mouse. Likewise, it only works if the device is connected by USB. By default, scrcpy uses Android mouse events injection, using absolute coordinates. By simulating a physical mouse, a mouse pointer appears on the @@ -901,7 +901,7 @@ scrcpy --otg # keyboard and mouse ``` Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is -connected by USB, and is currently only supported on Linux. +connected by USB. #### Text injection preference From 90816291d499ea0bc759f7d10e3d65d6d4313d81 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0481/1133] Add an explicit first step in wireless section Mention that the device must be plugged via USB before configuring TCP/IP connections. It wasn't obvious that the device should be first plugged before running scrcpy wirelessly, especially to those who aren't very familiar with adb. Note from committer: add this new step with index 0 to make the diff readable, the next commit will renumber all the steps. PR #1303 Signed-off-by: Romain Vimont --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3bafd53d..28d14dfd 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,7 @@ connect to the device before starting. Alternatively, it is possible to enable the TCP/IP connection manually using `adb`: +0. Plug the device into a USB port on your computer. 1. Connect the device to the same Wi-Fi as your computer. 2. Get your device IP address, in Settings → About phone → Status, or by executing this command: From 78b18b7cee8b0f31ce979d3ddf180370132c4703 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0482/1133] Renumber steps in wireless section The actual numbers are ignored by markdown, but start at 1 for consistency. PR #1303 Signed-off-by: Romain Vimont --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 28d14dfd..b3f78596 100644 --- a/README.md +++ b/README.md @@ -404,19 +404,19 @@ connect to the device before starting. Alternatively, it is possible to enable the TCP/IP connection manually using `adb`: -0. Plug the device into a USB port on your computer. -1. Connect the device to the same Wi-Fi as your computer. -2. Get your device IP address, in Settings → About phone → Status, or by +1. Plug the device into a USB port on your computer. +2. Connect the device to the same Wi-Fi as your computer. +3. Get your device IP address, in Settings → About phone → Status, or by executing this command: ```bash adb shell ip route | awk '{print $9}' ``` -3. Enable adb over TCP/IP on your device: `adb tcpip 5555`. -4. Unplug your device. -5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_. -6. Run `scrcpy` as usual. +4. Enable adb over TCP/IP on your device: `adb tcpip 5555`. +5. Unplug your device. +6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_. +7. Run `scrcpy` as usual. It may be useful to decrease the bit-rate and the definition: From 528275d5011189539af1fe7a493755002c5bbbf0 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0483/1133] Improve phrasing in wireless section PR #1303 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3f78596..740175e0 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ Alternatively, it is possible to enable the TCP/IP connection manually using `adb`: 1. Plug the device into a USB port on your computer. -2. Connect the device to the same Wi-Fi as your computer. +2. Connect the device to the same Wi-Fi network as your computer. 3. Get your device IP address, in Settings → About phone → Status, or by executing this command: From 8610e9a454048b710eb84b45ce197d8d8b8e5382 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0484/1133] Add troubleshooting in wireless section Add a small troubleshooting section since wireless might add some complexity, and to lessen incoming relevant issue posts. PR #1303 Signed-off-by: Romain Vimont --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 740175e0..99cf8996 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,11 @@ Alternatively, it is possible to enable the TCP/IP connection manually using 6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_. 7. Run `scrcpy` as usual. +If the connection randomly drops, run your `scrcpy` command to reconnect. If it +says there are no devices/emulators found, try running `adb connect +DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are +none found, try running `adb disconnect` and then run those two commands again. + It may be useful to decrease the bit-rate and the definition: ```bash From 16937972770df9ae20f8e5ae5579df15d1b00e57 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0485/1133] Make step more explicit in wireless section PR #1303 Signed-off-by: Romain Vimont --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99cf8996..b78acadc 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,8 @@ Alternatively, it is possible to enable the TCP/IP connection manually using 4. Enable adb over TCP/IP on your device: `adb tcpip 5555`. 5. Unplug your device. -6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_. +6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` +with the device IP address you found)_. 7. Run `scrcpy` as usual. If the connection randomly drops, run your `scrcpy` command to reconnect. If it From ff8a69d8eccac57b53dbebf001d1dce2245ab9b7 Mon Sep 17 00:00:00 2001 From: Flying Press Date: Mon, 20 Apr 2020 04:59:54 -0500 Subject: [PATCH 0486/1133] Mention adb wireless option for Android 11+ Add a paragraph about toggling an option to bypass having to physically connect the device to the user's computer. PR #1303 Signed-off-by: Romain Vimont --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b78acadc..57e2f8c3 100644 --- a/README.md +++ b/README.md @@ -419,6 +419,11 @@ Alternatively, it is possible to enable the TCP/IP connection manually using with the device IP address you found)_. 7. Run `scrcpy` as usual. +Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass +having to physically connect your device directly to your computer. + +[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ + If the connection randomly drops, run your `scrcpy` command to reconnect. If it says there are no devices/emulators found, try running `adb connect DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are From 6a9b2f2c36fe48edd451ca019523bff61914e499 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 18:31:37 +0100 Subject: [PATCH 0487/1133] Remove spurious empty line --- server/src/main/java/com/genymobile/scrcpy/Server.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d7e522c7..60f485d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -11,7 +11,6 @@ import java.util.Locale; public final class Server { - private Server() { // not instantiable } From 1f951f1a3a85377fbcecc6c10aba462517fdab9e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 19:08:15 +0100 Subject: [PATCH 0488/1133] Update FAQ to match the latest version --- FAQ.md | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/FAQ.md b/FAQ.md index 5829136d..9f22b8ba 100644 --- a/FAQ.md +++ b/FAQ.md @@ -4,23 +4,16 @@ Here are the common reported problems and their status. +If you encounter any error, the first step is to upgrade to the latest version. + ## `adb` issues `scrcpy` execute `adb` commands to initialize the connection with the device. If `adb` fails, then scrcpy will not work. -In that case, it will print this error: - -> ERROR: "adb get-serialno" returned with value 1 - This is typically not a bug in _scrcpy_, but a problem in your environment. -To find out the cause, execute: - -```bash -adb devices -``` ### `adb` not found @@ -32,11 +25,9 @@ in the release, so it should work out-of-the-box. ### Device unauthorized - -> error: device unauthorized. -> This adb server's $ADB_VENDOR_KEYS is not set -> Try 'adb kill-server' if that seems wrong. -> Otherwise check for a confirmation dialog on your device. +> ERROR: Device is unauthorized: +> ERROR: --> (usb) 0123456789abcdef unauthorized +> ERROR: A popup should open on the device to request authorization. When connecting, a popup should open on the device. You must authorize USB debugging. @@ -48,10 +39,16 @@ If it does not open, check [stackoverflow][device-unauthorized]. ### Device not detected -> error: no devices/emulators found +> ERROR: Could not find any ADB device Check that you correctly enabled [adb debugging][enable-adb]. +Your device must be detected by `adb`: + +``` +adb devices +``` + If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver]. [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling @@ -63,12 +60,23 @@ If your device is not detected, you may need some [drivers] (on Windows). There If several devices are connected, you will encounter this error: -> error: more than one device/emulator +ERROR: Multiple (2) ADB devices: +ERROR: --> (usb) 0123456789abcdef device Nexus_5 +ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913 +ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip) + +In that case, you can either provide the identifier of the device you want to +mirror: + +```bash +scrcpy -s 0123456789abcdef +``` -the identifier of the device you want to mirror must be provided: +Or request the single USB (or TCP/IP) device: ```bash -scrcpy -s 01234567890abcdef +scrcpy -d # USB device +scrcpy -e # TCP/IP device ``` Note that if your device is connected over TCP/IP, you might get this message: From 2716385887291d205716be85a0f209610ab04d9c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 19:12:02 +0100 Subject: [PATCH 0489/1133] Move "Device unauthorized" in FAQ Put "Device not detected" first. --- FAQ.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/FAQ.md b/FAQ.md index 9f22b8ba..4058e8fe 100644 --- a/FAQ.md +++ b/FAQ.md @@ -23,20 +23,6 @@ On Windows, the current directory is in your `PATH`, and `adb.exe` is included in the release, so it should work out-of-the-box. -### Device unauthorized - -> ERROR: Device is unauthorized: -> ERROR: --> (usb) 0123456789abcdef unauthorized -> ERROR: A popup should open on the device to request authorization. - -When connecting, a popup should open on the device. You must authorize USB -debugging. - -If it does not open, check [stackoverflow][device-unauthorized]. - -[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized - - ### Device not detected > ERROR: Could not find any ADB device @@ -56,6 +42,20 @@ If your device is not detected, you may need some [drivers] (on Windows). There [google-usb-driver]: https://developer.android.com/studio/run/win-usb +### Device unauthorized + +> ERROR: Device is unauthorized: +> ERROR: --> (usb) 0123456789abcdef unauthorized +> ERROR: A popup should open on the device to request authorization. + +When connecting, a popup should open on the device. You must authorize USB +debugging. + +If it does not open, check [stackoverflow][device-unauthorized]. + +[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized + + ### Several devices connected If several devices are connected, you will encounter this error: From 26953784d96c14f66e8b55ec2a1e7a13167ce547 Mon Sep 17 00:00:00 2001 From: hltdev8642 Date: Thu, 10 Feb 2022 09:37:02 -0500 Subject: [PATCH 0490/1133] Add ZSH completion script PR #3012 Signed-off-by: Romain Vimont --- app/data/zsh-completion/_scrcpy | 69 +++++++++++++++++++++++++++++++++ app/meson.build | 2 + 2 files changed, 71 insertions(+) create mode 100644 app/data/zsh-completion/_scrcpy diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy new file mode 100644 index 00000000..77e6027a --- /dev/null +++ b/app/data/zsh-completion/_scrcpy @@ -0,0 +1,69 @@ +#compdef -N scrcpy -N scrcpy.exe +# +# name: scrcpy +# auth: hltdev [hltdev8642@gmail.com] +# desc: completion file for scrcpy (all OSes) +# + +local arguments + +arguments=( + '--always-on-top[Make scrcpy window always on top \(above other windows\)]' + {-b,--bit-rate=}'[Encode the video at the given bit-rate]' + '--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]' + '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' + {-d,--select-usb}'[Use USB device]' + '--disable-screensaver[Disable screensaver while scrcpy is running]' + '--display=[Specify the display id to mirror]' + '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' + {-e,--select-tcpip}'[Use TCP/IP device]' + '--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]' + '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' + '--forward-all-clicks[Forward clicks to device]' + {-f,--fullscreen}'[Start in fullscreen]' + {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' + {-h,--help}'[Print the help]' + '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' + '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' + '--max-fps=[Limit the frame rate of screen capture]' + {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' + {-m,--max-size=}'[Limit both the width and height of the video to value]' + '--no-cleanup[Disable device cleanup actions on exit]' + '--no-clipboard-autosync[Disable automatic clipboard synchronization]' + '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' + {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' + {-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]' + '--no-key-repeat[Do not forward repeated key events when a key is held down]' + '--no-mipmaps[Disable the generation of mipmaps]' + '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' + {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' + '--power-off-on-close[Turn the device screen off when closing scrcpy]' + '--prefer-text[Inject alpha characters and space as text events instead of key events]' + '--print-fps[Start FPS counter, to print frame logs to the console]' + '--push-target=[Set the target directory for pushing files to the device by drag and drop]' + '--raw-key-events[Inject key events for all input keys, and ignore text events]' + {-r,--record=}'[Record screen to file]:record file:_files' + '--record-format=[Force recording format]:format:(mp4 mkv)' + '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' + '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' + {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]' + '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' + {-S,--turn-screen-off}'[Turn the device screen off immediately]' + {-t,--show-touches}'[Show physical touches]' + '--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]' + '--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]' + '--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]' + '--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]' + '--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]' + {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' + {-v,--version}'[Print the version of scrcpy]' + {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' + '--window-borderless[Disable window decorations \(display borderless window\)]' + '--window-title=[Set a custom window title]' + '--window-x=[Set the initial window horizontal position]' + '--window-y=[Set the initial window vertical position]' + '--window-width=[Set the initial window width]' + '--window-height=[Set the initial window height]' +) + +_arguments -s $arguments diff --git a/app/meson.build b/app/meson.build index 6255bcbc..95a92da2 100644 --- a/app/meson.build +++ b/app/meson.build @@ -227,6 +227,8 @@ install_man('scrcpy.1') install_data('data/icon.png', rename: 'scrcpy.png', install_dir: 'share/icons/hicolor/256x256/apps') +install_data('data/zsh-completion/_scrcpy', + install_dir: 'share/zsh/site-functions') ### TESTS From 3ce6f8ca91bd28e980943f9346ce6efcbee145b8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 21 Feb 2022 22:38:53 +0100 Subject: [PATCH 0491/1133] Add Bash completion script Fixes #2930 Refs #3012 --- app/data/bash-completion/scrcpy | 121 ++++++++++++++++++++++++++++++++ app/meson.build | 2 + 2 files changed, 123 insertions(+) create mode 100644 app/data/bash-completion/scrcpy diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy new file mode 100644 index 00000000..464bc532 --- /dev/null +++ b/app/data/bash-completion/scrcpy @@ -0,0 +1,121 @@ +_scrcpy() { + local cur prev words cword + local opts=" + --always-on-top + -b --bit-rate= + --codec-options= + --crop= + -d --select-usb + --disable-screensaver + --display= + --display-buffer= + -e --select-tcpip + --encoder= + --force-adb-forward + --forward-all-clicks + -f --fullscreen + -K --hid-keyboard + -h --help + --legacy-paste + --lock-video-orientation + --lock-video-orientation= + --max-fps= + -M --hid-mouse + -m --max-size= + --no-cleanup + --no-clipboard-on-error + --no-downsize-on-error + -n --no-control + -N --no-display + --no-key-repeat + --no-mipmaps + --otg + -p --port= + --power-off-on-close + --prefer-text + --print-fps + --push-target= + --raw-key-events + -r --record= + --record-format= + --render-driver= + --rotation= + -s --serial= + --shortcut-mod= + -S --turn-screen-off + -t --show-touches + --tcpip + --tcpip= + --tunnel-host= + --tunnel-port= + --v4l2-buffer= + --v4l2-sink= + -V --verbosity= + -v --version + -w --stay-awake + --window-borderless + --window-title= + --window-x= + --window-y= + --window-width= + --window-height=" + + _init_completion -s || return + + case "$prev" in + --lock-video-orientation) + COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) + return + ;; + -r|--record) + COMPREPLY=($(compgen -f -- "$cur")) + return + ;; + --record-format) + COMPREPLY=($(compgen -W 'mkv mp4' -- "$cur")) + return + ;; + --render-driver) + COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur")) + return + ;; + --rotation) + COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur")) + return + ;; + --shortcut-mod) + # Only auto-complete a single key + COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur")) + return + ;; + -V|--verbosity) + COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur")) + return + ;; + -b|--bitrate \ + |--codec-options \ + |--crop \ + |--display \ + |--display-buffer \ + |--encoder \ + |--max-fps \ + |-m|--max-size \ + |-p|--port \ + |--push-target \ + |-s|--serial \ + |--tunnel-host \ + |--tunnel-port \ + |--v4l2-buffer \ + |--v4l2-sink \ + |--tcpip \ + |--window-*) + # Option accepting an argument, but nothing to auto-complete + return + ;; + esac + + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + [[ $COMPREPLY == *= ]] && compopt -o nospace +} + +complete -F _scrcpy scrcpy diff --git a/app/meson.build b/app/meson.build index 95a92da2..e34b1893 100644 --- a/app/meson.build +++ b/app/meson.build @@ -229,6 +229,8 @@ install_data('data/icon.png', install_dir: 'share/icons/hicolor/256x256/apps') install_data('data/zsh-completion/_scrcpy', install_dir: 'share/zsh/site-functions') +install_data('data/bash-completion/scrcpy', + install_dir: 'share/bash-completion/completions') ### TESTS From 4ab4548769cb3d27052f366f5c1755895b10c375 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 19:31:35 +0100 Subject: [PATCH 0492/1133] Add contact links to the README Add Reddit and Twitter links (and an additional link to the GitHub issues). --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 57e2f8c3..a3df2314 100644 --- a/README.md +++ b/README.md @@ -1105,7 +1105,9 @@ See [BUILD]. ## Common issues -See the [FAQ](FAQ.md). +See the [FAQ].md). + +[FAQ]: FAQ.md ## Developers @@ -1140,6 +1142,17 @@ Read the [developers page]. [article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ [article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ +## Contact + +If you encounter a bug, please read the [FAQ] first, then open an [issue]. + +[issue]: https://github.com/Genymobile/scrcpy/issues + +For general questions or discussions, you could also use: + + - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) + - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) + ## Translations This README is available in other languages: From 71ef5cc0a9936f3a1f0f6e1809135953ca74bc98 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 21:00:43 +0100 Subject: [PATCH 0493/1133] Add missing include for vector Include stdlib.h for realloc(). --- app/src/util/vector.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/util/vector.h b/app/src/util/vector.h index a08fa9d6..2c03a430 100644 --- a/app/src/util/vector.h +++ b/app/src/util/vector.h @@ -5,6 +5,7 @@ #include #include +#include // Adapted from vlc_vector: // From 7deccef1c2f609870fae00a3a1eb11d0eb2a28a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 21:01:55 +0100 Subject: [PATCH 0494/1133] Bump version to 1.23 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index dbd6aedb..41101e70 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "1.22" + VALUE "ProductVersion", "1.23" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index b4b59353..e7270b5a 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.22', + version: '1.23', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index f262b02d..f3eb6d64 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 12200 - versionName "1.22" + versionCode 12300 + versionName "1.23" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index ecf7b604..b2bafa3f 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.22 +SCRCPY_VERSION_NAME=1.23 PLATFORM=${ANDROID_PLATFORM:-31} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} From 49434da36ea9bd52907609c08c0b644c2d0d6649 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Feb 2022 23:45:43 +0100 Subject: [PATCH 0495/1133] Update links to v1.23 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index 60be752d..90a7d18e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -272,10 +272,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.22`][direct-scrcpy-server] - _(SHA-256: c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b)_ + - [`scrcpy-server-v1.23`][direct-scrcpy-server] + _(SHA-256: 2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 4bf075d8..23a28d1e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.22) +# scrcpy (v1.23) scrcpy @@ -106,10 +106,10 @@ process][BUILD_simple]). For Windows, for simplicity, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.22.zip`][direct-win64] - _(SHA-256: ce4d9b8cc761e29862c4a72d8ad6f538bdd1f1831d15fd1f36633cd3b403db82)_ + - [`scrcpy-win64-v1.23.zip`][direct-win64] + _(SHA-256: d2f601b1d0157faf65153d8a093d827fd65aec5d5842d677ac86fb2b5b7704cc)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-win64-v1.22.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-win64-v1.23.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index 6dd71d25..9b1ed8e7 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22 -PREBUILT_SERVER_SHA256=c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 +PREBUILT_SERVER_SHA256=2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From adbe7908c6e05899d8c4a03cd5cbc10209e3d221 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 23 Feb 2022 01:21:18 +0100 Subject: [PATCH 0496/1133] Fix icon path in README The data/ directory was moved to app/data/. Refs 36c75e15b8e9eeb01bd287a42fa3f1513a728ebb --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23a28d1e..c89b0c8d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # scrcpy (v1.23) -scrcpy +scrcpy _pronounced "**scr**een **c**o**py**"_ From e4bb2b8728168aebb942bfdeabbba30a29f6b405 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Feb 2022 23:25:02 +0100 Subject: [PATCH 0497/1133] Add libusb error log Log libusb_get_string_descriptor_ascii() errors. Refs #3050 --- app/src/usb/usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 32a66f98..97aa9a33 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -15,6 +15,7 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) { (unsigned char *) buffer, sizeof(buffer)); if (result < 0) { + LOGD("Read string: libusb error: %s", libusb_strerror(result)); return NULL; } From 59656fe649d8fd46062fd130e41921b53d517bca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Feb 2022 23:26:12 +0100 Subject: [PATCH 0498/1133] Fix typo in error message --- app/src/usb/hid_keyboard.c | 4 ++-- app/src/usb/hid_mouse.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index 4a0e64ba..dc88944c 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -340,7 +340,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could request HID event"); + LOGW("Could not request HID event"); return false; } @@ -382,7 +382,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could request HID event"); + LOGW("Could not request HID event"); } } } diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index c24b4a4a..dadab586 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -181,7 +181,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could request HID event"); + LOGW("Could not request HID event"); } } @@ -203,7 +203,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could request HID event"); + LOGW("Could not request HID event"); } } @@ -228,7 +228,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could request HID event"); + LOGW("Could not request HID event"); } } From 8d91cda4f61b710caee719609ed9a8c043cb41c8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Feb 2022 23:27:39 +0100 Subject: [PATCH 0499/1133] Improve HID event push error message On HID event push failure, add the event type in the error message. --- app/src/usb/hid_keyboard.c | 4 ++-- app/src/usb/hid_mouse.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index dc88944c..a12fbf3b 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -340,7 +340,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could not request HID event"); + LOGW("Could not request HID event (mod lock state)"); return false; } @@ -382,7 +382,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could not request HID event"); + LOGW("Could not request HID event (key)"); } } } diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index dadab586..bab89940 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -181,7 +181,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could not request HID event"); + LOGW("Could not request HID event (mouse motion)"); } } @@ -203,7 +203,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could not request HID event"); + LOGW("Could not request HID event (mouse click)"); } } @@ -228,7 +228,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); - LOGW("Could not request HID event"); + LOGW("Could not request HID event (mouse scroll)"); } } From 1f4c801f3c4b2b46d001f705abb921c3f3c8a212 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 6 Mar 2022 22:02:46 +0100 Subject: [PATCH 0500/1133] Report server connection state We must distinguish 3 cases for await_for_server(): - an error occurred - no error occurred, the device is connected - no error occurred, the device is not connected (user requested to quit) For this purpose, use an additional output parameter to indicate if the device is connected (only set when no error occurs). Refs #3085 --- app/src/scrcpy.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3ed1d249..8c549a4e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -168,19 +168,22 @@ event_loop(struct scrcpy *s) { return false; } +// Return true on success, false on error static bool -await_for_server(void) { +await_for_server(bool *connected) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { case SDL_QUIT: LOGD("User requested to quit"); - return false; + *connected = false; + return true; case EVENT_SERVER_CONNECTION_FAILED: LOGE("Server connection failed"); return false; case EVENT_SERVER_CONNECTED: LOGD("Server connected"); + *connected = true; return true; default: break; @@ -351,7 +354,14 @@ scrcpy(struct scrcpy_options *options) { sdl_configure(options->display, options->disable_screensaver); // Await for server without blocking Ctrl+C handling - if (!await_for_server()) { + bool connected; + if (!await_for_server(&connected)) { + goto end; + } + + if (!connected) { + // This is not an error, user requested to quit + ret = true; goto end; } From b3f5dfe1de03ff8e19225aee08dcbd62d6089d56 Mon Sep 17 00:00:00 2001 From: "martin f. krafft" Date: Sat, 5 Mar 2022 15:47:58 +0100 Subject: [PATCH 0501/1133] Add specific exit code for device disconnection Modify the return logic such that exit code 1 is used when the initial connection fails, but if a session is established, and then the device disconnects, exit code 2 is emitted. Fixes #3083 PR #3085 Signed-off-by: martin f. krafft Signed-off-by: Romain Vimont --- app/scrcpy.1 | 6 ++++++ app/src/main.c | 16 ++++++++-------- app/src/scrcpy.c | 18 +++++++++--------- app/src/scrcpy.h | 13 ++++++++++++- app/src/usb/scrcpy_otg.c | 14 +++++++------- app/src/usb/scrcpy_otg.h | 4 ++-- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index f9d4ba24..f1ee732d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -355,6 +355,12 @@ Set the initial window height. Default is 0 (automatic). +.SH EXIT STATUS +.B scrcpy +will exit with code 0 on normal program termination. If an initial +connection cannot be established, the exit code 1 will be returned. If the +device disconnects while a session is active, exit code 2 will be returned. + .SH SHORTCUTS In the following list, MOD is the shortcut modifier. By default, it's (left) diff --git a/app/src/main.c b/app/src/main.c index 2d1575f8..3334cbf9 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -40,19 +40,19 @@ main(int argc, char *argv[]) { #endif if (!scrcpy_parse_args(&args, argc, argv)) { - return 1; + return SCRCPY_EXIT_FAILURE; } sc_set_log_level(args.opts.log_level); if (args.help) { scrcpy_print_usage(argv[0]); - return 0; + return SCRCPY_EXIT_SUCCESS; } if (args.version) { scrcpy_print_version(); - return 0; + return SCRCPY_EXIT_SUCCESS; } #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL @@ -66,17 +66,17 @@ main(int argc, char *argv[]) { #endif if (avformat_network_init()) { - return 1; + return SCRCPY_EXIT_FAILURE; } #ifdef HAVE_USB - bool ok = args.opts.otg ? scrcpy_otg(&args.opts) - : scrcpy(&args.opts); + enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts) + : scrcpy(&args.opts); #else - bool ok = scrcpy(&args.opts); + enum scrcpy_exit_code ret = scrcpy(&args.opts); #endif avformat_network_deinit(); // ignore failure - return ok ? 0 : 1; + return ret; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8c549a4e..8fbfe394 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -149,23 +149,23 @@ sdl_configure(bool display, bool disable_screensaver) { } } -static bool +static enum scrcpy_exit_code event_loop(struct scrcpy *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { case EVENT_STREAM_STOPPED: LOGW("Device disconnected"); - return false; + return SCRCPY_EXIT_DISCONNECTED; case SDL_QUIT: LOGD("User requested to quit"); - return true; + return SCRCPY_EXIT_SUCCESS; default: sc_screen_handle_event(&s->screen, &event); break; } } - return false; + return SCRCPY_EXIT_FAILURE; } // Return true on success, false on error @@ -265,7 +265,7 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) { // event } -bool +enum scrcpy_exit_code scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; struct scrcpy *s = &scrcpy; @@ -273,12 +273,12 @@ scrcpy(struct scrcpy_options *options) { // Minimal SDL initialization if (SDL_Init(SDL_INIT_EVENTS)) { LOGE("Could not initialize SDL: %s", SDL_GetError()); - return false; + return SCRCPY_EXIT_FAILURE; } atexit(SDL_Quit); - bool ret = false; + enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE; bool server_started = false; bool file_pusher_initialized = false; @@ -332,7 +332,7 @@ scrcpy(struct scrcpy_options *options) { .on_disconnected = sc_server_on_disconnected, }; if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) { - return false; + return SCRCPY_EXIT_FAILURE; } if (!sc_server_start(&s->server)) { @@ -361,7 +361,7 @@ scrcpy(struct scrcpy_options *options) { if (!connected) { // This is not an error, user requested to quit - ret = true; + ret = SCRCPY_EXIT_SUCCESS; goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index cdcecda7..d4d494a3 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -6,7 +6,18 @@ #include #include "options.h" -bool +enum scrcpy_exit_code { + // Normal program termination + SCRCPY_EXIT_SUCCESS, + + // No connection could be established + SCRCPY_EXIT_FAILURE, + + // Device was disconnected while running + SCRCPY_EXIT_DISCONNECTED, +}; + +enum scrcpy_exit_code scrcpy(struct scrcpy_options *options); #endif diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 1c53410e..db5e64d8 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -29,26 +29,26 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) { } } -static bool +static enum scrcpy_exit_code event_loop(struct scrcpy_otg *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { case EVENT_USB_DEVICE_DISCONNECTED: LOGW("Device disconnected"); - return false; + return SCRCPY_EXIT_DISCONNECTED; case SDL_QUIT: LOGD("User requested to quit"); - return true; + return SCRCPY_EXIT_SUCCESS; default: sc_screen_otg_handle_event(&s->screen_otg, &event); break; } } - return false; + return SCRCPY_EXIT_FAILURE; } -bool +enum scrcpy_exit_code scrcpy_otg(struct scrcpy_options *options) { static struct scrcpy_otg scrcpy_otg; struct scrcpy_otg *s = &scrcpy_otg; @@ -67,7 +67,7 @@ scrcpy_otg(struct scrcpy_options *options) { LOGW("Could not enable mouse focus clickthrough"); } - bool ret = false; + enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE; struct sc_hid_keyboard *keyboard = NULL; struct sc_hid_mouse *mouse = NULL; @@ -90,7 +90,7 @@ scrcpy_otg(struct scrcpy_options *options) { }; bool ok = sc_usb_init(&s->usb); if (!ok) { - return false; + return SCRCPY_EXIT_FAILURE; } struct sc_usb_device usb_device; diff --git a/app/src/usb/scrcpy_otg.h b/app/src/usb/scrcpy_otg.h index 24b9cde8..e477660b 100644 --- a/app/src/usb/scrcpy_otg.h +++ b/app/src/usb/scrcpy_otg.h @@ -3,10 +3,10 @@ #include "common.h" -#include #include "options.h" +#include "scrcpy.h" -bool +enum scrcpy_exit_code scrcpy_otg(struct scrcpy_options *options); #endif From b1dbc30072bdf4171f7d73532607ca329003094c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Mar 2022 09:11:50 +0100 Subject: [PATCH 0502/1133] Document exit status in --help Refs #3085 --- app/src/cli.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index a4f79840..bde5eb00 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -80,6 +80,11 @@ struct sc_envvar { const char *text; }; +struct sc_exit_status { + unsigned value; + const char *text; +}; + struct sc_getopt_adapter { char *optstring; struct option *longopts; @@ -662,7 +667,22 @@ static const struct sc_envvar envvars[] = { { .name = "SCRCPY_SERVER_PATH", .text = "Path to the server binary", - } + }, +}; + +static const struct sc_exit_status exit_statuses[] = { + { + .value = 0, + .text = "Normal program termination", + }, + { + .value = 1, + .text = "Start failure", + }, + { + .value = 2, + .text = "Device disconnected while running", + }, }; static char * @@ -901,6 +921,25 @@ print_envvar(const struct sc_envvar *envvar, unsigned cols) { free(text); } +static void +print_exit_status(const struct sc_exit_status *status, unsigned cols) { + assert(cols > 8); // sc_str_wrap_lines() requires indent < columns + assert(status->text); + + // The text starts at 9: 4 ident spaces, 3 chars for numeric value, 2 spaces + char *text = sc_str_wrap_lines(status->text, cols, 9); + if (!text) { + printf("\n"); + return; + } + + assert(strlen(text) >= 9); // Contains at least the initial identation + + // text + 9 to remove the initial indentation + printf(" %3d %s\n", status->value, text + 9); + free(text); +} + void scrcpy_print_usage(const char *arg0) { #define SC_TERM_COLS_DEFAULT 80 @@ -939,6 +978,11 @@ scrcpy_print_usage(const char *arg0) { for (size_t i = 0; i < ARRAY_LEN(envvars); ++i) { print_envvar(&envvars[i], cols); } + + printf("\nExit status:\n\n"); + for (size_t i = 0; i < ARRAY_LEN(exit_statuses); ++i) { + print_exit_status(&exit_statuses[i], cols); + } } static bool From 4ce7af42c635a6e9edd7c67902718d3a2c7f45fd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Mar 2022 12:29:33 +0100 Subject: [PATCH 0503/1133] Use $ANDROID_SERIAL if no selector is specified Like adb, read the ANDROID_SERIAL environment variable to select a device by serial if no explicit selection (-s, -d, -e or --tcpip=) is provided via the command line. Fixes #3111 PR #3113 --- README.md | 3 +++ app/scrcpy.1 | 4 ++++ app/src/cli.c | 5 +++++ app/src/server.c | 10 +++++++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c89b0c8d..614d1ed4 100644 --- a/README.md +++ b/README.md @@ -448,6 +448,9 @@ scrcpy --serial 0123456789abcdef scrcpy -s 0123456789abcdef # short version ``` +The serial may also be provided via the environment variable `ANDROID_SERIAL` +(also used by `adb`). + If the device is connected over TCP/IP: ```bash diff --git a/app/scrcpy.1 b/app/scrcpy.1 index f1ee732d..eb164475 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -477,6 +477,10 @@ Push file to device (see \fB\-\-push\-target\fR) .B ADB Path to adb. +.TP +.B ANDROID_SERIAL +Device serial to use if no selector (-s, -d, -e or --tcpip=) is specified. + .TP .B SCRCPY_ICON_PATH Path to the program icon. diff --git a/app/src/cli.c b/app/src/cli.c index bde5eb00..5dda86e5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -660,6 +660,11 @@ static const struct sc_envvar envvars[] = { .name = "ADB", .text = "Path to adb executable", }, + { + .name = "ANDROID_SERIAL", + .text = "Device serial to use if no selector (-s, -d, -e or " + "--tcpip=) is specified", + }, { .name = "SCRCPY_ICON_PATH", .text = "Path to the program icon", diff --git a/app/src/server.c b/app/src/server.c index c12b03d4..6b61924b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -707,7 +707,15 @@ run_server(void *data) { } else if (params->select_tcpip) { selector.type = SC_ADB_DEVICE_SELECT_TCPIP; } else { - selector.type = SC_ADB_DEVICE_SELECT_ALL; + // No explicit selection, check $ANDROID_SERIAL + const char *env_serial = getenv("ANDROID_SERIAL"); + if (env_serial) { + LOGI("Using ANDROID_SERIAL: %s", env_serial); + selector.type = SC_ADB_DEVICE_SELECT_SERIAL; + selector.serial = env_serial; + } else { + selector.type = SC_ADB_DEVICE_SELECT_ALL; + } } struct sc_adb_device device; ok = sc_adb_select_device(&server->intr, &selector, 0, &device); From e56f2ac7a9a3c6bbe8ea3d311e2054f5c1fa1c0d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 20 Mar 2022 14:56:52 +0100 Subject: [PATCH 0504/1133] Log an error on unexpected device state Refs #3129 --- app/src/adb/adb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 06090b46..7e1c7bc2 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -531,6 +531,8 @@ sc_adb_device_check_state(struct sc_adb_device *device, LOGE("A popup should open on the device to request authorization."); LOGE("Check the FAQ: " ""); + } else { + LOGE("Device could not be connected (state=%s)", state); } return false; From c3d45c8397b7886d9071ccd69f04e2c9245fb8e7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Mar 2022 21:07:31 +0100 Subject: [PATCH 0505/1133] Consider emulators as TCP/IP devices Emulators were wrongly considered as USB devices, so they were selected using -d instead of -e (which was inconsistent with the adb behavior). Refs #3005 Refs #3137 --- app/src/adb/adb.c | 18 +++++++++--------- app/src/adb/adb.h | 9 --------- app/src/adb/adb_device.c | 15 +++++++++++++++ app/src/adb/adb_device.h | 12 ++++++++++++ app/src/server.c | 3 ++- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 7e1c7bc2..344f7fcc 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -473,9 +473,12 @@ sc_adb_accept_device(const struct sc_adb_device *device, } return !strcmp(selector->serial, device->serial); case SC_ADB_DEVICE_SELECT_USB: - return !sc_adb_is_serial_tcpip(device->serial); + return sc_adb_device_get_type(device->serial) == + SC_ADB_DEVICE_TYPE_USB; case SC_ADB_DEVICE_SELECT_TCPIP: - return sc_adb_is_serial_tcpip(device->serial); + // Both emulators and TCP/IP devices are selected via -e + return sc_adb_device_get_type(device->serial) != + SC_ADB_DEVICE_TYPE_USB; default: assert(!"Missing SC_ADB_DEVICE_SELECT_* handling"); break; @@ -509,8 +512,10 @@ sc_adb_devices_log(enum sc_log_level level, struct sc_adb_device *devices, for (size_t i = 0; i < count; ++i) { struct sc_adb_device *d = &devices[i]; const char *selection = d->selected ? "-->" : " "; - const char *type = sc_adb_is_serial_tcpip(d->serial) ? "(tcpip)" - : " (usb)"; + bool is_usb = + sc_adb_device_get_type(d->serial) == SC_ADB_DEVICE_TYPE_USB; + const char *type = is_usb ? " (usb)" + : "(tcpip)"; LOG(level, " %s %s %-20s %16s %s", selection, type, d->serial, d->state, d->model ? d->model : ""); } @@ -707,8 +712,3 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { return sc_adb_parse_device_ip_from_output(buf); } - -bool -sc_adb_is_serial_tcpip(const char *serial) { - return strchr(serial, ':'); -} diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 10e8f293..ffd532ea 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -114,13 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); -/** - * Indicate if the serial represents an IP address - * - * In practice, it just returns true if and only if it contains a ':', which is - * sufficient to distinguish an ip:port from a real USB serial. - */ -bool -sc_adb_is_serial_tcpip(const char *serial); - #endif diff --git a/app/src/adb/adb_device.c b/app/src/adb/adb_device.c index cfd5dc30..4f500243 100644 --- a/app/src/adb/adb_device.c +++ b/app/src/adb/adb_device.c @@ -25,3 +25,18 @@ sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) { sc_vector_destroy(devices); } +enum sc_adb_device_type +sc_adb_device_get_type(const char *serial) { + // Starts with "emulator-" + if (!strncmp(serial, "emulator-", sizeof("emulator-") - 1)) { + return SC_ADB_DEVICE_TYPE_EMULATOR; + } + + // If the serial contains a ':', then it is a TCP/IP device (it is + // sufficient to distinguish an ip:port from a real USB serial) + if (strchr(serial, ':')) { + return SC_ADB_DEVICE_TYPE_TCPIP; + } + + return SC_ADB_DEVICE_TYPE_USB; +} diff --git a/app/src/adb/adb_device.h b/app/src/adb/adb_device.h index 14d0408c..56393bcf 100644 --- a/app/src/adb/adb_device.h +++ b/app/src/adb/adb_device.h @@ -15,6 +15,12 @@ struct sc_adb_device { bool selected; }; +enum sc_adb_device_type { + SC_ADB_DEVICE_TYPE_USB, + SC_ADB_DEVICE_TYPE_TCPIP, + SC_ADB_DEVICE_TYPE_EMULATOR, +}; + struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device); void @@ -35,4 +41,10 @@ sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src); void sc_adb_devices_destroy(struct sc_vec_adb_devices *devices); +/** + * Deduce the device type from the serial + */ +enum sc_adb_device_type +sc_adb_device_get_type(const char *serial); + #endif diff --git a/app/src/server.c b/app/src/server.c index 6b61924b..c02390a4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -649,7 +649,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server, static bool sc_server_configure_tcpip_unknown_address(struct sc_server *server, const char *serial) { - bool is_already_tcpip = sc_adb_is_serial_tcpip(serial); + bool is_already_tcpip = + sc_adb_device_get_type(serial) == SC_ADB_DEVICE_TYPE_TCPIP; if (is_already_tcpip) { // Nothing to do LOGI("Device already connected via TCP/IP: %s", serial); From 2edc73e4b80d35acc594adef2d35d99e743befed Mon Sep 17 00:00:00 2001 From: IskandarMa Date: Fri, 18 Mar 2022 11:53:26 +0800 Subject: [PATCH 0506/1133] Improve README.zh-Hans.md Fix misleading zoom instruction, which gave opposite action steps. PR #3126 Signed-off-by: Romain Vimont --- README.zh-Hans.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index caf964b3..ac0713a6 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -704,11 +704,11 @@ scrcpy --disable-screensaver #### 双指缩放 -模拟“双指缩放”:Ctrl+_按住并移动鼠标_。 +模拟“双指缩放”:Ctrl+_按下并拖动鼠标_。 -更准确的说,在按住鼠标左键时按住 Ctrl。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。 +在按住 Ctrl 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。 -实际上,_scrcpy_ 会在关于屏幕中心对称的位置上用“虚拟手指”发出触摸事件。 +具体来说,_scrcpy_ 会在鼠标位置,以及鼠标以屏幕中心镜像的位置分别生成触摸事件。 #### 物理键盘模拟 (HID) From aaf3869a544d6700032f0a834e047737f18fb145 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 30 Mar 2022 11:57:47 +0200 Subject: [PATCH 0507/1133] Fix OpenGL ES prefix skip --- app/src/opengl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/opengl.c b/app/src/opengl.c index da05c082..376690af 100644 --- a/app/src/opengl.c +++ b/app/src/opengl.c @@ -28,7 +28,7 @@ sc_opengl_init(struct sc_opengl *gl) { sizeof(OPENGL_ES_PREFIX) - 1); if (gl->is_opengles) { /* skip the prefix */ - version += sizeof(PREFIX) - 1; + version += sizeof(OPENGL_ES_PREFIX) - 1; } int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor); From 88543cb5453f74ac611759ff483b6a413f367e1b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 30 Mar 2022 14:00:05 +0200 Subject: [PATCH 0508/1133] Fix icon path in ./run The data/ directory has been moved to app/data/. Refs 36c75e15b8e9eeb01bd287a42fa3f1513a728ebb --- run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run b/run index fda3ea57..56f0a4e1 100755 --- a/run +++ b/run @@ -20,6 +20,6 @@ then exit 1 fi -SCRCPY_ICON_PATH="data/icon.png" \ +SCRCPY_ICON_PATH="app/data/icon.png" \ SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \ "$BUILDDIR/app/scrcpy" "$@" From 7b7cfc3a3e463e83067da2669b401338a9826953 Mon Sep 17 00:00:00 2001 From: e1e0 <91560320+e1e0@users.noreply.github.com> Date: Fri, 25 Feb 2022 22:29:38 +0000 Subject: [PATCH 0509/1133] Fix reference to FAQ in README PR #3065 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c89b0c8d..5bd515fb 100644 --- a/README.md +++ b/README.md @@ -1105,7 +1105,7 @@ See [BUILD]. ## Common issues -See the [FAQ].md). +See the [FAQ]. [FAQ]: FAQ.md From 0c94887075b4c84532b71cbdbad4f0121ab94099 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 12 Apr 2022 23:51:05 +0200 Subject: [PATCH 0510/1133] Add missing include Refs c3d45c8397b7886d9071ccd69f04e2c9245fb8e7 --- app/src/adb/adb_device.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/adb/adb_device.c b/app/src/adb/adb_device.c index 4f500243..5ea8eb44 100644 --- a/app/src/adb/adb_device.c +++ b/app/src/adb/adb_device.c @@ -1,6 +1,7 @@ #include "adb_device.h" #include +#include void sc_adb_device_destroy(struct sc_adb_device *device) { From fa5b2a29e983a46b49531def9cf3d80c40c3de37 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 12 Apr 2022 23:59:01 +0200 Subject: [PATCH 0511/1133] Add missing SC_ prefix to header guards --- app/src/common.h | 4 ++-- app/src/compat.h | 4 ++-- app/src/control_msg.h | 4 ++-- app/src/controller.h | 4 ++-- app/src/device_msg.h | 4 ++-- app/src/fps_counter.h | 4 ++-- app/src/icon.h | 4 ++-- app/src/input_manager.h | 4 ++-- app/src/receiver.h | 4 ++-- app/src/util/cbuf.h | 4 ++-- app/src/util/net.h | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/src/common.h b/app/src/common.h index ce9ccad4..dccc8316 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -1,5 +1,5 @@ -#ifndef COMMON_H -#define COMMON_H +#ifndef SC_COMMON_H +#define SC_COMMON_H #include "config.h" #include "compat.h" diff --git a/app/src/compat.h b/app/src/compat.h index 62435718..8265dbc8 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -1,5 +1,5 @@ -#ifndef COMPAT_H -#define COMPAT_H +#ifndef SC_COMPAT_H +#define SC_COMPAT_H #include "config.h" diff --git a/app/src/control_msg.h b/app/src/control_msg.h index fbe203d4..1463fddc 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -1,5 +1,5 @@ -#ifndef CONTROLMSG_H -#define CONTROLMSG_H +#ifndef SC_CONTROLMSG_H +#define SC_CONTROLMSG_H #include "common.h" diff --git a/app/src/controller.h b/app/src/controller.h index f8bc7c02..f8662353 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -1,5 +1,5 @@ -#ifndef CONTROLLER_H -#define CONTROLLER_H +#ifndef SC_CONTROLLER_H +#define SC_CONTROLLER_H #include "common.h" diff --git a/app/src/device_msg.h b/app/src/device_msg.h index a0c989e3..e8d9fed4 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -1,5 +1,5 @@ -#ifndef DEVICEMSG_H -#define DEVICEMSG_H +#ifndef SC_DEVICEMSG_H +#define SC_DEVICEMSG_H #include "common.h" diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index e21c49c4..e7619271 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -1,5 +1,5 @@ -#ifndef FPSCOUNTER_H -#define FPSCOUNTER_H +#ifndef SC_FPSCOUNTER_H +#define SC_FPSCOUNTER_H #include "common.h" diff --git a/app/src/icon.h b/app/src/icon.h index 8df53671..3251e48f 100644 --- a/app/src/icon.h +++ b/app/src/icon.h @@ -1,5 +1,5 @@ -#ifndef ICON_H -#define ICON_H +#ifndef SC_ICON_H +#define SC_ICON_H #include "common.h" diff --git a/app/src/input_manager.h b/app/src/input_manager.h index f6c210e3..46b1160e 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -1,5 +1,5 @@ -#ifndef INPUTMANAGER_H -#define INPUTMANAGER_H +#ifndef SC_INPUTMANAGER_H +#define SC_INPUTMANAGER_H #include "common.h" diff --git a/app/src/receiver.h b/app/src/receiver.h index 3c4e8c64..f5808e4b 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -1,5 +1,5 @@ -#ifndef RECEIVER_H -#define RECEIVER_H +#ifndef SC_RECEIVER_H +#define SC_RECEIVER_H #include "common.h" diff --git a/app/src/util/cbuf.h b/app/src/util/cbuf.h index 01e41044..2a756171 100644 --- a/app/src/util/cbuf.h +++ b/app/src/util/cbuf.h @@ -1,6 +1,6 @@ // generic circular buffer (bounded queue) implementation -#ifndef CBUF_H -#define CBUF_H +#ifndef SC_CBUF_H +#define SC_CBUF_H #include "common.h" diff --git a/app/src/util/net.h b/app/src/util/net.h index 15979cf9..d9289981 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -1,5 +1,5 @@ -#ifndef NET_H -#define NET_H +#ifndef SC_NET_H +#define SC_NET_H #include "common.h" From fc65c6cc4baced6d9758ee54d9fdab57966d09b8 Mon Sep 17 00:00:00 2001 From: Alessandro Sarretta Date: Sat, 26 Mar 2022 06:17:26 +0100 Subject: [PATCH 0512/1133] Update README.it.md to v1.23 PR #3151 Signed-off-by: Romain Vimont --- README.it.md | 342 ++++++++++++++++++++++++++++++++++++++++++--------- README.md | 2 +- 2 files changed, 286 insertions(+), 58 deletions(-) diff --git a/README.it.md b/README.it.md index 52e68ba3..a35c7bbb 100644 --- a/README.it.md +++ b/README.it.md @@ -1,23 +1,42 @@ -_Apri il [README](README.md) originale e sempre aggiornato._ +_Apri il [README](README.md) originale (in inglese) e sempre aggiornato._ -# scrcpy (v1.19) +scrcpy -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_. +# scrcpy (v1.23) + +_si pronuncia "**scr**een **c**o**py**"_ + +[Leggi in altre lingue](#traduzioni) + +Questa applicazione fornisce la visualizzazione e il controllo di dispositivi Android collegati via USB (o [via TCP/IP](#tcpip-wireless)). Non richiede alcun accesso _root_. Funziona su _GNU/Linux_, _Windows_ e _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) Si concentra su: - - **leggerezza** (nativo, mostra solo lo schermo del dispositivo) - - **prestazioni** (30~60fps) - - **qualità** (1920×1080 o superiore) - - **bassa latenza** ([35~70ms][lowlatency]) - - **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine) - - **non invadenza** (nulla viene lasciato installato sul dispositivo) + - **leggerezza**: nativo, mostra solo lo schermo del dispositivo + - **prestazioni**: 30~120fps, in funzione del dispositivo + - **qualità**: 1920×1080 o superiore + - **bassa latenza**: [35~70ms][lowlatency] + - **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine + - **non invadenza**: nulla rimane installato sul dispositivo + - **vantaggi per l'utente**: nessun account, nessuna pubblicità, non è richiesta alcuna connessione a internet + - **libertà**: software libero e a codice aperto (_free and open source_) [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 +Le sue caratteristiche includono: + - [registrazione](#registrazione) + - mirroring con [schermo del dispositivo spento](#spegnere-lo-schermo) + - [copia-incolla](#copia-incolla) in entrambe le direzioni + - [qualità configurabile](#configurazione-di-acquisizione) + - schermo del dispositivo [come webcam (V4L2)](#v4l2loopback) (solo per Linux) + - [simulazione della tastiera fisica (HID)](#simulazione-della-tastiera-fisica-HID) + - [simulazione mouse fisico (HID)](#simulazione-del-mouse-fisico-HID) + - [modalità OTG](#otg) + - e altro ancora... + ## Requisiti @@ -49,12 +68,18 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si ### Linux -Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04): +Su Debian e Ubuntu: ``` apt install scrcpy ``` +Su Arch Linux: + +``` +pacman -S scrcpy +``` + È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link]. [snap-link]: https://snapstats.org/snaps/scrcpy @@ -66,10 +91,6 @@ Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link]. [COPR]: https://fedoraproject.org/wiki/Category:Copr [copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ -Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link]. @@ -142,7 +163,7 @@ Collega un dispositivo Android ed esegui: scrcpy ``` -Scrcpy accetta argomenti da riga di comando, essi sono listati con: +Scrcpy accetta argomenti da riga di comando, elencati con: ```bash scrcpy --help @@ -186,6 +207,14 @@ scrcpy --max-fps 15 Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti. +L'attuale frame rate di acquisizione può essere stampato sulla console: + +``` +scrcpy --print-fps +``` + +Può anche essere abilitato o disabilitato in qualsiasi momento con MOD+i. + #### Ritaglio Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso. @@ -258,7 +287,7 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re #### 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. +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: @@ -321,42 +350,72 @@ 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 +scrcpy --v4l2-buffer=500 # aggiungi 500 ms di buffer per il v4l2 sink ``` ### Connessione -#### Wireless +#### TCP/IP (wireless) + +_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] a un dispositivo mediante TCP/IP. Il dispositivo deve essere collegato alla stessa rete del computer. +##### Automatico -_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP: +Un'opzione `--tcpip` permette di configurare automaticamente la connessione. Ci sono due varianti. -1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer. -2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando: +Se il dispositivo (accessibile a 192.168.1.1 in questo esempio) ascolta già su una porta (tipicamente 5555) per le connessioni adb in entrata, allora esegui: + +```bash +scrcpy --tcpip=192.168.1.1 # la porta predefinita è 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +Se la modalità TCP/IP di adb è disabilitata sul dispositivo (o se non si conosce l'indirizzo IP indirizzo), collegare il dispositivo tramite USB, quindi eseguire: + +```bash +scrcpy --tcpip # senza argomenti +``` + +Il comando troverà automaticamente l'indirizzo IP del dispositivo, abiliterà la modalità TCP/IP, quindi connettersi al dispositivo prima di iniziare. + +##### Manuale + +In alternativa, è possibile abilitare la connessione TCP/IP manualmente usando `adb`: + +1. Inserisci il dispositivo in una porta USB del tuo computer. +2. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer. +3. Ottieni l'indirizzo IP del tuo dispositivo, in Impostazioni → Informazioni sul telefono → Stato, o + eseguendo questo comando: ```bash adb shell ip route | awk '{print $9}' ``` -3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. -4. Scollega il tuo dispositivo. -5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_. -6. Esegui `scrcpy` come al solito. +4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. +5. Scollega il tuo dispositivo. +6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP` +con l'indirizzo IP del dispositivo che hai trovato)_. +7. Esegui `scrcpy` come al solito. + +Da Android 11, una [opzione di debug wireless][adb-wireless] permette di evitare di dover collegare fisicamente il dispositivo direttamente al computer. -Potrebbe essere utile diminuire il bit-rate e la definizione +[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ + +Se la connessione cade casualmente, esegui il comando `scrcpy` per riconnetterti. Se il comando dice che non ci sono dispositivi/emulatori trovati, prova ad eseguire `adb connect DEVICE_IP:5555` di nuovo, e poi `scrcpy` come al solito. Se dice ancora che non ne ha trovato nessuno, prova ad eseguire `adb disconnect` e poi esegui di nuovo questi due comandi. + +Potrebbe essere utile diminuire il bit-rate e la definizione: ```bash scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versione breve +scrcpy -b2M -m800 # versione breve ``` [connect]: https://developer.android.com/studio/command-line/adb.html#wireless - #### Multi dispositivo -Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_: +Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_: ```bash scrcpy --serial 0123456789abcdef @@ -370,6 +429,18 @@ scrcpy --serial 192.168.0.1:5555 scrcpy -s 192.168.0.1:5555 # versione breve ``` +Se solo un dispositivo è collegato via USB o TCP/IP, è possibile selezionarlo automaticamente: + +```bash +# Select the only device connected via USB +scrcpy -d # like adb -d +scrcpy --select-usb # long version + +# Select the only device connected via TCP/IP +scrcpy -e # like adb -e +scrcpy --select-tcpip # long version +``` + Puoi avviare più istanze di _scrcpy_ per diversi dispositivi. @@ -383,37 +454,77 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -#### Tunnel SSH +#### Tunnels + +Per connettersi a un dispositivo remoto, è possibile collegare un client `adb` locale a un server remoto `adb` (purché usino la stessa versione del protocollo _adb_). ). -Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_): +##### Server ADB remoto + +Per connettersi a un server ADB remoto, fate ascoltare il server su tutte le interfacce: ```bash -adb kill-server # termina il server adb locale su 5037 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# tieni questo aperto +adb kill-server +adb -a nodaemon server start +# tienilo aperto +``` + +**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.** + +Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy: + +```bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +Per impostazione predefinita, scrcpy utilizza la porta locale utilizzata per il tunnel `adb forward` (tipicamente `27183`, vedi `--port`). È anche possibile forzare una diversa porta del tunnel (può essere utile in situazioni più complesse, quando sono coinvolti più reindirizzamenti): + +``` +scrcpy --tunnel-port=1234 +``` + +##### SSH tunnel + +Per comunicare con un server ADB remoto in modo sicuro, è preferibile utilizzare un tunnel SSH. + +Per prima cosa, assicurati che il server ADB sia in esecuzione sul computer remoto: + +```bash +adb start-server ``` -Da un altro terminale: +Poi, crea un tunnel SSH: ```bash +# local 5038 --> remote 5037 +# local 27183 <-- remote 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer +# keep this open +``` + +Da un altro terminale, esegui scrcpy: + +```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy ``` Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`) ```bash -adb kill-server # termina il server adb locale su 5037 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer +# local 5038 --> remote 5037 +# local 27183 --> remote 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer # tieni questo aperto ``` -Da un altro terminale: +Da un altro terminale, esegui scrcpy: ```bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy --force-adb-forward ``` - Come per le connessioni wireless potrebbe essere utile ridurre la qualità: ``` @@ -551,6 +662,14 @@ scrcpy --turn-screen-off --stay-awake scrcpy -Sw ``` +#### Spegnimento alla chiusura + +Per spegnere lo schermo del dispositivo quando si chiude scrcpy: + +```bash +scrcpy --power-off-on-close +``` + #### Mostrare i tocchi @@ -596,20 +715,22 @@ Qualsiasi scorciatoia Ctrl viene inoltrata al dispositivo. In partico - Ctrl+x taglia - Ctrl+v incolla (dopo la sincronizzazione degli appunti da computer a dispositivo) -Questo solitamente funziona nella maniera più comune. +Questo solitamente funziona come ci si aspetta. Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con Ctrl+c, e _K-9 Mail_ compone un nuovo messaggio. Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7): - - MOD+c inietta `COPY` - - MOD+x inietta `CUT` - - MOD+v inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) + - MOD+c invia `COPY` + - MOD+x invia `CUT` + - MOD+v invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) -In aggiunta, MOD+Shift+v permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII. +In aggiunta, MOD+Shift+v permette l'invio del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può compromettere il contenuto non ASCII. **AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con Ctrl+v che con MOD+v) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera. -Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di Ctrl+v and MOD+v in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di MOD+Shift+v). +Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di Ctrl+v and MOD+v in modo tale che anch'essi inviino il testo degli appunti del computer come una sequenza di eventi di pressione dei tasti (nella stessa maniera di MOD+Shift+v). + +Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`. #### Pizzica per zoomare (pinch-to-zoom) @@ -617,16 +738,98 @@ Per simulare il "pizzica per zoomare": Ctrl+_click e trascina_. Più precisamente, tieni premuto Ctrl mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo. -Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo. +Concretamente, scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo. + +#### Simulazione della tastiera fisica (HID) + +Per impostazione predefinita, scrcpy utilizza l'invio dei tasti o del testo di Android: funziona ovunque, ma è limitato all'ASCII. + +In alternativa scrcpy può simulare una tastiera fisica USB su Android per fornire una migliore esperienza di input (utilizzando [USB HID over AOAv2][hid-aoav2]): la tastiera virtuale è disabilitata e funziona per tutti i caratteri e IME. + +[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support + +Tuttavia, funziona solo se il dispositivo è collegato via USB. + +Nota: su Windows, può funzionare solo in [odalità OTG](#otg), non durante il mirroring (non è possibile aprire un dispositivo USB se è già aperto da un altro processo come il daemon adb). + +Per abilitare questa modalità: + +```bash +scrcpy --hid-keyboard +scrcpy -K # versione breve +``` + +Se fallisce per qualche motivo (per esempio perché il dispositivo non è connesso via USB), ritorna automaticamente alla modalità predefinita (con un log nella console). Questo permette di usare le stesse opzioni della linea di comando quando si è connessi via USB e TCP/IP. + +In questa modalità, gli eventi i pressione originali (scancodes) sono inviati al dispositivo, indipendentemente dalla mappatura dei tasti dell'host. Pertanto, se il layout della tua tastiera non corrisponde, deve essere configurato sul dispositivo Android, in Impostazioni → Sistema → Lingue e input → [Tastiera fisica] (in inglese). + +Questa pagina di impostazioni può essere avviata direttamente: + +```bash +adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS +``` + +Tuttavia, l'opzione è disponibile solo quando la tastiera HID è abilitata (o quando una tastiera fisica è collegata). + +[Tastiera fisica]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 + +#### Simulazione del mouse fisico (HID) + +In modo simile alla simulazione della tastiera fisica, è possibile simulare un mouse fisico. Allo stesso modo funziona solo se il dispositivo è connesso via USB. + +Per impostazione predefinita, scrcpy utilizza l'invio degli eventi del mouse di Android, utilizzando coordinate assolute. Simulando un mouse fisico, un puntatore del mouse appare sul dispositivo Android e vengono inviati i movimenti relativi del mouse, i click e gli scorrimenti. + +Per abilitare questa modalità: + +```bash +scrcpy --hid-mouse +scrcpy -M # versione breve +``` + +Si potrebbe anche aggiungere `--forward-all-clicks` a [inoltra tutti i pulsanti del mouse][forward_all_clicks]. +[forward_all_clicks]: #click-destro-e-click-centrale -#### Preferenze di iniezione del testo + +Quando questa modalità è attivata, il mouse del computer viene "catturato" (il puntatore del mouse scompare dal computer e appare invece sul dispositivo Android). + +I tasti speciali di cattura, Alt o Super, commutano (disabilitano o abilitano) la cattura del mouse. Usa uno di essi per ridare il controllo del mouse al computer. + + +#### OTG + +È possibile eseguire _scrcpy_ con la sola simulazione della tastiera fisica e del mouse (HID), come se la tastiera e il mouse del computer fossero collegati direttamente al dispositivo tramite un cavo OTG. + +In questa modalità, _adb_ (debug USB) non è necessario e il mirroring è disabilitato. + +Per attivare la modallità OTG: + +```bash +scrcpy --otg +# Passa la seriale se sono disponibili diversi dispositivi USB +scrcpy --otg -s 0123456789abcdef +``` + +È possibile abilitare solo la tastiera HID o il mouse HID: + +```bash +scrcpy --otg --hid-keyboard # solo la tastiera +scrcpy --otg --hid-mouse # solo mouse +scrcpy --otg --hid-keyboard --hid-mouse # tastiera e mouse +# per comodità, abilita entrambi per default +scrcpy --otg # tastiera e mouse +``` + +Come `--hid-keyboard` e `--hid-mouse`, funziona solo se il dispositivo è collegato via USB. + + +#### Preferenze di invio del testo Ci sono due tipi di [eventi][textevents] generati quando si scrive testo: - _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato; - _eventi di testo_, segnalano che del testo è stato inserito. -In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD). +In maniera predefinita le lettere sono inviate usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD). Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con: @@ -636,13 +839,21 @@ scrcpy --prefer-text (ma questo romperà il normale funzionamento della tastiera nei giochi) +Al contrario, si potrebbe forzare per inviare sempre eventi di pressione grezzi: + +```bash +scrcpy --raw-key-events +``` + +Queste opzioni non hanno effetto sulla tastiera HID (tutti gli eventi di pressione sono inviati come scancodes in questa modalità). + [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 #### Ripetizione di tasti -In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati. +In maniera predefinita, tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati. Per prevenire l'inoltro ripetuto degli eventi di pressione: @@ -650,9 +861,12 @@ Per prevenire l'inoltro ripetuto degli eventi di pressione: scrcpy --no-key-repeat ``` +Questa opzione non ha effetto sulla tastiera HID (la ripetizione dei tasti è gestita da Android direttamente in questa modalità). + + #### Click destro e click centrale -In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo: +In maniera predefinita, click destro aziona BACK (indietro) o POWER on (accensione) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo: ```bash scrcpy --forward-all-clicks @@ -705,7 +919,7 @@ scrcpy --shortcut-mod=rctrl scrcpy --shortcut-mod=lctrl+lalt,lsuper ``` -_[Super] è il pulsante Windows o Cmd._ +_[Super] è solitamente il pulsante Windows o Cmd._ [Super]: https://it.wikipedia.org/wiki/Tasto_Windows @@ -720,7 +934,7 @@ _[Super] è il pulsante Windows o Cmd._ | Premi il tasto `HOME` | MOD+h \| _Click centrale_ | Premi il tasto `BACK` | MOD+b \| _Click destro²_ | Premi il tasto `APP_SWITCH` | MOD+s \| _4° click³_ - | Premi il tasto `MENU` (sblocca lo schermo) | MOD+m + | Premi il tasto `MENU` (sblocca lo schermo)⁴ | MOD+m | Premi il tasto `VOLUME_UP` | MOD+ _(su)_ | Premi il tasto `VOLUME_DOWN` | MOD+ _(giù)_ | Premi il tasto `POWER` | MOD+p @@ -731,17 +945,20 @@ _[Super] è il pulsante Windows o Cmd._ | Espandi il pannello delle notifiche | MOD+n \| _5° click³_ | Espandi il pannello delle impostazioni | MOD+n+n \| _Doppio 5° click³_ | Chiudi pannelli | MOD+Shift+n - | Copia negli appunti⁴ | MOD+c - | Taglia negli appunti⁴ | MOD+x - | Sincronizza gli appunti e incolla⁴ | MOD+v - | Inietta il testo degli appunti del computer | MOD+Shift+v + | Copia negli appunti⁵ | MOD+c + | Taglia negli appunti⁵ | MOD+x + | Sincronizza gli appunti e incolla⁵ | MOD+v + | Invia il testo degli appunti del computer | MOD+Shift+v | Abilita/Disabilita il contatore FPS (su stdout) | MOD+i | Pizzica per zoomare | Ctrl+_click e trascina_ + | Trascina file APK | Installa APK dal computer + | Trascina file non-APK | [Trasferisci file verso il dispositivo](#push-file-to-device) _¹Doppio click sui bordi neri per rimuoverli._ _²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ _³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._ -_⁴Solo in Android >= 7._ +_⁴Per le app native react in sviluppo, `MENU` attiva il menu di sviluppo._ +_⁵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": @@ -811,3 +1028,14 @@ Leggi la [pagina per sviluppatori]. [article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ [article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ + +## Contatti + +Se incontri un bug, per favore leggi prima le [FAQ](FAQ.it.md), poi apri una [issue]. + +[issue]: https://github.com/Genymobile/scrcpy/issues + +Per domande generali o discussioni, puoi anche usare: + + - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) + - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) diff --git a/README.md b/README.md index 5bd515fb..c7346974 100644 --- a/README.md +++ b/README.md @@ -1159,7 +1159,7 @@ This README is available in other languages: - [Deutsch (German, `de`) - v1.22](README.de.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md) -- [Italiano (Italiano, `it`) - v1.19](README.it.md) +- [Italiano (Italiano, `it`) - v1.23](README.it.md) - [日本語 (Japanese, `jp`) - v1.19](README.jp.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) From 53b1e16f420d818071840889da52d6ac63b14b4d Mon Sep 17 00:00:00 2001 From: John Veness Date: Sat, 9 Apr 2022 15:45:28 +0100 Subject: [PATCH 0513/1133] Fix typos/grammar issues in README PR #3174 Signed-off-by: Romain Vimont --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7346974..7eb6cea6 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ scrcpy --max-size 1024 scrcpy -m 1024 # short version ``` -The other dimension is computed to that the device aspect ratio is preserved. +The other dimension is computed so that the device aspect ratio is preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. @@ -789,7 +789,7 @@ break non-ASCII content. **WARNING:** Pasting the computer clipboard to the device (either via Ctrl+v or MOD+v) copies the content into the device clipboard. As a consequence, any Android application could read -its content. You should avoid to paste sensitive content (like passwords) that +its content. You should avoid pasting sensitive content (like passwords) that way. Some devices do not behave as expected when setting the device clipboard @@ -838,7 +838,7 @@ 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 +console). This allows using 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 @@ -1062,7 +1062,7 @@ _³4th and 5th mouse buttons, if your mouse has them._ _⁴For react-native apps in development, `MENU` triggers development menu._ _⁵Only on Android >= 7._ -Shortcuts with repeated keys are executted by releasing and pressing the key a +Shortcuts with repeated keys are executed by releasing and pressing the key a second time. For example, to execute "Expand settings panel": 1. Press and keep pressing MOD. From 3c8ebf9abd4410fd46485db43035e35b3780e8d4 Mon Sep 17 00:00:00 2001 From: Gitoffthelawn Date: Sun, 10 Apr 2022 08:45:34 -0700 Subject: [PATCH 0514/1133] Improve README Improve clarity, grammar, consistency, punctuation, and formatting. PR #3177 Signed-off-by: Romain Vimont --- README.md | 144 +++++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 7eb6cea6..66c8924b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ _pronounced "**scr**een **c**o**py**"_ [Read in another language](#translations) This application provides display and control of Android devices connected via -USB (or [over TCP/IP](#tcpip-wireless)). It does not require any _root_ access. +USB or [over TCP/IP](#tcpip-wireless). It does not require any _root_ access. It works on _GNU/Linux_, _Windows_ and _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) @@ -19,7 +19,7 @@ It focuses on: - **quality**: 1920×1080 or above - **low latency**: [35~70ms][lowlatency] - **low startup time**: ~1 second to display the first image - - **non-intrusiveness**: nothing is left installed on the device + - **non-intrusiveness**: nothing is left installed on the Android device - **user benefits**: no account, no ads, no internet required - **freedom**: free and open source software @@ -27,10 +27,10 @@ It focuses on: Its features include: - [recording](#recording) - - mirroring with [device screen off](#turn-screen-off) + - mirroring with [Android device screen off](#turn-screen-off) - [copy-paste](#copy-paste) in both directions - [configurable quality](#capture-configuration) - - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only) + - Android device [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) - [physical mouse simulation (HID)](#physical-mouse-simulation-hid) - [OTG mode](#otg) @@ -40,12 +40,12 @@ Its features include: The Android device requires at least API 21 (Android 5.0). -Make sure you [enabled adb debugging][enable-adb] on your device(s). +Make sure you [enable adb debugging][enable-adb] on your device(s). [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling On some devices, you also need to enable [an additional option][control] to -control it using keyboard and mouse. +control it using a keyboard and mouse. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 @@ -97,14 +97,14 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy -You could also [build the app manually][BUILD] ([simplified +You can also [build the app manually][BUILD] ([simplified process][BUILD_simple]). ### Windows -For Windows, for simplicity, a prebuilt archive with all the dependencies -(including `adb`) is available: +For Windows, a prebuilt archive with all the dependencies (including `adb`) is +available: - [`scrcpy-win64-v1.23.zip`][direct-win64] _(SHA-256: d2f601b1d0157faf65153d8a093d827fd65aec5d5842d677ac86fb2b5b7704cc)_ @@ -148,7 +148,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet: brew install android-platform-tools ``` -It's also available in [MacPorts], which sets up adb for you: +It's also available in [MacPorts], which sets up `adb` for you: ```bash sudo port install scrcpy @@ -162,7 +162,7 @@ You can also [build the app manually][BUILD]. ## Run -Plug an Android device, and execute: +Plug an Android device into your computer, and execute: ```bash scrcpy @@ -180,7 +180,7 @@ scrcpy --help #### Reduce size -Sometimes, it is useful to mirror an Android device at a lower definition to +Sometimes, it is useful to mirror an Android device at a lower resolution to increase performance. To limit both the width and height to some value (e.g. 1024): @@ -190,8 +190,8 @@ scrcpy --max-size 1024 scrcpy -m 1024 # short version ``` -The other dimension is computed so that the device aspect ratio is preserved. -That way, a device in 1920×1080 will be mirrored at 1024×576. +The other dimension is computed so that the Android device aspect ratio is +preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. #### Change bit-rate @@ -226,7 +226,7 @@ It may also be enabled or disabled at any time with MOD+i. The device screen may be cropped to mirror only part of the screen. -This is useful for example to mirror only one eye of the Oculus Go: +This is useful, for example, to mirror only one eye of the Oculus Go: ```bash scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) @@ -237,7 +237,6 @@ If `--max-size` is also specified, resizing is applied after cropping. #### Lock video orientation - To lock the orientation of the mirroring: ```bash @@ -262,7 +261,7 @@ crash. It is possible to select a different encoder: scrcpy --encoder OMX.qcom.video.encoder.avc ``` -To list the available encoders, you could pass an invalid encoder name, the +To list the available encoders, you can pass an invalid encoder name; the error will give the available encoders: ```bash @@ -326,7 +325,7 @@ v4l2-ctl --list-devices ls /dev/video* ``` -To start scrcpy using a v4l2 sink: +To start `scrcpy` using a v4l2 sink: ```bash scrcpy --v4l2-sink=/dev/videoN @@ -334,7 +333,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window scrcpy --v4l2-sink=/dev/videoN -N # short version ``` -(replace `N` by the device ID, check with `ls /dev/video*`) +(replace `N` with the device ID, check with `ls /dev/video*`) Once enabled, you can open your video stream with a v4l2-capable tool: @@ -350,7 +349,7 @@ For example, you could capture the video within [OBS]. #### Buffering -It is possible to add buffering. This increases latency but reduces jitter (see +It is possible to add buffering. This increases latency, but reduces jitter (see [#2464]). [#2464]: https://github.com/Genymobile/scrcpy/issues/2464 @@ -382,14 +381,14 @@ An option `--tcpip` allows to configure the connection automatically. There are two variants. If the device (accessible at 192.168.1.1 in this example) already listens on a -port (typically 5555) for incoming adb connections, then run: +port (typically 5555) for incoming _adb_ connections, then run: ```bash scrcpy --tcpip=192.168.1.1 # default port is 5555 scrcpy --tcpip=192.168.1.1:5555 ``` -If adb TCP/IP mode is disabled on the device (or if you don't know the IP +If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP address), connect the device over USB, then run: ```bash @@ -413,7 +412,7 @@ Alternatively, it is possible to enable the TCP/IP connection manually using adb shell ip route | awk '{print $9}' ``` -4. Enable adb over TCP/IP on your device: `adb tcpip 5555`. +4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. 5. Unplug your device. 6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` with the device IP address you found)_. @@ -427,9 +426,9 @@ having to physically connect your device directly to your computer. If the connection randomly drops, run your `scrcpy` command to reconnect. If it says there are no devices/emulators found, try running `adb connect DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are -none found, try running `adb disconnect` and then run those two commands again. +none found, try running `adb disconnect`, and then run those two commands again. -It may be useful to decrease the bit-rate and the definition: +It may be useful to decrease the bit-rate and the resolution: ```bash scrcpy --bit-rate 2M --max-size 800 @@ -488,7 +487,7 @@ protocol). ##### Remote ADB server -To connect to a remote ADB server, make the server listen on all interfaces: +To connect to a remote _adb server_, make the server listen on all interfaces: ```bash adb kill-server @@ -496,17 +495,18 @@ adb -a nodaemon server start # keep this open ``` -**Warning: all communications between clients and ADB server are unencrypted.** +**Warning: all communications between clients and the _adb server_ are +unencrypted.** Suppose that this server is accessible at 192.168.1.2. Then, from another -terminal, run scrcpy: +terminal, run `scrcpy`: ```bash export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 scrcpy --tunnel-host=192.168.1.2 ``` -By default, scrcpy uses the local port used for `adb forward` tunnel +By default, `scrcpy` uses the local port used for `adb forward` tunnel establishment (typically `27183`, see `--port`). It is also possible to force a different tunnel port (it may be useful in more complex situations, when more redirections are involved): @@ -518,16 +518,16 @@ scrcpy --tunnel-port=1234 ##### SSH tunnel -To communicate with a remote ADB server securely, it is preferable to use a SSH -tunnel. +To communicate with a remote _adb server_ securely, it is preferable to use an +SSH tunnel. -First, make sure the ADB server is running on the remote computer: +First, make sure the _adb server_ is running on the remote computer: ```bash adb start-server ``` -Then, establish a SSH tunnel: +Then, establish an SSH tunnel: ```bash # local 5038 --> remote 5037 @@ -536,7 +536,7 @@ ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer # keep this open ``` -From another terminal, run scrcpy: +From another terminal, run `scrcpy`: ```bash export ADB_SERVER_SOCKET=tcp:localhost:5038 @@ -553,7 +553,7 @@ ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer # keep this open ``` -From another terminal, run scrcpy: +From another terminal, run `scrcpy`: ```bash export ADB_SERVER_SOCKET=tcp:localhost:5038 @@ -595,7 +595,7 @@ scrcpy --window-borderless #### Always on top -To keep the scrcpy window always on top: +To keep the _scrcpy_ window always on top: ```bash scrcpy --always-on-top @@ -620,7 +620,7 @@ The window may be rotated: scrcpy --rotation 1 ``` -Possibles values are: +Possible values: - `0`: no rotation - `1`: 90 degrees counterclockwise - `2`: 180 degrees @@ -669,19 +669,19 @@ adb shell dumpsys display # search "mDisplayId=" in the output ``` The secondary display may only be controlled if the device runs at least Android -10 (otherwise it is mirrored in read-only). +10 (otherwise it is mirrored as read-only). #### Stay awake -To prevent the device to sleep after some delay when the device is plugged in: +To prevent the device from sleeping after a delay when the device is plugged in: ```bash scrcpy --stay-awake scrcpy -w ``` -The initial state is restored when scrcpy is closed. +The initial state is restored when _scrcpy_ is closed. #### Turn screen off @@ -699,9 +699,10 @@ Or by pressing MOD+o at any time. To turn it back on, press MOD+Shift+o. On Android, the `POWER` button always turns the screen on. For convenience, if -`POWER` is sent via scrcpy (via right-click or MOD+p), it -will force to turn the screen off after a small delay (on a best effort basis). -The physical `POWER` button will still cause the screen to be turned on. +`POWER` is sent via _scrcpy_ (via right-click or MOD+p), +it will force to turn the screen off after a small delay (on a best effort +basis). The physical `POWER` button will still cause the screen to be turned +on. It can also be useful to prevent the device from sleeping: @@ -712,7 +713,7 @@ scrcpy -Sw #### Power off on close -To turn the device screen off when closing scrcpy: +To turn the device screen off when closing _scrcpy_: ```bash scrcpy --power-off-on-close @@ -734,12 +735,13 @@ scrcpy --show-touches scrcpy -t ``` -Note that it only shows _physical_ touches (with the finger on the device). +Note that it only shows _physical_ touches (by a finger on the device). #### Disable screensaver -By default, scrcpy does not prevent the screensaver to run on the computer. +By default, _scrcpy_ does not prevent the screensaver from running on the +computer. To disable it: @@ -781,18 +783,18 @@ To copy, cut and paste in such cases (but only supported on Android >= 7): - MOD+v injects `PASTE` (after computer-to-device clipboard synchronization) -In addition, MOD+Shift+v allows to inject the -computer clipboard text as a sequence of key events. This is useful when the -component does not accept text pasting (for example in _Termux_), but it can -break non-ASCII content. +In addition, MOD+Shift+v injects the computer +clipboard text as a sequence of key events. This is useful when the component +does not accept text pasting (for example in _Termux_), but it can break +non-ASCII content. **WARNING:** Pasting the computer clipboard to the device (either via Ctrl+v or MOD+v) copies the content -into the device clipboard. As a consequence, any Android application could read +into the Android clipboard. As a consequence, any Android application could read its content. You should avoid pasting sensitive content (like passwords) that way. -Some devices do not behave as expected when setting the device clipboard +Some Android devices do not behave as expected when setting the device clipboard programmatically. An option `--legacy-paste` is provided to change the behavior of Ctrl+v and MOD+v so that they also inject the computer clipboard text as a sequence of key events (the same @@ -805,29 +807,29 @@ To disable automatic clipboard synchronization, use To simulate "pinch-to-zoom": Ctrl+_click-and-move_. -More precisely, hold Ctrl while pressing the left-click button. Until -the left-click button is released, all mouse movements scale and rotate the -content (if supported by the app) relative to the center of the screen. +More precisely, hold down Ctrl while pressing the left-click button. +Until the left-click button is released, all mouse movements scale and rotate +the 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. +Technically, _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. +By default, _scrcpy_ uses Android key or text injection: it works everywhere, +but is limited to ASCII. -Alternatively, scrcpy can simulate a physical USB 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. +Alternatively, `scrcpy` can simulate a physical USB 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. +However, it only works if the device is connected via USB. Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it is not possible to open a USB device if it is already open by another process -like the adb daemon). +like the _adb daemon_). To enable this mode: @@ -862,7 +864,7 @@ a physical keyboard is connected). Similarly to the physical keyboard simulation, it is possible to simulate a physical mouse. Likewise, it only works if the device is connected by USB. -By default, scrcpy uses Android mouse events injection, using absolute +By default, _scrcpy_ uses Android mouse events injection with absolute coordinates. By simulating a physical mouse, a mouse pointer appears on the Android device, and relative mouse motion, clicks and scrolls are injected. @@ -873,7 +875,7 @@ scrcpy --hid-mouse scrcpy -M # short version ``` -You could also add `--forward-all-clicks` to [forward all mouse +You can also add `--forward-all-clicks` to [forward all mouse buttons][forward_all_clicks]. [forward_all_clicks]: #right-click-and-middle-click @@ -892,7 +894,7 @@ It is possible to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. -In this mode, _adb_ (USB debugging) is not necessary, and mirroring is disabled. +In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. To enable OTG mode: @@ -918,7 +920,7 @@ connected by USB. #### Text injection preference -There are two kinds of [events][textevents] generated when typing text: +Two kinds of [events][textevents] are generated when typing text: - _key events_, signaling that a key is pressed or released; - _text events_, signaling that a text has been entered. @@ -1075,7 +1077,7 @@ handled by the active application. ## Custom paths -To use a specific _adb_ binary, configure its path in the environment variable +To use a specific `adb` binary, configure its path in the environment variable `ADB`: ```bash @@ -1088,7 +1090,7 @@ To override the path of the `scrcpy-server` file, configure its path in To override the icon, configure its path in `SCRCPY_ICON_PATH`. -## Why _scrcpy_? +## Why the name _scrcpy_? A colleague challenged me to find a name as unpronounceable as [gnirehtet]. @@ -1148,7 +1150,7 @@ If you encounter a bug, please read the [FAQ] first, then open an [issue]. [issue]: https://github.com/Genymobile/scrcpy/issues -For general questions or discussions, you could also use: +For general questions or discussions, you can also use: - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) From 7d8b72d4a644baf0b8ced72c2313605ad0a32a12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 15 Apr 2022 15:51:01 +0200 Subject: [PATCH 0515/1133] Adapt event injection to Android 13 Using the "input" service results in a permission error in Android 13. Use the InputManager instance (retrieved by InputManager.getInstance()) instead. Fixes #3186 PR #3190 --- .../com/genymobile/scrcpy/wrappers/InputManager.java | 5 ++--- .../com/genymobile/scrcpy/wrappers/ServiceManager.java | 9 ++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 61168993..c42f9de1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; -import android.os.IInterface; import android.view.InputEvent; import java.lang.reflect.InvocationTargetException; @@ -14,13 +13,13 @@ public final class InputManager { public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; - private final IInterface manager; + private final android.hardware.input.InputManager manager; private Method injectInputEventMethod; private boolean alternativeInjectInputEventMethod; private static Method setDisplayIdMethod; - public InputManager(IInterface manager) { + public InputManager(android.hardware.input.InputManager manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 6f4b9c04..ea2a0784 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.os.IBinder; import android.os.IInterface; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -56,7 +57,13 @@ public final class ServiceManager { public InputManager getInputManager() { if (inputManager == null) { - inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager")); + try { + Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance"); + android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null); + inputManager = new InputManager(im); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } } return inputManager; } From 6a4a4a283d4b79f41cf46fc6eb314af4b36cc1a5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 16 Apr 2022 09:28:24 +0200 Subject: [PATCH 0516/1133] Remove obsolete alternative injection method The previous commit replaced the IInterface instance (the "input" service) by the InputManager instance (retrieved by InputManager.getInstance()). Both define an "injectInputEvent" method, but the alternate version (probably) does not concern the InputManager. This reverts commit b7a06278fee0dbfbb18b651ad563d9fb0797c8bc. PR #3190 --- .../com/genymobile/scrcpy/wrappers/InputManager.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index c42f9de1..38e96d45 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -15,7 +15,6 @@ public final class InputManager { private final android.hardware.input.InputManager manager; private Method injectInputEventMethod; - private boolean alternativeInjectInputEventMethod; private static Method setDisplayIdMethod; @@ -25,12 +24,7 @@ public final class InputManager { private Method getInjectInputEventMethod() throws NoSuchMethodException { if (injectInputEventMethod == null) { - try { - injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); - } catch (NoSuchMethodException e) { - injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class); - alternativeInjectInputEventMethod = true; - } + injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); } return injectInputEventMethod; } @@ -38,10 +32,6 @@ public final class InputManager { public boolean injectInputEvent(InputEvent inputEvent, int mode) { try { Method method = getInjectInputEventMethod(); - if (alternativeInjectInputEventMethod) { - // See - return (boolean) method.invoke(manager, inputEvent, mode, 0); - } return (boolean) method.invoke(manager, inputEvent, mode); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); From b8d78743f7fbe7aefed957f41e68efc4791573ba Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Apr 2022 13:29:04 +0200 Subject: [PATCH 0517/1133] Upgrade platform-tools (33.0.1) for Windows PR #3206 --- app/prebuilt-deps/prepare-adb.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh index 2e7997e1..6a1f4896 100755 --- a/app/prebuilt-deps/prepare-adb.sh +++ b/app/prebuilt-deps/prepare-adb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=platform-tools-31.0.3 +DEP_DIR=platform-tools-33.0.1 -FILENAME=platform-tools_r31.0.3-windows.zip -SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 +FILENAME=platform-tools_r33.0.1-windows.zip +SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42 if [[ -d "$DEP_DIR" ]] then From 4db97531e887ef3455548ce09dd6cbff54b630ed Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 22 Apr 2022 13:33:50 +0200 Subject: [PATCH 0518/1133] Upgrade libusb (1.0.26) for Windows Upgrade and enable libusb support for Windows 32-bit builds. Refs #3011 Fixes #3204 PR #3206 --- app/meson.build | 4 ++-- app/prebuilt-deps/prepare-libusb.sh | 20 +++++++++++++------- cross_win32.txt | 4 ++-- cross_win64.txt | 4 ++-- release.mk | 6 ++---- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/meson.build b/app/meson.build index e34b1893..f5d76c61 100644 --- a/app/meson.build +++ b/app/meson.build @@ -143,12 +143,12 @@ else prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root') - libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/dll' + libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include' libusb = declare_dependency( dependencies: [ - cc.find_library('libusb-1.0', dirs: libusb_bin_dir), + cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir), ], include_directories: include_directories(libusb_include_dir) ) diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh index 54ead536..a0c3721d 100755 --- a/app/prebuilt-deps/prepare-libusb.sh +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=libusb-1.0.25 +DEP_DIR=libusb-1.0.26 -FILENAME=libusb-1.0.25.7z -SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d +FILENAME=libusb-1.0.26-binaries.7z +SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5 if [[ -d "$DEP_DIR" ]] then @@ -17,12 +17,18 @@ then exit 0 fi -get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM" +get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM" mkdir "$DEP_DIR" cd "$DEP_DIR" +# include/ is the same in all folders of the archive 7z x "../$FILENAME" \ - MinGW32/dll/libusb-1.0.dll \ - MinGW64/dll/libusb-1.0.dll \ - include / + libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \ + libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \ + libusb-1.0.26-binaries/libusb-MinGW-x64/include/ + +mv libusb-1.0.26-binaries/libusb-MinGW-Win32/bin MinGW-Win32 +mv libusb-1.0.26-binaries/libusb-MinGW-x64/bin MinGW-x64 +mv libusb-1.0.26-binaries/libusb-MinGW-x64/include . +rm -rf libusb-1.0.26-binaries diff --git a/cross_win32.txt b/cross_win32.txt index 750dbd78..a2341f21 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -21,5 +21,5 @@ ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' -prebuilt_libusb_root = 'libusb-1.0.25' -prebuilt_libusb = prebuilt_libusb_root + '/MinGW32' +prebuilt_libusb_root = 'libusb-1.0.26' +prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 114b0c22..0404aedc 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -21,5 +21,5 @@ ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.0' prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' -prebuilt_libusb_root = 'libusb-1.0.25' -prebuilt_libusb = prebuilt_libusb_root + '/MinGW64' +prebuilt_libusb_root = 'libusb-1.0.26' +prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64' diff --git a/release.mk b/release.mk index caf6ab1a..1c550afb 100644 --- a/release.mk +++ b/release.mk @@ -75,12 +75,10 @@ prepare-deps-win64: @app/prebuilt-deps/prepare-libusb.sh build-win32: prepare-deps-win32 - # -Dusb=false because of libusb-win32 build issue, cf #3011 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ meson "$(WIN32_BUILD_DIR)" \ --cross-file cross_win32.txt \ --buildtype release --strip -Db_lto=true \ - -Dusb=false \ -Dcompile_server=false \ -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" @@ -111,7 +109,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - #cp app/prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -130,7 +128,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.25/MinGW64/dll/libusb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ From c05981587f1a322c9d7c16f5ec6e9392f7e98849 Mon Sep 17 00:00:00 2001 From: Sean Wei Date: Mon, 25 Apr 2022 16:37:03 +0800 Subject: [PATCH 0519/1133] Fix typos in Indonesian README Fix `code` blocks. PR #3216 Signed-off-by: Romain Vimont --- README.id.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.id.md b/README.id.md index 9657b95a..1230f0cc 100644 --- a/README.id.md +++ b/README.id.md @@ -218,7 +218,7 @@ variation] does not impact the recorded file. #### Wireless -_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP: +_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan `adb` dapat [terhubung] ke perangkat melalui TCP / IP: 1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda. 2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status). @@ -281,7 +281,7 @@ Dari terminal lain: scrcpy ``` -Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`): +Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan `-R`): ```bash adb kill-server # matikan server adb lokal di 5037 @@ -579,7 +579,7 @@ Lihat juga [Masalah #14]. Dalam daftar berikut, MOD adalah pengubah pintasan. Secara default, ini (kiri) Alt atau (kiri) Super. -Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`,`rctrl`, `lalt`,` ralt`, `lsuper` dan` rsuper`. Sebagai contoh: +Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` dan `rsuper`. Sebagai contoh: ```bash # gunakan RCtrl untuk jalan pintas From 85512b14670db9b41606ec9841b8755a86633536 Mon Sep 17 00:00:00 2001 From: Sean Wei Date: Mon, 25 Apr 2022 16:39:12 +0800 Subject: [PATCH 0520/1133] Fix typo in German README Replace "Wifi" with "Wi-Fi", as in English and other translations. PR #3217 Signed-off-by: Romain Vimont --- README.de.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.de.md b/README.de.md index 92d00df2..1299ae0e 100644 --- a/README.de.md +++ b/README.de.md @@ -375,7 +375,7 @@ Dies finden automatisch das Gerät und aktiviert den TCP/IP-Modus. Anschließend Alternativ kann die TCP/IP-Verbindung auch manuell per `adb` aktiviert werden: -1. Gerät mit demselben Wifi wie den Computer verbinden. +1. Gerät mit demselben Wi-Fi wie den Computer verbinden. 2. IP-Adresse des Gerätes herausfinden, entweder über Einstellungen → Über das Telefon → Status, oder indem dieser Befehl ausgeführt wird: ```bash From a90dfb46bc2266f93eae5585f84b6a9265dd05c2 Mon Sep 17 00:00:00 2001 From: Sean Wei Date: Mon, 25 Apr 2022 16:39:15 +0800 Subject: [PATCH 0521/1133] Fix GitHub case in BUILD Replace "Github" with "GitHub". PR #3218 Signed-off-by: Romain Vimont --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 90a7d18e..bd007155 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,7 +46,7 @@ sudo ninja -Cbuild-auto uninstall ### `master` The `master` branch concerns the latest release, and is the home page of the -project on Github. +project on GitHub. ### `dev` From 326897a0d4ab3ea0cf741cd87f01d2703db38374 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 Apr 2022 18:33:08 +0200 Subject: [PATCH 0522/1133] Add missing mouse shortcuts in --help Document 4th-click and 5th-click shortcuts. Fixes #3122 --- app/src/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 5dda86e5..2983af73 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -577,7 +577,7 @@ static const struct sc_shortcut shortcuts[] = { .text = "Click on BACK", }, { - .shortcuts = { "MOD+s" }, + .shortcuts = { "MOD+s", "4th-click" }, .text = "Click on APP_SWITCH", }, { @@ -613,7 +613,7 @@ static const struct sc_shortcut shortcuts[] = { .text = "Rotate device screen", }, { - .shortcuts = { "MOD+n" }, + .shortcuts = { "MOD+n", "5th-click" }, .text = "Expand notification panel", }, { From 0fca2ad830dbcc24a9a38b974d922359be40b0d2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 Apr 2022 15:08:30 +0200 Subject: [PATCH 0523/1133] Add option to not power on on start By default, on start, the device is powered on. To prevent this behavior, add a new option --no-power-on. Fixes #3148 PR #3210 --- README.md | 10 ++++++++++ app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 9 +++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 4 ++++ app/src/server.h | 1 + .../main/java/com/genymobile/scrcpy/Controller.java | 6 ++++-- .../src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 6 +++++- 13 files changed, 51 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 614d1ed4..846b18ac 100644 --- a/README.md +++ b/README.md @@ -721,6 +721,16 @@ To turn the device screen off when closing scrcpy: scrcpy --power-off-on-close ``` +#### Power on on start + +By default, on start, the device is powered on. + +To prevent this behavior: + +```bash +scrcpy --no-power-on +``` + #### Show touches diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 464bc532..3e75cbb0 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -29,6 +29,7 @@ _scrcpy() { -N --no-display --no-key-repeat --no-mipmaps + --no-power-on --otg -p --port= --power-off-on-close diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 77e6027a..097c80d6 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -35,6 +35,7 @@ arguments=( {-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]' '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' + '--no-power-on[Do not power on the device on start]' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' '--power-off-on-close[Turn the device screen off when closing scrcpy]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index eb164475..7cb893b7 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -180,6 +180,10 @@ Do not forward repeated key events when a key is held down. .B \-\-no\-mipmaps If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. +.TP +.B \-\-no\-power\-on +Do not power on the device on start. + .TP .B \-\-otg Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. diff --git a/app/src/cli.c b/app/src/cli.c index 2983af73..538dd3e7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -56,6 +56,7 @@ #define OPT_OTG 1036 #define OPT_NO_CLEANUP 1037 #define OPT_PRINT_FPS 1038 +#define OPT_NO_POWER_ON 1039 struct sc_option { char shortopt; @@ -302,6 +303,11 @@ static const struct sc_option options[] = { "mipmaps are automatically generated to improve downscaling " "quality. This option disables the generation of mipmaps.", }, + { + .longopt_id = OPT_NO_POWER_ON, + .longopt = "no-power-on", + .text = "Do not power on the device on start.", + }, { .longopt_id = OPT_OTG, .longopt = "otg", @@ -1598,6 +1604,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_CLEANUP: opts->cleanup = false; break; + case OPT_NO_POWER_ON: + opts->power_on = false; + break; case OPT_PRINT_FPS: opts->start_fps_counter = true; break; diff --git a/app/src/options.c b/app/src/options.c index 6651020f..8b2624d9 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -64,4 +64,5 @@ const struct scrcpy_options scrcpy_options_default = { .select_usb = false, .cleanup = true, .start_fps_counter = false, + .power_on = true, }; diff --git a/app/src/options.h b/app/src/options.h index f63e5c42..7e542c06 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -139,6 +139,7 @@ struct scrcpy_options { bool select_tcpip; bool cleanup; bool start_fps_counter; + bool power_on; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8fbfe394..3588e9ae 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -324,6 +324,7 @@ scrcpy(struct scrcpy_options *options) { .tcpip = options->tcpip, .tcpip_dst = options->tcpip_dst, .cleanup = options->cleanup, + .power_on = options->power_on, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index c02390a4..663ef18b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -248,6 +248,10 @@ execute_server(struct sc_server *server, // By default, cleanup is true ADD_PARAM("cleanup=false"); } + if (!params->power_on) { + // By default, power_on is true + ADD_PARAM("power_on=false"); + } #undef ADD_PARAM diff --git a/app/src/server.h b/app/src/server.h index 5f630ca8..49ba83c1 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -47,6 +47,7 @@ struct sc_server_params { bool select_usb; bool select_tcpip; bool cleanup; + bool power_on; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 481c512f..913371ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -22,6 +22,7 @@ public class Controller { private final DesktopConnection connection; private final DeviceMessageSender sender; private final boolean clipboardAutosync; + private final boolean powerOn; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -32,10 +33,11 @@ public class Controller { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) { + public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) { this.device = device; this.connection = connection; this.clipboardAutosync = clipboardAutosync; + this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(connection); } @@ -56,7 +58,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device - if (!Device.isScreenOn()) { + if (powerOn && !Device.isScreenOn()) { device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); // dirty hack diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 4842b635..d1607c20 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -22,6 +22,7 @@ public class Options { private boolean clipboardAutosync = true; private boolean downsizeOnError = true; private boolean cleanup = true; + private boolean powerOn = true; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -164,6 +165,14 @@ public class Options { this.cleanup = cleanup; } + public boolean getPowerOn() { + return powerOn; + } + + public void setPowerOn(boolean powerOn) { + this.powerOn = powerOn; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 60f485d8..1df91552 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -82,7 +82,7 @@ public final class Server { Thread controllerThread = null; Thread deviceMessageSenderThread = null; if (control) { - final Controller controller = new Controller(device, connection, options.getClipboardAutosync()); + final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); // asynchronous controllerThread = startController(controller); @@ -248,6 +248,10 @@ public final class Server { boolean cleanup = Boolean.parseBoolean(value); options.setCleanup(cleanup); break; + case "power_on": + boolean powerOn = Boolean.parseBoolean(value); + options.setPowerOn(powerOn); + break; case "send_device_meta": boolean sendDeviceMeta = Boolean.parseBoolean(value); options.setSendDeviceMeta(sendDeviceMeta); From c6d97111097514cdf333689900abafd57ad354ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:10:45 +0200 Subject: [PATCH 0524/1133] Create OTG window with HIGHDPI flag This will avoid poor quality with HiDPI displays. PR #3219 --- app/src/usb/screen_otg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 561a84ca..93b0ba6e 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -72,7 +72,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen, int width = 256; int height = 256; - uint32_t window_flags = 0; + uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; } From fc8942aa03dd18b20d6b2e450b13ba56d2c3489e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 25 Apr 2022 18:44:42 +0200 Subject: [PATCH 0525/1133] Apply requested window size in OTG mode Fixes #3099 PR #3219 --- app/src/usb/scrcpy_otg.c | 2 ++ app/src/usb/screen_otg.c | 9 +++++++-- app/src/usb/screen_otg.h | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index db5e64d8..052facff 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -162,6 +162,8 @@ scrcpy_otg(struct scrcpy_options *options) { .always_on_top = options->always_on_top, .window_x = options->window_x, .window_y = options->window_y, + .window_width = options->window_width, + .window_height = options->window_height, .window_borderless = options->window_borderless, }; diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 93b0ba6e..450cbe1e 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -69,8 +69,8 @@ sc_screen_otg_init(struct sc_screen_otg *screen, ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED ? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; - int width = 256; - int height = 256; + int width = params->window_width ? params->window_width : 256; + int height = params->window_height ? params->window_height : 256; uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { @@ -97,6 +97,11 @@ sc_screen_otg_init(struct sc_screen_otg *screen, if (icon) { SDL_SetWindowIcon(screen->window, icon); + if (!SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) { + LOGW("Could not set renderer logical size: %s", SDL_GetError()); + // don't fail + } + screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon); scrcpy_icon_destroy(icon); if (!screen->texture) { diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index 0973ce59..a0acf40b 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -29,6 +29,8 @@ struct sc_screen_otg_params { bool always_on_top; int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED + uint16_t window_width; + uint16_t window_height; bool window_borderless; }; From 436b368f9df4469274502f29626586b48db44a0c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:11:42 +0200 Subject: [PATCH 0526/1133] Make OTG window resizable PR #3219 --- app/src/usb/screen_otg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 450cbe1e..abdfc9a4 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -72,7 +72,8 @@ sc_screen_otg_init(struct sc_screen_otg *screen, int width = params->window_width ? params->window_width : 256; int height = params->window_height ? params->window_height : 256; - uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI; + uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI + | SDL_WINDOW_RESIZABLE; if (params->always_on_top) { window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; } From 854a56e58893da352410d6e01708704f550fd6e4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:12:37 +0200 Subject: [PATCH 0527/1133] Enable linear filtering in OTG mode This improves the icon quality with non-standard window size. PR #3219 --- app/src/usb/scrcpy_otg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 052facff..ebcfa36f 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -55,6 +55,10 @@ scrcpy_otg(struct scrcpy_options *options) { const char *serial = options->serial; + if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { + LOGW("Could not enable linear filtering"); + } + // Minimal SDL initialization if (SDL_Init(SDL_INIT_EVENTS)) { LOGE("Could not initialize SDL: %s", SDL_GetError()); From 91706ae3d079abafa009c574845ddb12377588aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:23:59 +0200 Subject: [PATCH 0528/1133] Upgrade SDL (2.0.22) for Windows Include the latest version of SDL in Windows releases. --- app/prebuilt-deps/prepare-sdl.sh | 6 +++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index c414c854..0b41fe5c 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.0.20 +DEP_DIR=SDL2-2.0.22 -FILENAME=SDL2-devel-2.0.20-mingw.tar.gz -SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 +FILENAME=SDL2-devel-2.0.22-mingw.tar.gz +SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index a2341f21..d1d5d11b 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -20,6 +20,6 @@ ffmpeg_avcodec = 'avcodec-58' ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' -prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 0404aedc..f673c530 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -20,6 +20,6 @@ ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.0' -prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64' diff --git a/release.mk b/release.mk index 1c550afb..3e3d14db 100644 --- a/release.mk +++ b/release.mk @@ -108,7 +108,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 @@ -127,7 +127,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 From f9e3275d4e77394e19ac789ea46e16a244ec4dfe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:32:14 +0200 Subject: [PATCH 0529/1133] Upgrade FFmpeg (5.0.1) for Windows 64-bit Use the latest version of FFmpeg in Windows 64-bit releases. --- app/prebuilt-deps/prepare-ffmpeg-win64.sh | 11 ++++++----- cross_win64.txt | 2 +- release.mk | 10 +++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg-win64.sh b/app/prebuilt-deps/prepare-ffmpeg-win64.sh index a62eb261..8e6c2440 100755 --- a/app/prebuilt-deps/prepare-ffmpeg-win64.sh +++ b/app/prebuilt-deps/prepare-ffmpeg-win64.sh @@ -6,10 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=ffmpeg-win64-5.0 +VERSION=5.0.1 +DEP_DIR=ffmpeg-win64-$VERSION -FILENAME=ffmpeg-5.0-full_build-shared.7z -SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a +FILENAME=ffmpeg-$VERSION-full_build-shared.7z +SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c if [[ -d "$DEP_DIR" ]] then @@ -17,13 +18,13 @@ then exit 0 fi -get_file "https://github.com/GyanD/codexffmpeg/releases/download/5.0/$FILENAME" \ +get_file "https://github.com/GyanD/codexffmpeg/releases/download/$VERSION/$FILENAME" \ "$FILENAME" "$SHA256SUM" mkdir "$DEP_DIR" cd "$DEP_DIR" -ZIP_PREFIX=ffmpeg-5.0-full_build-shared +ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared 7z x "../$FILENAME" \ "$ZIP_PREFIX"/bin/avutil-57.dll \ "$ZIP_PREFIX"/bin/avcodec-59.dll \ diff --git a/cross_win64.txt b/cross_win64.txt index f673c530..cc9e4a0b 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -19,7 +19,7 @@ endian = 'little' ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' -prebuilt_ffmpeg = 'ffmpeg-win64-5.0' +prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1' prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64' diff --git a/release.mk b/release.mk index 3e3d14db..e8c5f177 100644 --- a/release.mk +++ b/release.mk @@ -119,11 +119,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 349dcd8e7b501d39ab2b854378a1974e38f3dac2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:49:34 +0200 Subject: [PATCH 0530/1133] Update installed files list in BUILD documentation --- BUILD.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 90a7d18e..e69c180b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -305,11 +305,14 @@ After a successful build, you can install _scrcpy_ on the system: sudo ninja -Cx install # without sudo on Windows ``` -This installs three files: - - - `/usr/local/bin/scrcpy` - - `/usr/local/share/scrcpy/scrcpy-server` - - `/usr/local/share/man/man1/scrcpy.1` +This installs several files: + + - `/usr/local/bin/scrcpy` (main app) + - `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device) + - `/usr/local/share/man/man1/scrcpy.1` (manpage) + - `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon) + - `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion) + - `/usr/local/share/bash-completion/completions/scrcpy` (bash completion) You can then [run](README.md#run) _scrcpy_. From 471a360099f73d6a3fa3ea5abb7d5b8f59b23415 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 19:50:07 +0200 Subject: [PATCH 0531/1133] Use quotes for commands in documentation --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index e69c180b..f079518e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -314,7 +314,7 @@ This installs several files: - `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion) - `/usr/local/share/bash-completion/completions/scrcpy` (bash completion) -You can then [run](README.md#run) _scrcpy_. +You can then [run](README.md#run) `scrcpy`. ### Uninstall From 05d84084efb07d07673123427c31331ca0de4f2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 20:18:13 +0200 Subject: [PATCH 0532/1133] Fix release script for platform-tools 33.0.1 These paths were not updated by commit b8d78743f7fbe7aefed957f41e68efc4791573ba. --- release.mk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/release.mk b/release.mk index e8c5f177..94a9680e 100644 --- a/release.mk +++ b/release.mk @@ -105,9 +105,9 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -124,9 +124,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 76b3fcf9863ce42de4057b583f9261104c78cda1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 20:40:20 +0200 Subject: [PATCH 0533/1133] Fix inverted check SDL_RenderSetLogicalSize() returns 0 on success. Refs fc8942aa03dd18b20d6b2e450b13ba56d2c3489e --- app/src/usb/screen_otg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index abdfc9a4..a8f72296 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -98,7 +98,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen, if (icon) { SDL_SetWindowIcon(screen->window, icon); - if (!SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) { + if (SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) { LOGW("Could not set renderer logical size: %s", SDL_GetError()); // don't fail } From 2f038c834a5f891d20095ed1fe7c44a05d6760e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 20:41:51 +0200 Subject: [PATCH 0534/1133] Revert "Make OTG window resizable" On Windows and macOS, resizing blocks the event loop. Handling it properly would require the same workaround as done in screen.c. This reverts commit 436b368f9df4469274502f29626586b48db44a0c. --- app/src/usb/screen_otg.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index a8f72296..e1d5cb01 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -72,8 +72,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen, int width = params->window_width ? params->window_width : 256; int height = params->window_height ? params->window_height : 256; - uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI - | SDL_WINDOW_RESIZABLE; + uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; } From ef13d394fd83a2c534d86f2dbc188f4324a6ee34 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 20:09:48 +0200 Subject: [PATCH 0535/1133] Bump version to 1.24 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 41101e70..6c731003 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "1.23" + VALUE "ProductVersion", "1.24" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index e7270b5a..bfca6134 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.23', + version: '1.24', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index f3eb6d64..dbc8261f 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 12300 - versionName "1.23" + versionCode 12400 + versionName "1.24" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index b2bafa3f..c881e38a 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.23 +SCRCPY_VERSION_NAME=1.24 PLATFORM=${ANDROID_PLATFORM:-31} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} From 3a99e129e628911dde73f486aece1ca3ebe06ead Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 21:03:03 +0200 Subject: [PATCH 0536/1133] Update links to v1.24 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index 84d1acac..e68e8834 100644 --- a/BUILD.md +++ b/BUILD.md @@ -272,10 +272,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.23`][direct-scrcpy-server] - _(SHA-256: 2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68)_ + - [`scrcpy-server-v1.24`][direct-scrcpy-server] + _(SHA-256: ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056)_ -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 375c465d..ba5bdefd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.23) +# scrcpy (v1.24) scrcpy @@ -106,10 +106,10 @@ process][BUILD_simple]). For Windows, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.23.zip`][direct-win64] - _(SHA-256: d2f601b1d0157faf65153d8a093d827fd65aec5d5842d677ac86fb2b5b7704cc)_ + - [`scrcpy-win64-v1.24.zip`][direct-win64] + _(SHA-256: 6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367)_ -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-win64-v1.23.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index 9b1ed8e7..88262f8e 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.23/scrcpy-server-v1.23 -PREBUILT_SERVER_SHA256=2a913fd47478c0b306fca507cb0beb625e49a19ff9fc7ab904e36ef5b9fe7e68 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24 +PREBUILT_SERVER_SHA256=ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From faf4535487926853250ebe842c76a40e7b5f8ceb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 28 Apr 2022 21:25:40 +0200 Subject: [PATCH 0537/1133] Reduce SHA-256 size in README and BUILD This avoids breaking the page layout on GitHub. --- BUILD.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index e68e8834..1d5d970b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -273,7 +273,7 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - [`scrcpy-server-v1.24`][direct-scrcpy-server] - _(SHA-256: ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056)_ + SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056` [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24 diff --git a/README.md b/README.md index ba5bdefd..20ad0f9c 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ For Windows, a prebuilt archive with all the dependencies (including `adb`) is available: - [`scrcpy-win64-v1.24.zip`][direct-win64] - _(SHA-256: 6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367)_ + SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367` [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip From 69fb5f6ee118f1e732c58f3b190e5cb4f1edc575 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 May 2022 21:03:42 +0200 Subject: [PATCH 0538/1133] Fix function declarations Add missing void in function parameters list. --- app/tests/test_adb_parser.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 1a127632..43abfe0a 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -5,7 +5,7 @@ #include "adb/adb_device.h" #include "adb/adb_parser.h" -static void test_adb_devices() { +static void test_adb_devices(void) { char output[] = "List of devices attached\n" "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " @@ -31,7 +31,7 @@ static void test_adb_devices() { sc_adb_devices_destroy(&vec); } -static void test_adb_devices_cr() { +static void test_adb_devices_cr(void) { char output[] = "List of devices attached\r\n" "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " @@ -57,7 +57,7 @@ static void test_adb_devices_cr() { sc_adb_devices_destroy(&vec); } -static void test_adb_devices_daemon_start() { +static void test_adb_devices_daemon_start(void) { char output[] = "* daemon not running; starting now at tcp:5037\n" "* daemon started successfully\n" @@ -78,7 +78,7 @@ static void test_adb_devices_daemon_start() { sc_adb_devices_destroy(&vec); } -static void test_adb_devices_daemon_start_mixed() { +static void test_adb_devices_daemon_start_mixed(void) { char output[] = "List of devices attached\n" "adb server version (41) doesn't match this client (39); killing...\n" @@ -105,7 +105,7 @@ static void test_adb_devices_daemon_start_mixed() { sc_adb_devices_destroy(&vec); } -static void test_adb_devices_without_eol() { +static void test_adb_devices_without_eol(void) { char output[] = "List of devices attached\n" "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " @@ -124,7 +124,7 @@ static void test_adb_devices_without_eol() { sc_adb_devices_destroy(&vec); } -static void test_adb_devices_without_header() { +static void test_adb_devices_without_header(void) { char output[] = "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "device:MyDevice transport_id:1\n"; @@ -134,7 +134,7 @@ static void test_adb_devices_without_header() { assert(!ok); } -static void test_adb_devices_corrupted() { +static void test_adb_devices_corrupted(void) { char output[] = "List of devices attached\n" "corrupted_garbage\n"; @@ -145,7 +145,7 @@ static void test_adb_devices_corrupted() { assert(vec.size == 0); } -static void test_adb_devices_spaces() { +static void test_adb_devices_spaces(void) { char output[] = "List of devices attached\n" "0123456789abcdef unauthorized usb:1-4 transport_id:3\n"; @@ -163,7 +163,7 @@ static void test_adb_devices_spaces() { sc_adb_devices_destroy(&vec); } -static void test_get_ip_single_line() { +static void test_get_ip_single_line(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34\r\r\n"; @@ -173,7 +173,7 @@ static void test_get_ip_single_line() { free(ip); } -static void test_get_ip_single_line_without_eol() { +static void test_get_ip_single_line_without_eol(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34"; @@ -183,7 +183,7 @@ static void test_get_ip_single_line_without_eol() { free(ip); } -static void test_get_ip_single_line_with_trailing_space() { +static void test_get_ip_single_line_with_trailing_space(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34 \n"; @@ -193,7 +193,7 @@ static void test_get_ip_single_line_with_trailing_space() { free(ip); } -static void test_get_ip_multiline_first_ok() { +static void test_get_ip_multiline_first_ok(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.1.2\r\n" "10.0.0.0/24 dev rmnet proto kernel scope link src " @@ -205,7 +205,7 @@ static void test_get_ip_multiline_first_ok() { free(ip); } -static void test_get_ip_multiline_second_ok() { +static void test_get_ip_multiline_second_ok(void) { char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src " "10.0.0.3\r\n" "192.168.1.0/24 dev wlan0 proto kernel scope link src " @@ -217,7 +217,7 @@ static void test_get_ip_multiline_second_ok() { free(ip); } -static void test_get_ip_no_wlan() { +static void test_get_ip_no_wlan(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34\r\r\n"; @@ -225,7 +225,7 @@ static void test_get_ip_no_wlan() { assert(!ip); } -static void test_get_ip_no_wlan_without_eol() { +static void test_get_ip_no_wlan_without_eol(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34"; @@ -233,7 +233,7 @@ static void test_get_ip_no_wlan_without_eol() { assert(!ip); } -static void test_get_ip_truncated() { +static void test_get_ip_truncated(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "\n"; From 55e65fa270643519d55d12d73d628d02e73de464 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 May 2022 21:04:10 +0200 Subject: [PATCH 0539/1133] Add missing return 0 in tests --- app/tests/test_adb_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 43abfe0a..6506122f 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -262,4 +262,6 @@ int main(int argc, char *argv[]) { test_get_ip_no_wlan(); test_get_ip_no_wlan_without_eol(); test_get_ip_truncated(); + + return 0; } From b1d8c7278054f33fffb57b163a1bf07aaba53305 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 May 2022 21:20:27 +0200 Subject: [PATCH 0540/1133] Rename function to simplify For consistency with sc_adb_parse_device(), do not include "from_output" in the function name. --- app/src/adb/adb.c | 2 +- app/src/adb/adb_parser.c | 2 +- app/src/adb/adb_parser.h | 2 +- app/tests/test_adb_parser.c | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 344f7fcc..8b92a2b0 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -710,5 +710,5 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { // It is parsed as a NUL-terminated string buf[r] = '\0'; - return sc_adb_parse_device_ip_from_output(buf); + return sc_adb_parse_device_ip(buf); } diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index 933eafbb..ab121347 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -199,7 +199,7 @@ sc_adb_parse_device_ip_from_line(char *line) { } char * -sc_adb_parse_device_ip_from_output(char *str) { +sc_adb_parse_device_ip(char *str) { size_t idx_line = 0; while (str[idx_line] != '\0') { char *line = &str[idx_line]; diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index e0cc389b..f20349f6 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -25,6 +25,6 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec); * Warning: this function modifies the buffer for optimization purposes. */ char * -sc_adb_parse_device_ip_from_output(char *str); +sc_adb_parse_device_ip(char *str); #endif diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index 6506122f..d95e7ef2 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -167,7 +167,7 @@ static void test_get_ip_single_line(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34\r\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -177,7 +177,7 @@ static void test_get_ip_single_line_without_eol(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -187,7 +187,7 @@ static void test_get_ip_single_line_with_trailing_space(void) { char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.12.34 \n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(ip); assert(!strcmp(ip, "192.168.12.34")); free(ip); @@ -199,7 +199,7 @@ static void test_get_ip_multiline_first_ok(void) { "10.0.0.0/24 dev rmnet proto kernel scope link src " "10.0.0.2\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(ip); assert(!strcmp(ip, "192.168.1.2")); free(ip); @@ -211,7 +211,7 @@ static void test_get_ip_multiline_second_ok(void) { "192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.1.3\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(ip); assert(!strcmp(ip, "192.168.1.3")); free(ip); @@ -221,7 +221,7 @@ static void test_get_ip_no_wlan(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34\r\r\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(!ip); } @@ -229,7 +229,7 @@ static void test_get_ip_no_wlan_without_eol(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(!ip); } @@ -237,7 +237,7 @@ static void test_get_ip_truncated(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "\n"; - char *ip = sc_adb_parse_device_ip_from_output(ip_route); + char *ip = sc_adb_parse_device_ip(ip_route); assert(!ip); } From af4b7855e115878b7b87c7fe2e719d62c07af40d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Jun 2022 15:01:03 +0200 Subject: [PATCH 0541/1133] Remove unused stream.h The file was not removed by 7dec225cebbf208cf1d607bd45fb49e3a9fa1167. --- app/src/stream.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-) delete mode 100644 app/src/stream.h diff --git a/app/src/stream.h b/app/src/stream.h deleted file mode 100644 index bdcefe39..00000000 --- a/app/src/stream.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef STREAM_H -#define STREAM_H - -#include "common.h" - -#include -#include -#include -#include - -#include "trait/packet_sink.h" -#include "util/net.h" -#include "util/thread.h" - -#define STREAM_MAX_SINKS 2 - -struct stream { - sc_socket socket; - sc_thread thread; - - struct sc_packet_sink *sinks[STREAM_MAX_SINKS]; - unsigned sink_count; - - AVCodecContext *codec_ctx; - AVCodecParserContext *parser; - // successive packets may need to be concatenated, until a non-config - // packet is available - AVPacket *pending; - - const struct stream_callbacks *cbs; - void *cbs_userdata; -}; - -struct stream_callbacks { - void (*on_eos)(struct stream *stream, void *userdata); -}; - -void -stream_init(struct stream *stream, sc_socket socket, - const struct stream_callbacks *cbs, void *cbs_userdata); - -void -stream_add_sink(struct stream *stream, struct sc_packet_sink *sink); - -bool -stream_start(struct stream *stream); - -void -stream_join(struct stream *stream); - -#endif From a83c3e30f3afbe1e0008dc50f769bb06fcdf0a87 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jun 2022 08:36:58 +0200 Subject: [PATCH 0542/1133] Fix environment variable configuration in FAQ In bash, the variable is set using "export". --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 400c4014..0cebeeb7 100644 --- a/FAQ.md +++ b/FAQ.md @@ -103,7 +103,7 @@ You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to use a specific `adb` binary, by setting the `ADB` environment variable: ```bash -set ADB=/path/to/your/adb +export ADB=/path/to/your/adb scrcpy ``` From ed84e18b1ae3e51d368f8c7bc88ba4db088e6855 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jun 2022 08:39:40 +0200 Subject: [PATCH 0543/1133] Document envvars for all platforms Document how to set environment variables from the terminal for bash, cmd and PowerShell. --- FAQ.md | 13 +++++++++++++ README.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/FAQ.md b/FAQ.md index 0cebeeb7..f6a8f226 100644 --- a/FAQ.md +++ b/FAQ.md @@ -103,10 +103,23 @@ You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to use a specific `adb` binary, by setting the `ADB` environment variable: ```bash +# in bash export ADB=/path/to/your/adb scrcpy ``` +```cmd +:: in cmd +set ADB=C:\path\to\your\adb.exe +scrcpy +``` + +```powershell +# in PowerShell +$env:ADB = 'C:\path\to\your\adb.exe' +scrcpy +``` + ### Device disconnected diff --git a/README.md b/README.md index 20ad0f9c..db531c61 100644 --- a/README.md +++ b/README.md @@ -505,10 +505,23 @@ Suppose that this server is accessible at 192.168.1.2. Then, from another terminal, run `scrcpy`: ```bash +# in bash export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 scrcpy --tunnel-host=192.168.1.2 ``` +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:192.168.1.2:5037' +scrcpy --tunnel-host=192.168.1.2 +``` + By default, `scrcpy` uses the local port used for `adb forward` tunnel establishment (typically `27183`, see `--port`). It is also possible to force a different tunnel port (it may be useful in more complex situations, when more @@ -542,10 +555,23 @@ ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer From another terminal, run `scrcpy`: ```bash +# in bash export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy ``` +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' +scrcpy +``` + To avoid enabling remote port forwarding, you could force a forward connection instead (notice the `-L` instead of `-R`): @@ -559,10 +585,23 @@ ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer From another terminal, run `scrcpy`: ```bash +# in bash export ADB_SERVER_SOCKET=tcp:localhost:5038 scrcpy --force-adb-forward ``` +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy --force-adb-forward +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' +scrcpy --force-adb-forward +``` + Like for wireless connections, it may be useful to reduce quality: From 7f2f5950f2624c1a3fd847af6248bcbef71a3124 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Jun 2022 21:23:05 +0200 Subject: [PATCH 0544/1133] Remove useless dependencies reference There is no libs/ directory with local jar files. --- server/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index dbc8261f..9725089e 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -19,7 +19,6 @@ android { } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.13.1' } From 396e4bd9258cba5c841cc40a02e7fea0819f2c7f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 19 Jul 2022 12:15:05 +0200 Subject: [PATCH 0545/1133] Add missing LOG_OOM() on malloc failure --- app/src/adb/adb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 8b92a2b0..e8775a01 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -401,6 +401,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, #define BUFSIZE 65536 char *buf = malloc(BUFSIZE); if (!buf) { + LOG_OOM(); return false; } From 4aeb78ece280d44607eda23f7996bae3d0c9415a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 19 Jul 2022 12:17:02 +0200 Subject: [PATCH 0546/1133] Add missing allocation failure check --- app/src/usb/usb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 97aa9a33..190a4108 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -23,6 +23,11 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) { // When non-negative, 'result' contains the number of bytes written char *s = malloc(result + 1); + if (!s) { + LOG_OOM(); + return NULL; + } + memcpy(s, buffer, result); s[result] = '\0'; return s; From db8c1ce8e1e4a7d3f96fad7d36a281798f529453 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 20 Jul 2022 11:39:55 +0200 Subject: [PATCH 0547/1133] Fix protocol documentation in comments Flags were in the correct order in the schema, but their description were reversed. --- app/src/demuxer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 35ea4c06..9412eda7 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -37,8 +37,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // CK...... ........ ........ ........ ........ ........ ........ ........ // ^^<-------------------------------------------------------------------> // || PTS - // | `- config packet - // `-- key frame + // | `- key frame + // `-- config packet uint8_t header[SC_PACKET_HEADER_SIZE]; ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE); From a47848f304965fe4baf548697ae79ed8db73e8b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 27 Jul 2022 14:53:15 +0200 Subject: [PATCH 0548/1133] Detect Windows using _WIN32 in network util For consistency, always use _WIN32 instead of a mix of __WINDOWS__ and _WIN32. --- app/src/util/net.c | 15 +++++++-------- app/src/util/net.h | 5 ++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index b1aa7445..e5f84676 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -3,11 +3,10 @@ #include #include #include -#include #include "log.h" -#ifdef __WINDOWS__ +#ifdef _WIN32 # include typedef int socklen_t; typedef SOCKET sc_raw_socket; @@ -29,7 +28,7 @@ bool net_init(void) { -#ifdef __WINDOWS__ +#ifdef _WIN32 WSADATA wsa; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; if (res < 0) { @@ -42,14 +41,14 @@ net_init(void) { void net_cleanup(void) { -#ifdef __WINDOWS__ +#ifdef _WIN32 WSACleanup(); #endif } static inline sc_socket wrap(sc_raw_socket sock) { -#ifdef __WINDOWS__ +#ifdef _WIN32 if (sock == INVALID_SOCKET) { return SC_SOCKET_NONE; } @@ -72,7 +71,7 @@ wrap(sc_raw_socket sock) { static inline sc_raw_socket unwrap(sc_socket socket) { -#ifdef __WINDOWS__ +#ifdef _WIN32 if (socket == SC_SOCKET_NONE) { return INVALID_SOCKET; } @@ -248,7 +247,7 @@ net_interrupt(sc_socket socket) { sc_raw_socket raw_sock = unwrap(socket); -#ifdef __WINDOWS__ +#ifdef _WIN32 if (!atomic_flag_test_and_set(&socket->closed)) { return !closesocket(raw_sock); } @@ -262,7 +261,7 @@ bool net_close(sc_socket socket) { sc_raw_socket raw_sock = unwrap(socket); -#ifdef __WINDOWS__ +#ifdef _WIN32 bool ret = true; if (!atomic_flag_test_and_set(&socket->closed)) { ret = !closesocket(raw_sock); diff --git a/app/src/util/net.h b/app/src/util/net.h index d9289981..801bdeaf 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -5,9 +5,8 @@ #include #include -#include -#ifdef __WINDOWS__ +#ifdef _WIN32 # include # include @@ -17,7 +16,7 @@ atomic_flag closed; } *sc_socket; -#else // not __WINDOWS__ +#else // not _WIN32 # include # define SC_SOCKET_NONE -1 From d23b3e88a4010716a50502da03c8326c2d0a1e81 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 26 Jul 2022 12:31:31 +0200 Subject: [PATCH 0549/1133] Replace '%g' by '%f' as printf format For some reason, '%g' does not work correctly with MinGW. Refs #3369 PR #3399 --- app/src/clock.c | 2 +- app/src/control_msg.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/clock.c b/app/src/clock.c index fe072f01..bb2430fd 100644 --- a/app/src/clock.c +++ b/app/src/clock.c @@ -98,7 +98,7 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) { sc_clock_estimate(clock, &clock->slope, &clock->offset); #ifndef SC_CLOCK_NDEBUG - LOGD("Clock estimation: %g * pts + %" PRItick, + LOGD("Clock estimation: %f * pts + %" PRItick, clock->slope, clock->offset); #endif } diff --git a/app/src/control_msg.c b/app/src/control_msg.c index a57d8cc2..3c398016 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -170,7 +170,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) { if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { // string pointer id LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 - " pressure=%g buttons=%06lx", + " pressure=%f buttons=%06lx", id == POINTER_ID_MOUSE ? "mouse" : "vfinger", MOTIONEVENT_ACTION_LABEL(action), msg->inject_touch_event.position.point.x, @@ -180,7 +180,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) { } else { // numeric pointer id LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" - PRIi32 " pressure=%g buttons=%06lx", + PRIi32 " pressure=%f buttons=%06lx", id, MOTIONEVENT_ACTION_LABEL(action), msg->inject_touch_event.position.point.x, From d19606eb0cd51b532177f80d3a9bc6619be304de Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Aug 2022 16:38:59 +0200 Subject: [PATCH 0550/1133] Rename net_listen() parameter For consistency with net_accept(), which necessarily uses a server socket, name the net_listen() parameter "server_socket". --- app/src/util/net.c | 4 ++-- app/src/util/net.h | 2 +- app/src/util/net_intr.c | 6 +++--- app/src/util/net_intr.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index e5f84676..ed4882cd 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -159,8 +159,8 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) { } bool -net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) { - sc_raw_socket raw_sock = unwrap(socket); +net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) { + sc_raw_socket raw_sock = unwrap(server_socket); int reuse = 1; if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, diff --git a/app/src/util/net.h b/app/src/util/net.h index 801bdeaf..21396882 100644 --- a/app/src/util/net.h +++ b/app/src/util/net.h @@ -39,7 +39,7 @@ bool net_connect(sc_socket socket, uint32_t addr, uint16_t port); bool -net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog); +net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog); sc_socket net_accept(sc_socket server_socket); diff --git a/app/src/util/net_intr.c b/app/src/util/net_intr.c index bb70010b..55286af6 100644 --- a/app/src/util/net_intr.c +++ b/app/src/util/net_intr.c @@ -15,14 +15,14 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, } bool -net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, +net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) { - if (!sc_intr_set_socket(intr, socket)) { + if (!sc_intr_set_socket(intr, server_socket)) { // Already interrupted return false; } - bool ret = net_listen(socket, addr, port, backlog); + bool ret = net_listen(server_socket, addr, port, backlog); sc_intr_set_socket(intr, SC_SOCKET_NONE); return ret; diff --git a/app/src/util/net_intr.h b/app/src/util/net_intr.h index a83fadda..dbef528d 100644 --- a/app/src/util/net_intr.h +++ b/app/src/util/net_intr.h @@ -11,7 +11,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, uint16_t port); bool -net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr, +net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr, uint16_t port, int backlog); sc_socket From 9c1722f42846859866d26823a8f6cf07495ab803 Mon Sep 17 00:00:00 2001 From: Derek Wu Date: Tue, 16 Aug 2022 23:52:08 +0200 Subject: [PATCH 0551/1133] Use DisplayManagerGlobal instance Use the client instance to communicate with the DisplayManager server. Fixes #3446 Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/wrappers/DisplayManager.java | 6 ++---- .../com/genymobile/scrcpy/wrappers/ServiceManager.java | 9 ++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index cedb3f47..3f4f897d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -3,12 +3,10 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.DisplayInfo; import com.genymobile.scrcpy.Size; -import android.os.IInterface; - public final class DisplayManager { - private final IInterface manager; + private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal - public DisplayManager(IInterface manager) { + public DisplayManager(Object manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index ea2a0784..68f6817d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -50,7 +50,14 @@ public final class ServiceManager { public DisplayManager getDisplayManager() { if (displayManager == null) { - displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager")); + try { + Class clazz = Class.forName("android.hardware.display.DisplayManagerGlobal"); + Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); + Object dmg = getInstanceMethod.invoke(null); + displayManager = new DisplayManager(dmg); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } } return displayManager; } From 77ebe786ea18ed0a049107231668bb48c5f32d01 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 21 Aug 2022 13:51:12 +0200 Subject: [PATCH 0552/1133] Fix FAQ formatting --- FAQ.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/FAQ.md b/FAQ.md index f6a8f226..e74d5873 100644 --- a/FAQ.md +++ b/FAQ.md @@ -44,9 +44,9 @@ If your device is not detected, you may need some [drivers] (on Windows). There ### Device unauthorized -> ERROR: Device is unauthorized: -> ERROR: --> (usb) 0123456789abcdef unauthorized -> ERROR: A popup should open on the device to request authorization. +> ERROR: Device is unauthorized: +> ERROR: --> (usb) 0123456789abcdef unauthorized +> ERROR: A popup should open on the device to request authorization. When connecting, a popup should open on the device. You must authorize USB debugging. @@ -60,10 +60,10 @@ If it does not open, check [stackoverflow][device-unauthorized]. If several devices are connected, you will encounter this error: -ERROR: Multiple (2) ADB devices: -ERROR: --> (usb) 0123456789abcdef device Nexus_5 -ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913 -ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip) +> ERROR: Multiple (2) ADB devices: +> ERROR: --> (usb) 0123456789abcdef device Nexus_5 +> ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913 +> ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip) In that case, you can either provide the identifier of the device you want to mirror: From 72ba913324d65cded8672dca7a57415cb82da095 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 23 Jul 2022 16:10:48 +0200 Subject: [PATCH 0553/1133] Move README and FAQ translations to the wiki This lowers the barrier to contribute to translations, and frees up my maintenance time used to review and merge translations and their updates in many languages. --- FAQ.it.md | 235 ---------- FAQ.ko.md | 84 ---- FAQ.md | 8 +- FAQ.zh-Hans.md | 284 ------------- README.de.md | 1016 ------------------------------------------- README.id.md | 696 ------------------------------ README.it.md | 1041 --------------------------------------------- README.jp.md | 799 ---------------------------------- README.ko.md | 498 ---------------------- README.md | 15 +- README.pt-br.md | 880 -------------------------------------- README.sp.md | 974 ------------------------------------------ README.tr.md | 824 ----------------------------------- README.zh-Hans.md | 993 ------------------------------------------ README.zh-Hant.md | 702 ------------------------------ 15 files changed, 7 insertions(+), 9042 deletions(-) delete mode 100644 FAQ.it.md delete mode 100644 FAQ.ko.md delete mode 100644 FAQ.zh-Hans.md delete mode 100644 README.de.md delete mode 100644 README.id.md delete mode 100644 README.it.md delete mode 100644 README.jp.md delete mode 100644 README.ko.md delete mode 100644 README.pt-br.md delete mode 100644 README.sp.md delete mode 100644 README.tr.md delete mode 100644 README.zh-Hans.md delete mode 100644 README.zh-Hant.md diff --git a/FAQ.it.md b/FAQ.it.md deleted file mode 100644 index 0da656c0..00000000 --- a/FAQ.it.md +++ /dev/null @@ -1,235 +0,0 @@ -_Apri le [FAQ](FAQ.md) originali e sempre aggiornate._ - -# Domande Frequenti (FAQ) - -Questi sono i problemi più comuni riportati e i loro stati. - - -## Problemi di `adb` - -`scrcpy` esegue comandi `adb` per inizializzare la connessione con il dispositivo. Se `adb` fallisce, scrcpy non funzionerà. - -In questo caso sarà stampato questo errore: - -> ERROR: "adb push" returned with value 1 - -Questo solitamente non è un bug di _scrcpy_, ma un problema del tuo ambiente. - -Per trovare la causa, esegui: - -```bash -adb devices -``` - -### `adb` not found (`adb` non trovato) - -È necessario che `adb` sia accessibile dal tuo `PATH`. - -In Windows, la cartella corrente è nel tuo `PATH` e `adb.exe` è incluso nella release, perciò dovrebbe già essere pronto all'uso. - - -### Device unauthorized (Dispositivo non autorizzato) - -Controlla [stackoverflow][device-unauthorized] (in inglese). - -[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized - - -### Device not detected (Dispositivo non rilevato) - -> adb: error: failed to get feature set: no devices/emulators found - -Controlla di aver abilitato correttamente il [debug con adb][enable-adb] (link in inglese). - -Se il tuo dispositivo non è rilevato, potresti avere bisogno dei [driver][drivers] (link in inglese) (in Windows). - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling -[drivers]: https://developer.android.com/studio/run/oem-usb.html - - -### Più dispositivi connessi - -Se più dispositivi sono connessi, riscontrerai questo errore: - -> adb: error: failed to get feature set: more than one device/emulator - -l'identificatore del tuo dispositivo deve essere fornito: - -```bash -scrcpy -s 01234567890abcdef -``` - -Notare che se il tuo dispositivo è connesso mediante TCP/IP, riscontrerai questo messaggio: - -> adb: error: more than one device/emulator -> ERROR: "adb reverse" returned with value 1 -> WARN: 'adb reverse' failed, fallback to 'adb forward' - -Questo è un problema atteso (a causa di un bug di una vecchia versione di Android, vedi [#5] (link in inglese)), ma in quel caso scrcpy ripiega su un metodo differente, il quale dovrebbe funzionare. - -[#5]: https://github.com/Genymobile/scrcpy/issues/5 - - -### Conflitti tra versioni di adb - -> adb server version (41) doesn't match this client (39); killing... - -L'errore compare quando usi più versioni di `adb` simultaneamente. Devi trovare il programma che sta utilizzando una versione differente di `adb` e utilizzare la stessa versione dappertutto. - -Puoi sovrascrivere i binari di `adb` nell'altro programma, oppure chiedere a _scrcpy_ di usare un binario specifico di `adb`, impostando la variabile d'ambiente `ADB`: - -```bash -set ADB=/path/to/your/adb -scrcpy -``` - - -### Device disconnected (Dispositivo disconnesso) - -Se _scrcpy_ si interrompe con l'avviso "Device disconnected", allora la connessione `adb` è stata chiusa. - -Prova con un altro cavo USB o inseriscilo in un'altra porta USB. Vedi [#281] (in inglese) e [#283] (in inglese). - -[#281]: https://github.com/Genymobile/scrcpy/issues/281 -[#283]: https://github.com/Genymobile/scrcpy/issues/283 - - - -## Problemi di controllo - -### Mouse e tastiera non funzionano - -Su alcuni dispositivi potresti dover abilitare un opzione che permette l'[input simulato][simulating input] (link in inglese). Nelle opzioni sviluppatore, abilita: - -> **Debug USB (Impostazioni di sicurezza)** -> _Permetti la concessione dei permessi e la simulazione degli input mediante il debug USB_ - - -[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -### I caratteri speciali non funzionano - -Iniettare del testo in input è [limitato ai caratteri ASCII][text-input] (link in inglese). Un trucco permette di iniettare dei [caratteri accentati][accented-characters] (link in inglese), ma questo è tutto. Vedi [#37] (link in inglese). - -[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode -[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters -[#37]: https://github.com/Genymobile/scrcpy/issues/37 - - -## Problemi del client - -### La qualità è bassa - -Se la definizione della finestra del tuo client è minore di quella del tuo dispositivo, allora potresti avere una bassa qualità di visualizzazione, specialmente individuabile nei testi (vedi [#40] (link in inglese)). - -[#40]: https://github.com/Genymobile/scrcpy/issues/40 - -Per migliorare la qualità di ridimensionamento (downscaling), il filtro trilineare è applicato automaticamente se il renderizzatore è OpenGL e se supporta la creazione di mipmap. - -In Windows, potresti voler forzare OpenGL: - -``` -scrcpy --render-driver=opengl -``` - -Potresti anche dover configurare il [comportamento di ridimensionamento][scaling behavior] (link in inglese): - -> `scrcpy.exe` > Propietà > Compatibilità > Modifica impostazioni DPI elevati > Esegui l'override del comportamento di ridimensionamento DPI elevati > Ridimensionamento eseguito per: _Applicazione_. - -[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 - - -### 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 - -In Plasma Desktop, il compositore è disabilitato mentre _scrcpy_ è in esecuzione. - -Come soluzione alternativa, [disattiva la "composizione dei blocchi"][kwin] (link in inglese). - - -[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 - - -## Crash - -### Eccezione - -Ci potrebbero essere molte ragioni. Una causa comune è che il codificatore hardware del tuo dispositivo non riesce a codificare alla definizione selezionata: - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> android.media.MediaCodec$CodecException: Error 0xfffffc0e -> ... -> Exit due to uncaughtException in main thread: -> ERROR: Could not open video stream -> INFO: Initial texture: 1080x2336 -> ``` - -o - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> java.lang.IllegalStateException -> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) -> ``` - -Prova con una definizione inferiore: - -``` -scrcpy -m 1920 -scrcpy -m 1024 -scrcpy -m 800 -``` - -Potresti anche provare un altro [codificatore](README.it.md#codificatore). - - -## Linea di comando in Windows - -Alcuni utenti Windows non sono familiari con la riga di comando. Qui è descritto come aprire un terminale ed eseguire `scrcpy` con gli argomenti: - - 1. Premi Windows+r, questo apre una finestra di dialogo. - 2. Scrivi `cmd` e premi Enter, questo apre un terminale. - 3. Vai nella tua cartella di _scrcpy_ scrivendo (adatta il percorso): - - ```bat - cd C:\Users\user\Downloads\scrcpy-win64-xxx - ``` - - e premi Enter - 4. Scrivi il tuo comando. Per esempio: - - ```bat - scrcpy --record file.mkv - ``` - -Se pianifichi di utilizzare sempre gli stessi argomenti, crea un file `myscrcpy.bat` (abilita mostra [estensioni nomi file][show file extensions] per evitare di far confusione) contenente il tuo comando nella cartella di `scrcpy`. Per esempio: - -```bat -scrcpy --prefer-text --turn-screen-off --stay-awake -``` - -Poi fai doppio click su quel file. - -Potresti anche modificare (una copia di) `scrcpy-console.bat` o `scrcpy-noconsole.vbs` per aggiungere alcuni argomenti. - -[show file extensions]: https://www.techpedia.it/14-windows/windows-10/171-visualizzare-le-estensioni-nomi-file-con-windows-10 diff --git a/FAQ.ko.md b/FAQ.ko.md deleted file mode 100644 index c9e06e24..00000000 --- a/FAQ.ko.md +++ /dev/null @@ -1,84 +0,0 @@ -# 자주하는 질문 (FAQ) - -다음은 자주 제보되는 문제들과 그들의 현황입니다. - - -### Windows 운영체제에서, 디바이스가 발견되지 않습니다. - -가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다. -다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요: - - adb devices - -Windows는 당신의 디바이스를 감지하기 위해 [드라이버]가 필요할 수도 있습니다. - -[드라이버]: https://developer.android.com/studio/run/oem-usb.html - - -### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다. - -일부 디바이스에서는, [simulating input]을 허용하기 위해서 한가지 옵션을 활성화해야 할 수도 있습니다. -개발자 옵션에서 (developer options) 다음을 활성화 하세요: - -> **USB debugging (Security settings)** -> _권한 부여와 USB 디버깅을 통한 simulating input을 허용한다_ - -[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -### 마우스 클릭이 다른 곳에 적용됩니다. - -Mac 운영체제에서, HiDPI support 와 여러 스크린 창이 있는 경우, 입력 위치가 잘못 파악될 수 있습니다. -[issue 15]를 참고하세요. - -[issue 15]: https://github.com/Genymobile/scrcpy/issues/15 - -차선책은 HiDPI support을 비활성화 하고 build하는 방법입니다: - -```bash -meson x --buildtype release -Dhidpi_support=false -``` - -하지만, 동영상은 낮은 해상도로 재생될 것 입니다. - - -### HiDPI display의 화질이 낮습니다. - -Windows에서는, [scaling behavior] 환경을 설정해야 할 수도 있습니다. - -> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > -> Override high DPI scaling behavior > Scaling performed by: _Application_. - -[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 - - -### KWin compositor가 실행되지 않습니다 - -Plasma Desktop에서는,_scrcpy_ 가 실행중에는 compositor가 비활성화 됩니다. - -차석책으로는, ["Block compositing"를 비활성화하세요][kwin]. - -[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 - - -###비디오 스트림을 열 수 없는 에러가 발생합니다.(Could not open video stream). - -여러가지 원인이 있을 수 있습니다. 가장 흔한 원인은 디바이스의 하드웨어 인코더(hardware encoder)가 -주어진 해상도를 인코딩할 수 없는 경우입니다. - -``` -ERROR: Exception on thread Thread[main,5,main] -android.media.MediaCodec$CodecException: Error 0xfffffc0e -... -Exit due to uncaughtException in main thread: -ERROR: Could not open video stream -INFO: Initial texture: 1080x2336 -``` - -더 낮은 해상도로 시도 해보세요: - -``` -scrcpy -m 1920 -scrcpy -m 1024 -scrcpy -m 800 -``` diff --git a/FAQ.md b/FAQ.md index e74d5873..e6c3c94d 100644 --- a/FAQ.md +++ b/FAQ.md @@ -324,8 +324,8 @@ to add some arguments. ## Translations -This FAQ is available in other languages: +Translations of this FAQ in other languages are available in the [wiki]. - - [Italiano (Italiano, `it`) - v1.19](FAQ.it.md) - - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) - - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md) +[wiki]: https://github.com/Genymobile/scrcpy/wiki + +Only this README file is guaranteed to be up-to-date. diff --git a/FAQ.zh-Hans.md b/FAQ.zh-Hans.md deleted file mode 100644 index 23674eed..00000000 --- a/FAQ.zh-Hans.md +++ /dev/null @@ -1,284 +0,0 @@ -_Only the original [FAQ.md](FAQ.md) is guaranteed to be up-to-date._ - -_只有原版的 [FAQ.md](FAQ.md)是保证最新的。_ - -Current version is based on [28054cd] - -本文根据[28054cd]进行翻译。 - -[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md - -# 常见问题 - -这里是一些常见的问题以及他们的状态。 - -## `adb` 相关问题 - -`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果 `adb` 执行失败了, scrcpy 就无法工作。 - -在这种情况中,将会输出这个错误: - -> ERROR: "adb get-serialno" returned with value 1 - -这通常不是 _scrcpy_ 的bug,而是你的环境的问题。 - -要找出原因,请执行以下操作: - -```bash -adb devices -``` - -### 找不到`adb` - - -你的`PATH`中需要能访问到`adb`。 - -在Windows上,当前目录会包含在`PATH`中,并且`adb.exe`也包含在发行版中,因此它应该是开箱即用(直接解压就可以)的。 - - -### 设备未授权 - - -> error: device unauthorized. -> This adb server's $ADB_VENDOR_KEYS is not set -> Try 'adb kill-server' if that seems wrong. -> Otherwise check for a confirmation dialog on your device. - -连接时,在设备上应该会打开一个弹出窗口。 您必须授权 USB 调试。 - -如果没有打开,参见[stackoverflow][device-unauthorized]. - -[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized - - -### 未检测到设备 - -> error: no devices/emulators found - -确认已经正确启用 [adb debugging][enable-adb]. - -如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver]. - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling -[drivers]: https://developer.android.com/studio/run/oem-usb.html -[google-usb-driver]: https://developer.android.com/studio/run/win-usb - - -### 已连接多个设备 - -如果连接了多个设备,您将遇到以下错误: - -> error: more than one device/emulator - -必须提供要镜像的设备的标识符: - -```bash -scrcpy -s 01234567890abcdef -``` - -注意,如果你的设备是通过 TCP/IP 连接的, 你将会收到以下消息: - -> adb: error: more than one device/emulator -> ERROR: "adb reverse" returned with value 1 -> WARN: 'adb reverse' failed, fallback to 'adb forward' - -这是意料之中的 (由于旧版安卓的一个bug, 请参见 [#5]),但是在这种情况下,scrcpy会退回到另一种方法,这种方法应该可以起作用。 - -[#5]: https://github.com/Genymobile/scrcpy/issues/5 - - -### adb版本之间冲突 - -> adb server version (41) doesn't match this client (39); killing... - -同时使用多个版本的`adb`时会发生此错误。你必须查找使用不同`adb`版本的程序,并在所有地方使用相同版本的`adb`。 - -你可以覆盖另一个程序中的`adb`二进制文件,或者通过设置`ADB`环境变量来让 _scrcpy_ 使用特定的`adb`二进制文件。 - -```bash -set ADB=/path/to/your/adb -scrcpy -``` - - -### 设备断开连接 - -如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。 - -请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。 - -[#281]: https://github.com/Genymobile/scrcpy/issues/281 -[#283]: https://github.com/Genymobile/scrcpy/issues/283 - - -## 控制相关问题 - -### 鼠标和键盘不起作用 - - -在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。 -在开发者选项中,打开: - -> **USB调试 (安全设置)** -> _允许通过USB调试修改权限或模拟点击_ - -[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -### 特殊字符不起作用 - -可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。 - -自 Linux 上的 scrcpy v1.20 之后,可以模拟[物理键盘][hid] (HID)。 - -[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 - - -## 客户端相关问题 - -### 效果很差 - -如果你的客户端窗口分辨率比你的设备屏幕小,则可能出现效果差的问题,尤其是在文本上(参见 [#40])。 - -[#40]: https://github.com/Genymobile/scrcpy/issues/40 - -为了提升降尺度的质量,如果渲染器是OpenGL并且支持mip映射,就会自动开启三线性过滤。 - -在Windows上,你可能希望强制使用OpenGL: - -``` -scrcpy --render-driver=opengl -``` - -你可能还需要配置[缩放行为][scaling behavior]: - -> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > -> Override high DPI scaling behavior > Scaling performed by: _Application_. - -[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 - - -### Wayland相关的问题 - -在Linux上,SDL默认使用x11。可以通过`SDL_VIDEODRIVER`环境变量来更改[视频驱动][video driver]: - -[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver - -```bash -export SDL_VIDEODRIVER=wayland -scrcpy -``` - -在一些发行版上 (至少包括 Fedora), `libdecor` 包必须手动安装。 - -参见 [#2554] 和 [#2559]。 - -[#2554]: https://github.com/Genymobile/scrcpy/issues/2554 -[#2559]: https://github.com/Genymobile/scrcpy/issues/2559 - - -### KWin compositor 崩溃 - -在Plasma桌面中,当 _scrcpy_ 运行时,会禁用compositor。 - -一种解决方法是, [禁用 "Block compositing"][kwin]. - -[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 - - -## 崩溃 - -### 异常 - -可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码: - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> android.media.MediaCodec$CodecException: Error 0xfffffc0e -> ... -> Exit due to uncaughtException in main thread: -> ERROR: Could not open video stream -> INFO: Initial texture: 1080x2336 -> ``` - -或者 - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> java.lang.IllegalStateException -> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) -> ``` - -请尝试使用更低的清晰度: - -``` -scrcpy -m 1920 -scrcpy -m 1024 -scrcpy -m 800 -``` - -自 scrcpy v1.22以来,scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。 - -你也可以尝试另一种 [编码器](README.md#encoder)。 - - -如果您在 Android 12 上遇到此异常,则只需升级到 scrcpy >= 1.18 (见 [#2129]): - -``` -> ERROR: Exception on thread Thread[main,5,main] -java.lang.AssertionError: java.lang.reflect.InvocationTargetException - at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75) - ... -Caused by: java.lang.reflect.InvocationTargetException - at java.lang.reflect.Method.invoke(Native Method) - at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73) - ... 7 more -Caused by: java.lang.IllegalArgumentException: displayToken must not be null - at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067) - at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147) - ... 9 more -``` - -[#2129]: https://github.com/Genymobile/scrcpy/issues/2129 - - -## Windows命令行 - -从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如: - -``` -scrcpy --record file.mkv -``` - -您也可以打开终端并手动转到 scrcpy 文件夹: - - 1. 按下 Windows+r,打开一个对话框。 - 2. 输入 `cmd` 并按 Enter,这样就打开了一个终端。 - 3. 通过输入以下命令,切换到你的 _scrcpy_ 所在的目录 (根据你的实际位置修改路径): - - ```bat - cd C:\Users\user\Downloads\scrcpy-win64-xxx - ``` - - 然后按 Enter - 4. 输入你的命令。比如: - - ```bat - scrcpy --record file.mkv - ``` - -如果你打算总是使用相同的参数,在`scrcpy`目录创建一个文件 `myscrcpy.bat` -(启用 [显示文件拓展名][show file extensions] 避免混淆),文件中包含你的命令。例如: - -```bat -scrcpy --prefer-text --turn-screen-off --stay-awake -``` - -然后只需双击刚刚创建的文件。 - -你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。 - -[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ diff --git a/README.de.md b/README.de.md deleted file mode 100644 index 1299ae0e..00000000 --- a/README.de.md +++ /dev/null @@ -1,1016 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -# scrcpy (v1.22) - -scrcpy - -_ausgesprochen "**scr**een **c**o**py**"_ - -Diese Anwendung liefert sowohl Anzeige als auch Steuerung eines Android-Gerätes über USB (oder [über TCP/IP](#tcpip-kabellos)). Dabei wird kein _root_ Zugriff benötigt. -Die Anwendung funktioniert unter _GNU/Linux_, _Windows_ und _macOS_. - -![screenshot](assets/screenshot-debian-600.jpg) - -Dabei liegt der Fokus auf: - - - **Leichtigkeit**: native, nur Anzeige des Gerätedisplays - - **Leistung**: 30~120fps, abhängig vom Gerät - - **Qualität**: 1920×1080 oder mehr - - **Geringe Latenz**: [35~70ms][lowlatency] - - **Kurze Startzeit**: ~1 Sekunde um das erste Bild anzuzeigen - - **Keine Aufdringlichkeit**: Es wird keine installierte Software auf dem Gerät zurückgelassen - - **Nutzervorteile**: kein Account, keine Werbung, kein Internetzugriff notwendig - - **Freiheit**: gratis und open-source - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - -Die Features beinhalten: - - [Aufnahme](#Aufnahme) - - Spiegeln mit [ausgeschaltetem Bildschirm](#bildschirm-ausschalten) - - [Copy&Paste](#copy-paste) in beide Richtungen - - [Einstellbare Qualität](#Aufnahmekonfiguration) - - Gerätebildschirm [als Webcam (V4L2)](#v4l2loopback) (nur Linux) - - [Simulation einer physischen Tastatur (HID)](#simulation-einer-physischen-tastatur-mit-hid) - (nur Linux) - - [Simulation einer physischen Maus (HID)](#simulation-einer-physischen-maus-mit-hid) - (nur Linux) - - [OTG Modus](#otg) (nur Linux) - - und mehr… - -## Voraussetzungen - -Das Android-Gerät benötigt mindestens API 21 (Android 5.0). - -Es muss sichergestellt sein, dass [adb debugging][enable-adb] auf dem Gerät aktiv ist. - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -Auf manchen Geräten müssen zudem [weitere Optionen][control] aktiv sein um das Gerät mit Maus und Tastatur steuern zu können. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## Installation der App - -Packaging status - -### Zusammenfassung - - - Linux: `apt install scrcpy` - - Windows: [download (siehe README)](README.md#windows) - - macOS: `brew install scrcpy` - -Direkt von der Source bauen: [BUILD] ([vereinfachter Prozess (englisch)][BUILD_simple]) - -[BUILD]: BUILD.md -[BUILD_simple]: BUILD.md#simple - - -### Linux - -Auf Debian und Ubuntu: - -``` -apt install scrcpy -``` - -Auf Arch Linux: - -``` -pacman -S scrcpy -``` - -Ein [Snap] package ist verfügbar: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Für Fedora ist ein [COPR] package verfügbar: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - - -Für Gentoo ist ein [Ebuild] verfügbar: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -Die App kann zudem [manuell gebaut werden][BUILD] ([vereinfachter Prozess (englisch)][BUILD_simple]). - - -### Windows - -Für Windows ist der Einfachheit halber ein vorgebautes Archiv mit allen Abhängigkeiten (inklusive `adb`) vorhanden. - - - [README](README.md#windows) - -Es ist zudem in [Chocolatey] vorhanden: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # falls noch nicht vorhanden -``` - -Und in [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # falls noch nicht vorhanden -``` - -[Scoop]: https://scoop.sh - -Die App kann zudem [manuell gebaut werden][BUILD]. - - -### macOS - -Die Anwendung ist in [Homebrew] verfügbar. Installation: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -Es wird `adb` benötigt, auf welches über `PATH` zugegriffen werden kann. Falls noch nicht vorhanden: - -```bash -brew install android-platform-tools -``` - -Es ist außerdem in [MacPorts] vorhanden, welches adb bereits aufsetzt: - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - - -Die Anwendung kann zudem [manuell gebaut werden][BUILD]. - - -## Ausführen - -Ein Android-Gerät anschließen und diese Befehle ausführen: - -```bash -scrcpy -``` - -Dabei werden Kommandozeilenargumente akzeptiert, aufgelistet per: - -```bash -scrcpy --help -``` - -## Funktionalitäten - -### Aufnahmekonfiguration - -#### Größe reduzieren - -Manchmal ist es sinnvoll, das Android-Gerät mit einer geringeren Auflösung zu spiegeln, um die Leistung zu erhöhen. - -Um die Höhe und Breite auf einen Wert zu limitieren (z.B. 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # short version -``` - -Die andere Größe wird dabei so berechnet, dass das Seitenverhältnis des Gerätes erhalten bleibt. -In diesem Fall wird ein Gerät mit einer 1920×1080-Auflösung mit 1024×576 gespiegelt. - - -#### Ändern der Bit-Rate - -Die Standard-Bitrate ist 8 Mbps. Um die Bitrate zu ändern (z.B. zu 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # Kurzversion -``` - -#### Limitieren der Bildwiederholrate - -Die Aufnahme-Bildwiederholrate kann begrenzt werden: - -```bash -scrcpy --max-fps 15 -``` - -Dies wird offiziell seit Android 10 unterstützt, kann jedoch bereits auf früheren Versionen funktionieren. - -#### Zuschneiden - -Der Geräte-Bildschirm kann zugeschnitten werden, sodass nur ein Teil gespiegelt wird. - -Dies ist beispielsweise nützlich, um nur ein Auge der Oculus Go zu spiegeln: - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 am Versatz (0,0) -``` - -Falls `--max-size` auch festgelegt ist, wird das Ändern der Größe nach dem Zuschneiden angewandt. - - -#### Feststellen der Videoorientierung - - -Um die Orientierung während dem Spiegeln festzustellen: - -```bash -scrcpy --lock-video-orientation # ursprüngliche (momentane) Orientierung -scrcpy --lock-video-orientation=0 # normale Orientierung -scrcpy --lock-video-orientation=1 # 90° gegen den Uhrzeigersinn -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90° mit dem Uhrzeigersinn -``` - -Dies beeinflusst die Aufnahmeausrichtung. - -Das [Fenster kann auch unabhängig rotiert](#Rotation) werden. - - -#### Encoder - -Manche Geräte besitzen mehr als einen Encoder. Manche dieser Encoder können dabei sogar zu Problemen oder Abstürzen führen. -Die Auswahl eines anderen Encoders ist möglich: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -Um eine Liste aller verfügbaren Encoder zu erhalten (eine Fehlermeldung gibt alle verfügbaren Encoder aus): - -```bash -scrcpy --encoder _ -``` - -### Aufnahme - -#### Aufnehmen von Videos - -Es ist möglich, das Display während des Spiegelns aufzunehmen: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Um das Spiegeln während des Aufnehmens zu deaktivieren: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# Unterbrechen der Aufnahme mit Strg+C -``` - -"Übersprungene Bilder" werden aufgenommen, selbst wenn sie in Echtzeit (aufgrund von Performancegründen) nicht dargestellt werden. Die Einzelbilder sind mit _Zeitstempeln_ des Gerätes versehen are, sodass eine [Paketverzögerungsvariation] nicht die Aufnahmedatei beeinträchtigt. - -[Paketverzögerungsvariation]: https://www.wikide.wiki/wiki/en/Packet_delay_variation - - -#### v4l2loopback - -Auf Linux ist es möglich, den Video-Stream zu einem v4l2 loopback Gerät zu senden, sodass das Android-Gerät von jedem v4l2-fähigen Tool wie eine Webcam verwendet werden kann. - -Das Modul `v4l2loopback` muss dazu installiert werden: - -```bash -sudo apt install v4l2loopback-dkms -``` - -Um ein v4l2 Gerät zu erzeugen: - -```bash -sudo modprobe v4l2loopback -``` - -Dies erzeugt ein neues Video-Gerät in `/dev/videoN`, wobei `N` ein Integer ist (mehr [Optionen](https://github.com/umlaeute/v4l2loopback#options) sind verfügbar um mehrere Geräte oder Geräte mit spezifischen Nummern zu erzeugen). - -Um die aktivierten Geräte aufzulisten: - -```bash -# benötigt das v4l-utils package -v4l2-ctl --list-devices - -# simpel, kann aber ausreichend -ls /dev/video* -``` - -Um scrcpy mithilfe eines v4l2 sink zu starten: - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # Fenster mit Spiegelung ausschalten -scrcpy --v4l2-sink=/dev/videoN -N # kurze Version -``` - -(`N` muss mit der Geräte-ID ersetzt werden, welche mit `ls /dev/video*` überprüft werden kann) - -Einmal aktiv, kann der Stream mit einem v4l2-fähigen Tool verwendet werden: - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLC kann eine gewisse Bufferverzögerung herbeiführen -``` - -Beispielsweise kann das Video mithilfe von [OBS] aufgenommen werden. - -[OBS]: https://obsproject.com/ - - -#### Buffering - -Es ist möglich, Buffering hinzuzufügen. Dies erhöht die Latenz, reduziert aber etwaigen Jitter (see [#2464]). - -[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 - -Diese Option ist sowohl für Video-Buffering: - -```bash -scrcpy --display-buffer=50 # fügt 50ms Buffering zum Display hinzu -``` - -als auch V4L2 sink verfügbar: - -```bash -scrcpy --v4l2-buffer=500 # fügt 500ms Buffering für v4l2 sink hinzu -``` - - -### Verbindung - -#### TCP/IP Kabellos - -_Scrcpy_ verwendet `adb`, um mit dem Gerät zu kommunizieren. `adb` kann sich per TCP/IP mit einem Gerät [verbinden]. Das Gerät muss dabei mit demselben Netzwerk wie der Computer verbunden sein. - -##### Automatisch - -Die Option `--tcpip` erlaubt es, die Verbindung automatisch zu konfigurieren. Dabei gibt es zwei Varianten. - -Falls das Gerät (verfügbar unter 192.168.1.1 in diesem Beispiel) bereit an einem Port (typically 5555) nach einkommenden adb-Verbindungen hört, dann führe diesen Befehl aus: - -```bash -scrcpy --tcpip=192.168.1.1 # Standard-Port ist 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -Falls adb TCP/IP auf dem Gerät deaktiviert ist (oder falls die IP-Adresse des Gerätes nicht bekannt ist): Gerät per USB verbinden, anschließend diesen Befehl ausführen: - -```bash -scrcpy --tcpip # ohne weitere Argumente -``` - -Dies finden automatisch das Gerät und aktiviert den TCP/IP-Modus. Anschließend verbindet sich der Befehl mit dem Gerät bevor die Verbindung startet. - -##### Manuell - -Alternativ kann die TCP/IP-Verbindung auch manuell per `adb` aktiviert werden: - -1. Gerät mit demselben Wi-Fi wie den Computer verbinden. -2. IP-Adresse des Gerätes herausfinden, entweder über Einstellungen → Über das Telefon → Status, oder indem dieser Befehl ausgeführt wird: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. Aktivieren von adb über TCP/IP auf dem Gerät: `adb tcpip 5555`. -4. Ausstecken des Geräts. -5. Verbinden zum Gerät: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` ersetzen)_. -6. `scrcpy` wie normal ausführen. - -Es kann sinnvoll sein, die Bit-Rate sowie dei Auflösung zu reduzieren: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # kurze Version -``` - -[verbinden]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### Mehrere Geräte - -Falls mehrere Geräte unter `adb devices` aufgelistet werden, muss die _Seriennummer_ angegeben werden: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # kurze Version -``` - -Falls das Gerät über TCP/IP verbunden ist: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # kurze Version -``` - -Es können mehrere Instanzen von _scrcpy_ für mehrere Geräte gestartet werden. - -#### Autostart beim Verbinden eines Gerätes - -Hierfür kann [AutoAdb] verwendet werden: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Tunnel - -Um sich zu einem entfernten Gerät zu verbinden, kann der `adb` Client mit einem remote-`adb`-Server verbunden werden (Voraussetzung: Gleiche Version des `adb`-Protokolls). - -##### Remote ADB Server - -Um sich zu einem Remote-`adb`-Server zu verbinden: Der Server muss auf allen Ports hören - -```bash -adb kill-server -adb -a nodaemon server start -# Diesen Dialog offen halten -``` - -**Warnung: Die gesamte Kommunikation zwischen adb und den Geräten ist unverschlüsselt.** - -Angenommen, der Server ist unter 192.168.1.2 verfügbar. Dann kann von einer anderen Kommandozeile scrcpy aufgeführt werden: - -```bash -export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -Standardmäßig verwendet scrcpy den lokalen Port für die Einrichtung des `adb forward`-Tunnels (typischerweise `27183`, siehe `--port`). -Es ist zudem möglich, einen anderen Tunnel-Port zuzuweisen (sinnvoll in Situationen, bei welchen viele Weiterleitungen erfolgen): - -``` -scrcpy --tunnel-port=1234 -``` - - -##### SSH Tunnel - -Um mit einem Remote-`adb`-Server sicher zu kommunizieren, wird ein SSH-Tunnel empfohlen. - -Sicherstellen, dass der Remote-`adb`-Server läuft: - -```bash -adb start-server -``` - -Erzeugung eines SSH-Tunnels: - -```bash -# local 5038 --> remote 5037 -# local 27183 <-- remote 27183 -ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer -# Diesen Dialog geöffnet halten -``` - -Von einer anderen Kommandozeile aus scrcpy ausführen: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -Um das Aktivieren von Remote-Weiterleitung zu verhindern, kann eine Vorwärts-Verbindung verwendet werden (`-L` anstatt von `-R`): - -```bash -# local 5038 --> remote 5037 -# local 27183 --> remote 27183 -ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer -# Diesen Dialog geöffnet halten -``` - -Von einer anderen Kommandozeile aus scrcpy ausführen: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - - -Wie für kabellose Verbindungen kann es sinnvoll sein, die Qualität zu reduzieren: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Fensterkonfiguration - -#### Titel - -Standardmäßig ist der Fenstertitel das Gerätemodell. Der Titel kann jedoch geändert werden: - -```bash -scrcpy --window-title 'Mein Gerät' -``` - -#### Position und Größe - -Die anfängliche Fensterposition und Größe können festgelegt werden: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Rahmenlos - -Um den Rahmen des Fensters zu deaktivieren: - -```bash -scrcpy --window-borderless -``` - -#### Immer im Vordergrund - -Um das Fenster immer im Vordergrund zu halten: - -```bash -scrcpy --always-on-top -``` - -#### Vollbild - -Die Anwendung kann direkt im Vollbildmodus gestartet werden: - -```bash -scrcpy --fullscreen -scrcpy -f # kurze Version -``` - -Das Vollbild kann dynamisch mit MOD+f gewechselt werden. - -#### Rotation - -Das Fenster kann rotiert werden: - -```bash -scrcpy --rotation 1 -``` - -Mögliche Werte sind: - - `0`: keine Rotation - - `1`: 90 grad gegen den Uhrzeigersinn - - `2`: 180 grad - - `3`: 90 grad mit dem Uhrzeigersinn - -die Rotation kann zudem dynamisch mit MOD+ -_(links)_ and MOD+ _(rechts)_ angepasst werden. - -_scrcpy_ schafft 3 verschiedene Rotationen: - - MOD+r erfordert von Gerät den Wechsel zwischen Hochformat und Querformat (die momentane App kann dies verweigern, wenn die geforderte Ausrichtung nicht unterstützt wird). - - [`--lock-video-orientation`](#feststellen-der-videoorientierung) ändert die Ausrichtung der Spiegelung (die Ausrichtung des an den Computer gesendeten Videos). Dies beeinflusst eventuelle Aufnahmen. - - `--rotation` (or MOD+/MOD+) rotiert nur das Fenster, eventuelle Aufnahmen sind hiervon nicht beeinflusst. - - -### Andere Spiegel-Optionen - -#### Lesezugriff - -Um die Steuerung (alles, was mit dem Gerät interagieren kann: Tasten, Mausklicks, Drag-and-drop von Dateien) zu deaktivieren: - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Anzeige - -Falls mehrere Displays vorhanden sind, kann das zu spiegelnde Display gewählt werden: - -```bash -scrcpy --display 1 -``` - -Die Liste an verfügbaren Displays kann mit diesem Befehl ausgegeben werden: - -```bash -adb shell dumpsys display # Nach "mDisplayId=" in der Ausgabe suchen -``` - -Das zweite Display kann nur gesteuert werden, wenn das Gerät Android 10 oder höher besitzt. Ansonsten wird das Display nur mit Lesezugriff gespiegelt. - - -#### Wach bleiben - -Um zu verhindern, dass das Gerät nach einer Weile in den Ruhezustand übergeht (solange es eingesteckt ist): - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -Der ursprüngliche Zustand wird beim Schließen von scrcpy wiederhergestellt. - - -#### Bildschirm ausschalten - -Es ist möglich, beim Starten des Spiegelns mithilfe eines Kommandozeilenarguments den Bildschirm des Gerätes auszuschalten: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Oder durch das Drücken von MOD+o jederzeit. - -Um das Display wieder einzuschalten muss MOD+Shift+o gedrückt werden. - -Auf Android aktiviert der `POWER` Knopf das Display immer. -Für den Komfort wird, wenn `POWER` via scrcpy gesendet wird (über Rechtsklick oder MOD+p), wird versucht, das Display nach einer kurzen Zeit wieder auszuschalten (falls es möglich ist). -Der physische `POWER` Button aktiviert das Display jedoch immer. - -Dies kann zudem nützlich sein, um das Gerät vom Ruhezustand abzuhalten: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### Ausschalten beim Schließen - -Um den Gerätebildschirm abzuschalten, wenn scrcpy geschlossen wird: - -```bash -scrcpy --power-off-on-close -``` - - -#### Anzeigen von Berührungen - -Für Präsentationen kann es sinnvoll sein, die physischen Berührungen anzuzeigen (auf dem physischen Gerät). - -Android stellt dieses Feature in den _Entwickleroptionen_ zur Verfügung. - -_Scrcpy_ stellt die Option zur Verfügung, dies beim Start zu aktivieren und beim Schließen auf den Ursprungszustand zurückzusetzen: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Anmerkung: Nur _physische Berührungen_ werden angezeigt (mit dem Finger auf dem Gerät). - - -#### Bildschirmschoner deaktivieren - -Standardmäßig unterbindet scrcpy nicht den Bildschirmschoner des Computers. - -Um den Bildschirmschoner zu unterbinden: - -```bash -scrcpy --disable-screensaver -``` - - -### Eingabesteuerung - -#### Geräte-Bildschirm drehen - -MOD+r drücken, um zwischen Hoch- und Querformat zu wechseln. - -Anmerkung: Dis funktioniert nur, wenn die momentan geöffnete App beide Rotationen unterstützt. - -#### Copy-paste - -Immer, wenn sich die Zwischenablage von Android ändert wird dies mit dem Computer synchronisiert. - -Jedes Strg wird an das Gerät weitergegeben. Insbesonders: - - Strg+c kopiert typischerweise - - Strg+x schneidet typischerweise aus - - Strg+v fügt typischerweise ein (nach der Computer-zu-Gerät-Synchronisation) - -Dies funktioniert typischerweise wie erwartet. - -Die wirkliche Funktionsweise hängt jedoch von der jeweiligen Anwendung ab. Beispielhaft sendet _Termux_ SIGINT bei Strg+c, und _K-9 Mail_ erzeugt eine neue Nachricht. - -Um kopieren, ausschneiden und einfügen in diesen Fällen zu verwenden (nur bei Android >= 7 unterstützt): - - MOD+c gibt `COPY` ein - - MOD+x gibt `CUT` ein - - MOD+v gibt `PASTE` ein (nach der Computer-zu-Gerät-Synchronisation) - -Zusätzlich erlaubt es MOD+Shift+v den momentanen Inhalt der Zwischenablage als eine Serie von Tastenevents einzugeben. -Dies ist nützlich, fall die Applikation kein Einfügen unterstützt (z.B. _Termux_). Jedoch kann nicht-ASCII-Inhalt dabei zerstört werden. - -**WARNUNG:** Das Einfügen der Computer-Zwischenablage in das Gerät (entweder mit Strg+v oder MOD+v) kopiert den Inhalt in die Zwischenablage des Gerätes. -Als Konsequenz kann somit jede Android-Applikation diesen Inhalt lesen. Das Einfügen von sensiblen Informationen wie Passwörtern sollte aus diesem Grund vermieden werden. - -Mache Geräte verhalten sich nicht wie erwartet, wenn die Zwischenablage per Programm verändert wird. -Die Option `--legacy-paste` wird bereitgestellt, welche das Verhalten von Strg+v und MOD+v so ändert, dass die Zwischenablage wie bei MOD+Shift+v als eine Serie von Tastenevents ausgeführt wird. - -Um die automatische Synchronisierung der Zwischenablage zu deaktivieren: -`--no-clipboard-autosync`. - -#### Ziehen zum Zoomen - -Um "Ziehen-zum-Zoomen" zu simulieren: Strg+_klicken-und-bewegen_. - -Genauer: Strg halten, während Linksklick gehalten wird. Solange Linksklick gehalten wird, skalieren und rotieren die Mausbewegungen den Inhalt (soweit von der jeweiligen App unterstützt). - -Konkret erzeugt scrcpy einen am Mittelpunkt des Displays gespiegelten, "virtuellen" Finger. - -#### Simulation einer physischen Tastatur mit HID - -Standardmäßig verwendet scrcpy Android-Tasten oder Textinjektion. Dies funktioniert zwar immer, jedoch nur mit ASCII. - -Auf Linux kann scrcpy mithilfe von HID eine physische Tastatur simulieren, um eine bessere Eingabeerfahrung zu gewährleisten (dies nutzt [USB HID over AOAv2][hid-aoav2]): Die virtuelle Tastatur wird deaktiviert, es funktioniert für alle Zeichen und mit IME. - -[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support - -Dies funktioniert jedoch nur, wenn das Gerät über USB verbunden ist. Zudem wird dies momentan nur unter Linux unterstützt. - -Um diesen Modus zu aktivieren: - -```bash -scrcpy --hid-keyboard -scrcpy -K # kurze Version -``` - -Falls dies auf gewissen Gründen fehlschlägt (z.B. Gerät ist nicht über USB verbunden), so fällt scrcpy auf den Standardmodus zurück (mit einer Ausgabe in der Konsole). -Dies erlaubt es, dieselben Kommandozeilenargumente zu verwenden, egal ob das Gerät per USB oder TCP/IP verbunden ist. - -In diesem Modus werden rohe Tastenevents (scancodes) an das Gerät gesendet. -Aus diesem Grund muss ein nicht passenden Tastaturformat in den Einstellungen des Android-Gerätes unter Einstellungen → System → Sprache und Eingabe → [Physical keyboard] konfiguriert werden. - -Diese Einstellungsseite kann direkt mit diesem Befehl geöffnet werden: - -```bash -adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -``` - -Diese Option ist jedoch nur verfügbar, wenn eine HID-Tastatur oder eine physische Tastatur verbunden sind. - -[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - -#### Simulation einer physischen Maus mit HID - -Ähnlich zu einer Tastatur kann auch eine Maus mithilfe von HID simuliert werden. -Wie zuvor funktioniert dies jedoch nur, wenn das Gerät über USB verbunden ist. Zudem wird dies momentan nur unter Linux unterstützt. - -Standardmäßig verwendet scrcpy Android Maus Injektionen mit absoluten Koordinaten. -Durch die Simulation einer physischen Maus erscheint auf dem Display des Geräts ein Mauszeiger, zu welchem die Bewegungen, Klicks und Scrollbewegungen relativ eingegeben werden. - -Um diesen Modus zu aktivieren: - -```bash -scrcpy --hid-mouse -scrcpy -M # kurze Version -``` - -Es kann zudem`--forward-all-clicks` übergeben werden, um [alle Mausklicks an das Gerät weiterzugeben](#rechtsklick-und-mittelklick). - -Wenn dieser Modus aktiv ist, ist der Mauszeiger des Computers auf dem Fenster gefangen (Zeiger verschwindet von Computer und erscheint auf dem Android-Gerät). - -Spezielle Tasteneingaben wie Alt oder Super ändern den Zustand des Mauszeigers (geben diesen wieder frei/fangen ihn wieder ein). -Eine dieser Tasten kann verwendet werden, um die Kontrolle der Maus wieder zurück an den Computer zu geben. - - -#### OTG - -Es ist möglich, _scrcpy_ so auszuführen, dass nur Maus und Tastatur, wie wenn diese direkt über ein OTG-Kabel verbunden wären, simuliert werden. - -In diesem Modus ist _adb_ nicht nötig, ebenso ist das Spiegeln der Anzeige deaktiviert. - -Um den OTG-Modus zu aktivieren: - -```bash -scrcpy --otg -# Seriennummer übergeben, falls mehrere Geräte vorhanden sind -scrcpy --otg -s 0123456789abcdef -``` - -Es ist möglich, nur HID-Tastatur oder HID-Maus zu aktivieren: - -```bash -scrcpy --otg --hid-keyboard # nur Tastatur -scrcpy --otg --hid-mouse # nur Maus -scrcpy --otg --hid-keyboard --hid-mouse # Tastatur und Maus -# Der Einfachheit halber sind standardmäßig beide aktiv -scrcpy --otg # Tastatur und Maus -``` - -Wie `--hid-keyboard` und `--hid-mouse` funktioniert dies nur, wenn das Gerät per USB verbunden ist. -Zudem wird dies momentan nur unter Linux unterstützt. - - -#### Textinjektions-Vorliebe - -Beim Tippen von Text werden zwei verschiedene [Events][textevents] generiert: - - _key events_, welche signalisieren, ob eine Taste gedrückt oder losgelassen wurde; - - _text events_, welche signalisieren, dass Text eingegeben wurde. - -Standardmäßig werden key events verwendet, da sich bei diesen die Tastatur in Spielen wie erwartet verhält (typischerweise für WASD). - -Dies kann jedoch [Probleme verursachen][prefertext]. Trifft man auf ein solches Problem, so kann dies mit diesem Befehl umgangen werden: - -```bash -scrcpy --prefer-text -``` - -Dies kann jedoch das Tastaturverhalten in Spielen beeinträchtigen/zerstören. - -Auf der anderen Seite kann jedoch auch die Nutzung von key events erzwungen werden: - -```bash -scrcpy --raw-key-events -``` - -Diese Optionen haben jedoch keinen Einfluss auf eine etwaige HID-Tastatur, da in diesem modus alle key events als scancodes gesendet werden. - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### Wiederholen von Tasten - -Standardmäßig löst das gedrückt halten einer Taste das jeweilige Event mehrfach aus. Dies kann jedoch zu Performanceproblemen in manchen Spielen führen. - -Um das Weitergeben von sich wiederholenden Tasteneingaben zu verhindern: - -```bash -scrcpy --no-key-repeat -``` - -This option has no effect on HID keyboard (key repeat is handled by Android -directly in this mode). - - -#### Rechtsklick und Mittelklick - -Standardmäßig löst Rechtsklick BACK (wenn Bildschirm aus: POWER) und Mittelklick BACK aus. Um diese Kürzel abzuschalten und stattdessen die Eingaben direkt an das Gerät weiterzugeben: - -```bash -scrcpy --forward-all-clicks -``` - - -### Dateien ablegen - -#### APK installieren - -Um eine AKP zu installieren, kann diese per Drag-and-drop auf das _scrcpy_-Fenster gezogen werden. - -Dabei erfolgt kein visuelles Feedback, ein Log wird in die Konsole ausgegeben. - - -#### Datei auf Gerät schieben - -Um eine Datei nach `/sdcard/Download/` auf dem Gerät zu schieben, Drag-and-drop die (nicht-APK)-Datei auf das _scrcpy_-Fenster. - -Dabei erfolgt kein visuelles Feedback, ein Log wird in die Konsole ausgegeben. - -Das Zielverzeichnis kann beim Start geändert werden: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - - -### Audioweitergabe - -Audio wird von _scrcpy_ nicht übertragen. Hierfür kann [sndcpy] verwendet werden. - -Siehe zudem [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Tastenkürzel - -In der folgenden Liste ist MOD der Kürzel-Auslöser. Standardmäßig ist dies (links) Alt oder (links) Super. - -Dies kann mithilfe von `--shortcut-mod` geändert werden. Mögliche Tasten sind `lstrg`, `rstrg`, -`lalt`, `ralt`, `lsuper` und `rsuper`. Beispielhaft: - -```bash -# Nutze rStrg als Auslöser -scrcpy --shortcut-mod=rctrl - -# Nutze entweder LStrg+LAlt oder LSuper für Tastenkürzel -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] ist typischerweise die Windows oder Cmd Taste._ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - -| Aktion | Tastenkürzel | | -|--------------------------------------------------------|-----------------------------------------------------------|:-------------------------| -| Vollbild wechseln | MOD+f | | -| Display nach links rotieren | MOD+ _(links)_ | | -| Display nach links rotieren | MOD+ _(rechts)_ | | -| Fenstergröße 1:1 replizieren (pixel-perfect) | MOD+g | | -| Fenstergröße zum entfernen der schwarzen Balken ändern | MOD+w | _Doppel-Linksklick¹_ | -| Klick auf `HOME` | MOD+h | _Mittelklick_ | -| Klick auf `BACK` | MOD+b | _Rechtsklick²_ | -| Klick auf `APP_SWITCH` | MOD+s | _4.-Taste-Klick³_ | -| Klick auf `MENU` (Bildschirm entsperren)⁴ | MOD+m | | -| Klick auf `VOLUME_UP` | MOD+ _(hoch)_ | | -| CKlick auf `VOLUME_DOWN` | MOD+ _(runter)_ | | -| Klick auf `POWER` | MOD+p | | -| Power an | _Rechtsklick²_ | | -| Gerätebildschirm ausschalten (weiterhin spiegeln) | MOD+o | | -| Gerätebildschirm einschalten | MOD+Shift+o | | -| Gerätebildschirm drehen | MOD+r | | -| Benachrichtigungs-Bereich anzeigen | MOD+n | _5.-Taste-Klick³_ | -| Erweitertes Einstellungs-Menü anzeigen | MOD+n+n | _Doppel-5.-Taste-Klick³_ | -| Bedienfelder einklappen | MOD+Shift+n | | -| In die Zwischenablage kopieren⁵ | MOD+c | | -| In die Zwischenablage kopieren⁵ | MOD+x | | -| Zwischenablage synchronisieren und einfügen⁵ | MOD+v | | -| Computer-Zwischenablage einfügen (per Tastenevents) | MOD+Shift+v | | -| FPS-Zähler aktivieren/deaktivieren (ing stdout) | MOD+i | | -| Ziehen zum Zoomen | Strg+_Klicken-und-Bewegen_ | | -| Drag-and-drop mit APK-Datei | APK von Computer installieren | | -| Drag-and-drop mit Nicht-APK Datei | [Datei auf das Gerät schieben](#datei-auf-gerät-schieben) | | - - -_¹Doppelklick auf die schwarzen Balken, um diese zu entfernen._ -_²Rechtsklick aktiviert den Bildschirm, falls dieser aus war, ansonsten ZURÜCK._ -_³4. und 5. Maustasten, wenn diese an der jeweiligen Maus vorhanden sind._ -_⁴Für react-native Applikationen in Entwicklung, `MENU` öffnet das Entwickler-Menü._ -_⁵Nur für Android >= 7._ - -Abkürzungen mit mehreren Tastenanschlägen werden durch das Loslassen und erneute Drücken der Taste erreicht. -Beispielhaft, um "Erweitere das Einstellungs-Menü" auszuführen: - - 1. Drücke und halte MOD. - 2. Doppelklicke n. - 3. Lasse MOD los. - -Alle Strg+_Taste_ Tastenkürzel werden an das Gerät übergeben, sodass sie von der jeweiligen Applikation ausgeführt werden können. - - -## Personalisierte Pfade - -Um eine spezifische _adb_ Binary zu verwenden, muss deren Pfad als Umgebungsvariable `ADB` deklariert werden: - -```bash -ADB=/path/to/adb scrcpy -``` - -Um den Pfad der `scrcpy-server` Datei zu bearbeiten, muss deren Pfad in `SCRCPY_SERVER_PATH` bearbeitet werden. - -Um das Icon von scrcpy zu ändern, muss `SCRCPY_ICON_PATH` geändert werden. - - -## Warum _scrcpy_? - -Ein Kollege hat mich dazu herausgefordert, einen Namen so unaussprechbar wie [gnirehtet] zu finden. - -[`strcpy`] kopiert einen **str**ing; `scrcpy` kopiert einen **scr**een. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## Selbst bauen? - -Siehe [BUILD]. - - -## Typische Fehler - -Siehe [FAQ](FAQ.md). - - -## Entwickler - -[Entwicklerseite](DEVELOP.md). - - -## Licence - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Artikel (auf Englisch) - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.id.md b/README.id.md deleted file mode 100644 index 1230f0cc..00000000 --- a/README.id.md +++ /dev/null @@ -1,696 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -# scrcpy (v1.16) - -Aplikasi ini menyediakan tampilan dan kontrol perangkat Android yang terhubung pada USB (atau [melalui TCP/IP][article-tcpip]). Ini tidak membutuhkan akses _root_ apa pun. Ini bekerja pada _GNU/Linux_, _Windows_ and _macOS_. - -![screenshot](assets/screenshot-debian-600.jpg) - -Ini berfokus pada: - - - **keringanan** (asli, hanya menampilkan layar perangkat) - - **kinerja** (30~60fps) - - **kualitas** (1920×1080 atau lebih) - - **latensi** rendah ([35~70ms][lowlatency]) - - **waktu startup rendah** (~1 detik untuk menampilkan gambar pertama) - - **tidak mengganggu** (tidak ada yang terpasang di perangkat) - - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - - -## Persyaratan -Perangkat Android membutuhkan setidaknya API 21 (Android 5.0). - -Pastikan Anda [mengaktifkan debugging adb][enable-adb] pada perangkat Anda. - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -Di beberapa perangkat, Anda juga perlu mengaktifkan [opsi tambahan][control] untuk mengontrolnya menggunakan keyboard dan mouse. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## Dapatkan aplikasinya - -### Linux - -Di Debian (_testing_ dan _sid_ untuk saat ini) dan Ubuntu (20.04): - -``` -apt install scrcpy -``` - -Paket [Snap] tersedia: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Untuk Fedora, paket [COPR] tersedia: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -Untuk Arch Linux, paket [AUR] tersedia: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -Untuk Gentoo, tersedia [Ebuild]: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak terlalu sulit). - - -### Windows - -Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia : - - - [README](README.md#windows) - -Ini juga tersedia di [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # jika Anda belum memilikinya -``` - -Dan di [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # jika Anda belum memilikinya -``` - -[Scoop]: https://scoop.sh - -Anda juga dapat [membangun aplikasi secara manual][BUILD]. - - -### macOS - -Aplikasi ini tersedia di [Homebrew]. Instal saja: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` -Anda membutuhkan `adb`, dapat diakses dari `PATH` Anda. Jika Anda belum memilikinya: - -```bash -brew cask install android-platform-tools -``` - -Anda juga dapat [membangun aplikasi secara manual][BUILD]. - - -## Menjalankan - -Pasang perangkat Android, dan jalankan: - -```bash -scrcpy -``` - -Ini menerima argumen baris perintah, didaftarkan oleh: - -```bash -scrcpy --help -``` - -## Fitur - -### Menangkap konfigurasi - -#### Mengurangi ukuran - -Kadang-kadang, berguna untuk mencerminkan perangkat Android dengan definisi yang lebih rendah untuk meningkatkan kinerja. - -Untuk membatasi lebar dan tinggi ke beberapa nilai (mis. 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # versi pendek -``` - -Dimensi lain dihitung agar rasio aspek perangkat dipertahankan. -Dengan begitu, perangkat 1920×1080 akan dicerminkan pada 1024×576. - -#### Ubah kecepatan bit - -Kecepatan bit default adalah 8 Mbps. Untuk mengubah bitrate video (mis. Menjadi 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # versi pendek -``` - -#### Batasi frekuensi gambar - -Kecepatan bingkai pengambilan dapat dibatasi: - -```bash -scrcpy --max-fps 15 -``` - -Ini secara resmi didukung sejak Android 10, tetapi dapat berfungsi pada versi sebelumnya. - -#### Memotong - -Layar perangkat dapat dipotong untuk mencerminkan hanya sebagian dari layar. - -Ini berguna misalnya untuk mencerminkan hanya satu mata dari Oculus Go: - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 Mengimbangi (0,0) -``` - -Jika `--max-size` juga ditentukan, pengubahan ukuran diterapkan setelah pemotongan. - - -#### Kunci orientasi video - -Untuk mengunci orientasi pencerminan: - -```bash -scrcpy --lock-video-orientation 0 # orientasi alami -scrcpy --lock-video-orientation 1 # 90° berlawanan arah jarum jam -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 90° searah jarum jam -``` - -Ini mempengaruhi orientasi perekaman. - - -### Rekaman - -Anda dapat merekam layar saat melakukan mirroring: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Untuk menonaktifkan pencerminan saat merekam: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# berhenti merekam dengan Ctrl+C -``` - -"Skipped frames" are recorded, even if they are not displayed in real time (for -performance reasons). Frames are _timestamped_ on the device, so [packet delay -variation] does not impact the recorded file. - -"Frame yang dilewati" direkam, meskipun tidak ditampilkan secara real time (untuk alasan performa). Bingkai *diberi stempel waktu* pada perangkat, jadi [variasi penundaan paket] tidak memengaruhi file yang direkam. - -[variasi penundaan paket]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -### Koneksi - -#### Wireless - -_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan `adb` dapat [terhubung] ke perangkat melalui TCP / IP: - -1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda. -2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status). -3. Aktifkan adb melalui TCP / IP pada perangkat Anda: `adb tcpip 5555`. -4. Cabut perangkat Anda. -5. Hubungkan ke perangkat Anda: `adb connect DEVICE_IP: 5555` (*ganti* *`DEVICE_IP`*). -6. Jalankan `scrcpy` seperti biasa. - -Mungkin berguna untuk menurunkan kecepatan bit dan definisi: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versi pendek -``` - -[terhubung]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### Multi-perangkat - -Jika beberapa perangkat dicantumkan di `adb devices`, Anda harus menentukan _serial_: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # versi pendek -``` - -If the device is connected over TCP/IP: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # versi pendek -``` - -Anda dapat memulai beberapa contoh _scrcpy_ untuk beberapa perangkat. - -#### Mulai otomatis pada koneksi perangkat - -Anda bisa menggunakan [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Koneksi via SSH tunnel - -Untuk menyambung ke perangkat jarak jauh, dimungkinkan untuk menghubungkan klien `adb` lokal ke server `adb` jarak jauh (asalkan mereka menggunakan versi yang sama dari _adb_ protocol): - -```bash -adb kill-server # matikan server adb lokal di 5037 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 komputer_jarak_jauh_anda -# jaga agar tetap terbuka -``` - -Dari terminal lain: - -```bash -scrcpy -``` - -Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan `-R`): - -```bash -adb kill-server # matikan server adb lokal di 5037 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 komputer_jarak_jauh_anda -# jaga agar tetap terbuka -``` - -Dari terminal lain: - -```bash -scrcpy --force-adb-forward -``` - -Seperti koneksi nirkabel, mungkin berguna untuk mengurangi kualitas: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Konfigurasi Jendela - -#### Judul - -Secara default, judul jendela adalah model perangkat. Itu bisa diubah: - -```bash -scrcpy --window-title 'Perangkat Saya' -``` - -#### Posisi dan ukuran - -Posisi dan ukuran jendela awal dapat ditentukan: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Jendela tanpa batas - -Untuk menonaktifkan dekorasi jendela: - -```bash -scrcpy --window-borderless -``` - -#### Selalu di atas - -Untuk menjaga jendela scrcpy selalu di atas: - -```bash -scrcpy --always-on-top -``` - -#### Layar penuh - -Aplikasi dapat dimulai langsung dalam layar penuh:: - -```bash -scrcpy --fullscreen -scrcpy -f # versi pendek -``` - -Layar penuh kemudian dapat diubah secara dinamis dengan MOD+f. - -#### Rotasi - -Jendela mungkin diputar: - -```bash -scrcpy --rotation 1 -``` - -Nilai yang mungkin adalah: - - `0`: tidak ada rotasi - - `1`: 90 derajat berlawanan arah jarum jam - - `2`: 180 derajat - - `3`: 90 derajat searah jarum jam - -Rotasi juga dapat diubah secara dinamis dengan MOD+ -_(kiri)_ and MOD+ _(kanan)_. - -Perhatikan bahwa _scrcpy_ mengelola 3 rotasi berbeda:: - - MOD+r meminta perangkat untuk beralih antara potret dan lanskap (aplikasi yang berjalan saat ini mungkin menolak, jika mendukung orientasi yang diminta). - - `--lock-video-orientation` mengubah orientasi pencerminan (orientasi video yang dikirim dari perangkat ke komputer). Ini mempengaruhi rekaman. - - `--rotation` (atau MOD+/MOD+) - memutar hanya konten jendela. Ini hanya mempengaruhi tampilan, bukan rekaman. - - -### Opsi pencerminan lainnya - -#### Hanya-baca - -Untuk menonaktifkan kontrol (semua yang dapat berinteraksi dengan perangkat: tombol input, peristiwa mouse, seret & lepas file): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Layar - -Jika beberapa tampilan tersedia, Anda dapat memilih tampilan untuk cermin: - -```bash -scrcpy --display 1 -``` - -Daftar id tampilan dapat diambil dengan:: - -``` -adb shell dumpsys display # cari "mDisplayId=" di keluaran -``` - -Tampilan sekunder hanya dapat dikontrol jika perangkat menjalankan setidaknya Android 10 (jika tidak maka akan dicerminkan dalam hanya-baca). - - -#### Tetap terjaga - -Untuk mencegah perangkat tidur setelah beberapa penundaan saat perangkat dicolokkan: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -Keadaan awal dipulihkan ketika scrcpy ditutup. - - -#### Matikan layar - -Dimungkinkan untuk mematikan layar perangkat saat pencerminan mulai dengan opsi baris perintah: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Atau dengan menekan MOD+o kapan saja. - -Untuk menyalakannya kembali, tekan MOD+Shift+o. - -Di Android, tombol `POWER` selalu menyalakan layar. Untuk kenyamanan, jika `POWER` dikirim melalui scrcpy (melalui klik kanan atauMOD+p), itu akan memaksa untuk mematikan layar setelah penundaan kecil (atas dasar upaya terbaik). -Tombol fisik `POWER` masih akan menyebabkan layar dihidupkan. - -Ini juga berguna untuk mencegah perangkat tidur: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### Render frame kedaluwarsa - -Secara default, untuk meminimalkan latensi, _scrcpy_ selalu menampilkan frame yang terakhir didekodekan tersedia, dan menghapus frame sebelumnya. - -Untuk memaksa rendering semua frame (dengan kemungkinan peningkatan latensi), gunakan: - -```bash -scrcpy --render-expired-frames -``` - -#### Tunjukkan sentuhan - -Untuk presentasi, mungkin berguna untuk menunjukkan sentuhan fisik (pada perangkat fisik). - -Android menyediakan fitur ini di _Opsi Pengembang_. - -_Scrcpy_ menyediakan opsi untuk mengaktifkan fitur ini saat mulai dan mengembalikan nilai awal saat keluar: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Perhatikan bahwa ini hanya menunjukkan sentuhan _fisik_ (dengan jari di perangkat). - - -#### Nonaktifkan screensaver - -Secara default, scrcpy tidak mencegah screensaver berjalan di komputer. - -Untuk menonaktifkannya: - -```bash -scrcpy --disable-screensaver -``` - - -### Kontrol masukan - -#### Putar layar perangkat - -Tekan MOD+r untuk beralih antara mode potret dan lanskap. - -Perhatikan bahwa itu berputar hanya jika aplikasi di latar depan mendukung orientasi yang diminta. - -#### Salin-tempel - -Setiap kali papan klip Android berubah, secara otomatis disinkronkan ke papan klip komputer. - -Apa saja Ctrl pintasan diteruskan ke perangkat. Khususnya: - - Ctrl+c biasanya salinan - - Ctrl+x biasanya memotong - - Ctrl+v biasanya menempel (setelah sinkronisasi papan klip komputer-ke-perangkat) - -Ini biasanya berfungsi seperti yang Anda harapkan. - -Perilaku sebenarnya tergantung pada aplikasi yang aktif. Sebagai contoh, -_Termux_ mengirim SIGINT ke Ctrl+c sebagai gantinya, dan _K-9 Mail_ membuat pesan baru. - -Untuk menyalin, memotong dan menempel dalam kasus seperti itu (tetapi hanya didukung di Android> = 7): - - MOD+c injeksi `COPY` _(salin)_ - - MOD+x injeksi `CUT` _(potong)_ - - MOD+v injeksi `PASTE` (setelah sinkronisasi papan klip komputer-ke-perangkat) - -Tambahan, MOD+Shift+v memungkinkan untuk memasukkan teks papan klip komputer sebagai urutan peristiwa penting. Ini berguna ketika komponen tidak menerima penempelan teks (misalnya di _Termux_), tetapi dapat merusak konten non-ASCII. - -**PERINGATAN:** Menempelkan papan klip komputer ke perangkat (baik melalui -Ctrl+v or MOD+v) menyalin konten ke clipboard perangkat. Akibatnya, aplikasi Android apa pun dapat membaca kontennya. Anda harus menghindari menempelkan konten sensitif (seperti kata sandi) seperti itu. - - -#### Cubit untuk memperbesar/memperkecil - -Untuk mensimulasikan "cubit-untuk-memperbesar/memperkecil": Ctrl+_klik-dan-pindah_. - -Lebih tepatnya, tahan Ctrl sambil menekan tombol klik kiri. Hingga tombol klik kiri dilepaskan, semua gerakan mouse berskala dan memutar konten (jika didukung oleh aplikasi) relatif ke tengah layar. - -Secara konkret, scrcpy menghasilkan kejadian sentuh tambahan dari "jari virtual" di lokasi yang dibalik melalui bagian tengah layar. - - -#### Preferensi injeksi teks - -Ada dua jenis [peristiwa][textevents] dihasilkan saat mengetik teks: -- _peristiwa penting_, menandakan bahwa tombol ditekan atau dilepaskan; -- _peristiwa teks_, menandakan bahwa teks telah dimasukkan. - -Secara default, huruf dimasukkan menggunakan peristiwa kunci, sehingga keyboard berperilaku seperti yang diharapkan dalam game (biasanya untuk tombol WASD). - -Tapi ini mungkin [menyebabkan masalah][prefertext]. Jika Anda mengalami masalah seperti itu, Anda dapat menghindarinya dengan: - -```bash -scrcpy --prefer-text -``` - -(tapi ini akan merusak perilaku keyboard dalam game) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### Ulangi kunci - -Secara default, menahan tombol akan menghasilkan peristiwa kunci yang berulang. Ini dapat menyebabkan masalah kinerja di beberapa game, di mana acara ini tidak berguna. - -Untuk menghindari penerusan peristiwa penting yang berulang: - -```bash -scrcpy --no-key-repeat -``` - - -### Seret/jatuhkan file - -#### Pasang APK - -Untuk menginstal APK, seret & lepas file APK (diakhiri dengan `.apk`) ke jendela _scrcpy_. - -Tidak ada umpan balik visual, log dicetak ke konsol. - - -#### Dorong file ke perangkat - -Untuk mendorong file ke `/sdcard/` di perangkat, seret & jatuhkan file (non-APK) ke jendela _scrcpy_. - -Tidak ada umpan balik visual, log dicetak ke konsol. - -Direktori target dapat diubah saat mulai: - -```bash -scrcpy --push-target /sdcard/foo/bar/ -``` - - -### Penerusan audio - -Audio tidak diteruskan oleh _scrcpy_. Gunakan [sndcpy]. - -Lihat juga [Masalah #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[Masalah #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Pintasan - -Dalam daftar berikut, MOD adalah pengubah pintasan. Secara default, ini (kiri) Alt atau (kiri) Super. - -Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` dan `rsuper`. Sebagai contoh: - -```bash -# gunakan RCtrl untuk jalan pintas -scrcpy --shortcut-mod=rctrl - -# gunakan baik LCtrl+LAlt atau LSuper untuk jalan pintas -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] biasanya adalah Windows atau Cmd key._ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | Aksi | Pintasan - | ------------------------------------------------------|:----------------------------- - | Alihkan mode layar penuh | MOD+f - | Putar layar kiri | MOD+ _(kiri)_ - | Putar layar kanan | MOD+ _(kanan)_ - | Ubah ukuran jendela menjadi 1:1 (piksel-sempurna) | MOD+g - | Ubah ukuran jendela menjadi hapus batas hitam | MOD+w \| _klik-dua-kali¹_ - | Klik `HOME` | MOD+h \| _Klik-tengah_ - | Klik `BACK` | MOD+b \| _Klik-kanan²_ - | Klik `APP_SWITCH` | MOD+s - | Klik `MENU` (buka kunci layar) | MOD+m - | Klik `VOLUME_UP` | MOD+ _(naik)_ - | Klik `VOLUME_DOWN` | MOD+ _(turun)_ - | Klik `POWER` | MOD+p - | Menyalakan | _Klik-kanan²_ - | Matikan layar perangkat (tetap mirroring) | MOD+o - | Hidupkan layar perangkat | MOD+Shift+o - | Putar layar perangkat | MOD+r - | Luaskan panel notifikasi | MOD+n - | Ciutkan panel notifikasi | MOD+Shift+n - | Menyalin ke papan klip³ | MOD+c - | Potong ke papan klip³ | MOD+x - | Sinkronkan papan klip dan tempel³ | MOD+v - | Masukkan teks papan klip komputer | MOD+Shift+v - | Mengaktifkan/menonaktifkan penghitung FPS (di stdout) | MOD+i - | Cubit-untuk-memperbesar/memperkecil | Ctrl+_klik-dan-pindah_ - -_¹Klik-dua-kali pada batas hitam untuk menghapusnya._ -_²Klik-kanan akan menghidupkan layar jika mati, tekan BACK jika tidak._ -_³Hanya di Android >= 7._ - -Semua Ctrl+_key_ pintasan diteruskan ke perangkat, demikian adanya -ditangani oleh aplikasi aktif. - - -## Jalur kustom - -Untuk menggunakan biner _adb_ tertentu, konfigurasikan jalurnya di variabel lingkungan `ADB`: - - ADB=/path/to/adb scrcpy - -Untuk mengganti jalur file `scrcpy-server`, konfigurasikan jalurnya di -`SCRCPY_SERVER_PATH`. - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - - -## Mengapa _scrcpy_? - -Seorang kolega menantang saya untuk menemukan nama yang tidak dapat diucapkan seperti [gnirehtet]. - -[`strcpy`] menyalin sebuah **str**ing; `scrcpy` menyalin sebuah **scr**een. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## Bagaimana Cara membangun? - -Lihat [BUILD]. - -[BUILD]: BUILD.md - - -## Masalah umum - -Lihat [FAQ](FAQ.md). - - -## Pengembang - -Baca [halaman pengembang]. - -[halaman pengembang]: DEVELOP.md - - -## Lisensi - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Artikel - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ - diff --git a/README.it.md b/README.it.md deleted file mode 100644 index a35c7bbb..00000000 --- a/README.it.md +++ /dev/null @@ -1,1041 +0,0 @@ -_Apri il [README](README.md) originale (in inglese) e sempre aggiornato._ - -scrcpy - -# scrcpy (v1.23) - -_si pronuncia "**scr**een **c**o**py**"_ - -[Leggi in altre lingue](#traduzioni) - -Questa applicazione fornisce la visualizzazione e il controllo di dispositivi Android collegati via USB (o [via TCP/IP](#tcpip-wireless)). Non richiede alcun accesso _root_. -Funziona su _GNU/Linux_, _Windows_ e _macOS_. - -![screenshot](assets/screenshot-debian-600.jpg) - -Si concentra su: - - - **leggerezza**: nativo, mostra solo lo schermo del dispositivo - - **prestazioni**: 30~120fps, in funzione del dispositivo - - **qualità**: 1920×1080 o superiore - - **bassa latenza**: [35~70ms][lowlatency] - - **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine - - **non invadenza**: nulla rimane installato sul dispositivo - - **vantaggi per l'utente**: nessun account, nessuna pubblicità, non è richiesta alcuna connessione a internet - - **libertà**: software libero e a codice aperto (_free and open source_) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - -Le sue caratteristiche includono: - - [registrazione](#registrazione) - - mirroring con [schermo del dispositivo spento](#spegnere-lo-schermo) - - [copia-incolla](#copia-incolla) in entrambe le direzioni - - [qualità configurabile](#configurazione-di-acquisizione) - - schermo del dispositivo [come webcam (V4L2)](#v4l2loopback) (solo per Linux) - - [simulazione della tastiera fisica (HID)](#simulazione-della-tastiera-fisica-HID) - - [simulazione mouse fisico (HID)](#simulazione-del-mouse-fisico-HID) - - [modalità OTG](#otg) - - e altro ancora... - - -## Requisiti - -Il dispositivo Android richiede almeno le API 21 (Android 5.0). - -Assiucurati di aver [attivato il debug usb][enable-adb] sul(/i) tuo(i) dispositivo(/i). - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -In alcuni dispositivi, devi anche abilitare [un'opzione aggiuntiva][control] per controllarli con tastiera e mouse. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - -## Ottieni l'app - -Packaging status - -### Sommario - - - Linux: `apt install scrcpy` - - Windows: [download](README.md#windows) - - macOS: `brew install scrcpy` - -Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)) - -[BUILD]: BUILD.md -[BUILD_simple]: BUILD.md#simple - - -### Linux - -Su Debian e Ubuntu: - -``` -apt install scrcpy -``` - -Su Arch Linux: - -``` -pacman -S scrcpy -``` - -È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://it.wikipedia.org/wiki/Snappy_(gestore_pacchetti) - -Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - - -Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -Puoi anche [compilare l'app manualmente][BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)). - - -### Windows - -Per Windows, per semplicità è disponibile un archivio precompilato con tutte le dipendenze (incluso `adb`): - - - [README](README.md#windows) (Link al README originale per l'ultima versione) - -È anche disponibile in [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # se non lo hai già -``` - -E in [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # se non lo hai già -``` - -[Scoop]: https://scoop.sh - -Puoi anche [compilare l'app manualmente][BUILD] (in inglese). - - -### macOS - -L'applicazione è disponibile in [Homebrew]. Basta installarlo: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -Serve che `adb` sia accessibile dal tuo `PATH`. Se non lo hai già: - -```bash -brew install android-platform-tools -``` - -È anche disponibile in [MacPorts], che imposta adb per te: - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - - -Puoi anche [compilare l'app manualmente][BUILD] (in inglese). - - -## Esecuzione - -Collega un dispositivo Android ed esegui: - -```bash -scrcpy -``` - -Scrcpy accetta argomenti da riga di comando, elencati con: - -```bash -scrcpy --help -``` - -## Funzionalità - -### Configurazione di acquisizione - -#### Riduci dimensione - -Qualche volta è utile trasmettere un dispositvo Android ad una definizione inferiore per aumentare le prestazioni. - -Per limitare sia larghezza che altezza ad un certo valore (ad es. 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # versione breve -``` - -L'altra dimensione è calcolata in modo tale che il rapporto di forma del dispositivo sia preservato. -In questo esempio un dispositivo in 1920x1080 viene trasmesso a 1024x576. - - -#### Cambia bit-rate (velocità di trasmissione) - -Il bit-rate predefinito è 8 Mbps. Per cambiare il bitrate video (ad es. a 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # versione breve -``` - -#### Limitare il frame rate (frequenza di fotogrammi) - -Il frame rate di acquisizione può essere limitato: - -```bash -scrcpy --max-fps 15 -``` - -Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti. - -L'attuale frame rate di acquisizione può essere stampato sulla console: - -``` -scrcpy --print-fps -``` - -Può anche essere abilitato o disabilitato in qualsiasi momento con MOD+i. - -#### Ritaglio - -Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso. - -Questo può essere utile, per esempio, per trasmettere solo un occhio dell'Oculus Go: - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) -``` - -Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il ritaglio. - - -#### Blocca orientamento del video - - -Per bloccare l'orientamento della trasmissione: - -```bash -scrcpy --lock-video-orientation # 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. - - -La [finestra può anche essere ruotata](#rotazione) indipendentemente. - - -#### Codificatore - -Alcuni dispositivi hanno più di un codificatore e alcuni di questi possono provocare problemi o crash. È possibile selezionare un encoder diverso: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -Per elencare i codificatori disponibili puoi immettere un nome di codificatore non valido e l'errore mostrerà i codificatori disponibili: - -```bash -scrcpy --encoder _ -``` - -### Cattura - -#### Registrazione - -È possibile registrare lo schermo durante la trasmissione: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Per disabilitare la trasmissione durante la registrazione: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# interrompere la registrazione con Ctrl+C -``` - -I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo reale (per motivi di prestazioni). I fotogrammi sono _datati_ sul dispositivo, così una [variazione di latenza dei pacchetti][packet delay variation] non impatta il file registrato. - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -#### 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 500 ms di buffer per il v4l2 sink -``` - - -### Connessione - -#### TCP/IP (wireless) - -_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] a un dispositivo mediante TCP/IP. Il dispositivo deve essere collegato alla stessa rete del computer. - -##### Automatico - -Un'opzione `--tcpip` permette di configurare automaticamente la connessione. Ci sono due varianti. - -Se il dispositivo (accessibile a 192.168.1.1 in questo esempio) ascolta già su una porta (tipicamente 5555) per le connessioni adb in entrata, allora esegui: - -```bash -scrcpy --tcpip=192.168.1.1 # la porta predefinita è 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -Se la modalità TCP/IP di adb è disabilitata sul dispositivo (o se non si conosce l'indirizzo IP indirizzo), collegare il dispositivo tramite USB, quindi eseguire: - -```bash -scrcpy --tcpip # senza argomenti -``` - -Il comando troverà automaticamente l'indirizzo IP del dispositivo, abiliterà la modalità TCP/IP, quindi connettersi al dispositivo prima di iniziare. - -##### Manuale - -In alternativa, è possibile abilitare la connessione TCP/IP manualmente usando `adb`: - -1. Inserisci il dispositivo in una porta USB del tuo computer. -2. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer. -3. Ottieni l'indirizzo IP del tuo dispositivo, in Impostazioni → Informazioni sul telefono → Stato, o - eseguendo questo comando: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. -5. Scollega il tuo dispositivo. -6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP` -con l'indirizzo IP del dispositivo che hai trovato)_. -7. Esegui `scrcpy` come al solito. - -Da Android 11, una [opzione di debug wireless][adb-wireless] permette di evitare di dover collegare fisicamente il dispositivo direttamente al computer. - -[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ - -Se la connessione cade casualmente, esegui il comando `scrcpy` per riconnetterti. Se il comando dice che non ci sono dispositivi/emulatori trovati, prova ad eseguire `adb connect DEVICE_IP:5555` di nuovo, e poi `scrcpy` come al solito. Se dice ancora che non ne ha trovato nessuno, prova ad eseguire `adb disconnect` e poi esegui di nuovo questi due comandi. - -Potrebbe essere utile diminuire il bit-rate e la definizione: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versione breve -``` - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - -#### Multi dispositivo - -Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # versione breve -``` - -Se il dispositivo è collegato mediante TCP/IP: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # versione breve -``` - -Se solo un dispositivo è collegato via USB o TCP/IP, è possibile selezionarlo automaticamente: - -```bash -# Select the only device connected via USB -scrcpy -d # like adb -d -scrcpy --select-usb # long version - -# Select the only device connected via TCP/IP -scrcpy -e # like adb -e -scrcpy --select-tcpip # long version -``` - -Puoi avviare più istanze di _scrcpy_ per diversi dispositivi. - - -#### Avvio automativo alla connessione del dispositivo - -Potresti usare [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Tunnels - -Per connettersi a un dispositivo remoto, è possibile collegare un client `adb` locale a un server remoto `adb` (purché usino la stessa versione del protocollo _adb_). ). - -##### Server ADB remoto - -Per connettersi a un server ADB remoto, fate ascoltare il server su tutte le interfacce: - -```bash -adb kill-server -adb -a nodaemon server start -# tienilo aperto -``` - -**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.** - -Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -Per impostazione predefinita, scrcpy utilizza la porta locale utilizzata per il tunnel `adb forward` (tipicamente `27183`, vedi `--port`). È anche possibile forzare una diversa porta del tunnel (può essere utile in situazioni più complesse, quando sono coinvolti più reindirizzamenti): - -``` -scrcpy --tunnel-port=1234 -``` - -##### SSH tunnel - -Per comunicare con un server ADB remoto in modo sicuro, è preferibile utilizzare un tunnel SSH. - -Per prima cosa, assicurati che il server ADB sia in esecuzione sul computer remoto: - -```bash -adb start-server -``` - -Poi, crea un tunnel SSH: - -```bash -# local 5038 --> remote 5037 -# local 27183 <-- remote 27183 -ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer -# keep this open -``` - -Da un altro terminale, esegui scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`) - -```bash -# local 5038 --> remote 5037 -# local 27183 --> remote 27183 -ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer -# tieni questo aperto -``` - -Da un altro terminale, esegui scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - -Come per le connessioni wireless potrebbe essere utile ridurre la qualità: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Configurazione della finestra - -#### Titolo - -Il titolo della finestra è il modello del dispositivo per impostazione predefinita. Esso può essere cambiato: - -```bash -scrcpy --window-title 'My device' -``` - -#### Posizione e dimensione - -La posizione e la dimensione iniziale della finestra può essere specificata: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Senza bordi - -Per disabilitare le decorazioni della finestra: - -```bash -scrcpy --window-borderless -``` - -#### Sempre in primo piano - -Per tenere scrcpy sempre in primo piano: - -```bash -scrcpy --always-on-top -``` - -#### Schermo intero - -L'app può essere avviata direttamente a schermo intero: - -```bash -scrcpy --fullscreen -scrcpy -f # versione breve -``` - -Lo schermo intero può anche essere attivato/disattivato con MOD+f. - -#### Rotazione - -La finestra può essere ruotata: - -```bash -scrcpy --rotation 1 -``` - -I valori possibili sono: - - `0`: nessuna rotazione - - `1`: 90 gradi antiorari - - `2`: 180 gradi - - `3`: 90 gradi orari - -La rotazione può anche essere cambiata dinamicamente con MOD+ -_(sinistra)_ e MOD+ _(destra)_. - -Notare che _scrcpy_ gestisce 3 diversi tipi di rotazione: - - MOD+r richiede al dispositvo di cambiare tra orientamento verticale (portrait) e orizzontale (landscape) (l'app in uso potrebbe rifiutarsi se non supporta l'orientamento richiesto). - - [`--lock-video-orientation`](#blocca-orientamento-del-video) cambia l'orientamento della trasmissione (l'orientamento del video inviato dal dispositivo al computer). Questo influenza la registrazione. - - `--rotation` (o MOD+/MOD+) ruota solo il contenuto della finestra. Questo influenza solo la visualizzazione, non la registrazione. - - -### Altre opzioni di trasmissione - -#### "Sola lettura" - -Per disabilitare i controlli (tutto ciò che può interagire col dispositivo: tasti di input, eventi del mouse, trascina e rilascia (drag&drop) file): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Schermo - -Se sono disponibili più schermi, è possibile selezionare lo schermo da trasmettere: - -```bash -scrcpy --display 1 -``` - -La lista degli id schermo può essere ricavata da: - -```bash -adb shell dumpsys display # cerca "mDisplayId=" nell'output -``` - -Lo schermo secondario potrebbe essere possibile controllarlo solo se il dispositivo esegue almeno Android 10 (in caso contrario è trasmesso in modalità sola lettura). - - -#### Mantenere sbloccato - -Per evitare che il dispositivo si blocchi dopo un po' che il dispositivo è collegato: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -Lo stato iniziale è ripristinato quando scrcpy viene chiuso. - - -#### Spegnere lo schermo - -È possibile spegnere lo schermo del dispositivo durante la trasmissione con un'opzione da riga di comando: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Oppure premendo MOD+o in qualsiasi momento. - -Per riaccenderlo premere MOD+Shift+o. - -In Android il pulsante `POWER` (tasto di accensione) accende sempre lo schermo. Per comodità, se `POWER` è inviato via scrcpy (con click destro o con MOD+p), si forza il dispositivo a spegnere lo schermo dopo un piccolo ritardo (appena possibile). -Il pulsante fisico `POWER` continuerà ad accendere lo schermo normalmente. - -Può anche essere utile evitare il blocco del dispositivo: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### Spegnimento alla chiusura - -Per spegnere lo schermo del dispositivo quando si chiude scrcpy: - -```bash -scrcpy --power-off-on-close -``` - - -#### Mostrare i tocchi - -Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico). - -Android fornisce questa funzionalità nelle _Opzioni sviluppatore_. - -_Scrcpy_ fornisce un'opzione per abilitare questa funzionalità all'avvio e ripristinare il valore iniziale alla chiusura: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Notare che mostra solo i tocchi _fisici_ (con le dita sul dispositivo). - - -#### Disabilitare il salvaschermo - -In maniera predefinita scrcpy non previene l'attivazione del salvaschermo del computer. - -Per disabilitarlo: - -```bash -scrcpy --disable-screensaver -``` - - -### Input di controlli - -#### Rotazione dello schermo del dispostivo - -Premere MOD+r per cambiare tra le modalità verticale (portrait) e orizzontale (landscape). - -Notare che la rotazione avviene solo se l'applicazione in primo piano supporta l'orientamento richiesto. - -#### Copia-incolla - -Quando gli appunti di Android cambiano, essi vengono automaticamente sincronizzati con gli appunti del computer. - -Qualsiasi scorciatoia Ctrl viene inoltrata al dispositivo. In particolare: - - Ctrl+c copia - - Ctrl+x taglia - - Ctrl+v incolla (dopo la sincronizzazione degli appunti da computer a dispositivo) - -Questo solitamente funziona come ci si aspetta. - -Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con Ctrl+c, e _K-9 Mail_ compone un nuovo messaggio. - -Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7): - - MOD+c invia `COPY` - - MOD+x invia `CUT` - - MOD+v invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) - -In aggiunta, MOD+Shift+v permette l'invio del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può compromettere il contenuto non ASCII. - -**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con Ctrl+v che con MOD+v) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera. - -Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di Ctrl+v and MOD+v in modo tale che anch'essi inviino il testo degli appunti del computer come una sequenza di eventi di pressione dei tasti (nella stessa maniera di MOD+Shift+v). - -Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`. - -#### Pizzica per zoomare (pinch-to-zoom) - -Per simulare il "pizzica per zoomare": Ctrl+_click e trascina_. - -Più precisamente, tieni premuto Ctrl mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo. - -Concretamente, scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo. - -#### Simulazione della tastiera fisica (HID) - -Per impostazione predefinita, scrcpy utilizza l'invio dei tasti o del testo di Android: funziona ovunque, ma è limitato all'ASCII. - -In alternativa scrcpy può simulare una tastiera fisica USB su Android per fornire una migliore esperienza di input (utilizzando [USB HID over AOAv2][hid-aoav2]): la tastiera virtuale è disabilitata e funziona per tutti i caratteri e IME. - -[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support - -Tuttavia, funziona solo se il dispositivo è collegato via USB. - -Nota: su Windows, può funzionare solo in [odalità OTG](#otg), non durante il mirroring (non è possibile aprire un dispositivo USB se è già aperto da un altro processo come il daemon adb). - -Per abilitare questa modalità: - -```bash -scrcpy --hid-keyboard -scrcpy -K # versione breve -``` - -Se fallisce per qualche motivo (per esempio perché il dispositivo non è connesso via USB), ritorna automaticamente alla modalità predefinita (con un log nella console). Questo permette di usare le stesse opzioni della linea di comando quando si è connessi via USB e TCP/IP. - -In questa modalità, gli eventi i pressione originali (scancodes) sono inviati al dispositivo, indipendentemente dalla mappatura dei tasti dell'host. Pertanto, se il layout della tua tastiera non corrisponde, deve essere configurato sul dispositivo Android, in Impostazioni → Sistema → Lingue e input → [Tastiera fisica] (in inglese). - -Questa pagina di impostazioni può essere avviata direttamente: - -```bash -adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -``` - -Tuttavia, l'opzione è disponibile solo quando la tastiera HID è abilitata (o quando una tastiera fisica è collegata). - -[Tastiera fisica]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - -#### Simulazione del mouse fisico (HID) - -In modo simile alla simulazione della tastiera fisica, è possibile simulare un mouse fisico. Allo stesso modo funziona solo se il dispositivo è connesso via USB. - -Per impostazione predefinita, scrcpy utilizza l'invio degli eventi del mouse di Android, utilizzando coordinate assolute. Simulando un mouse fisico, un puntatore del mouse appare sul dispositivo Android e vengono inviati i movimenti relativi del mouse, i click e gli scorrimenti. - -Per abilitare questa modalità: - -```bash -scrcpy --hid-mouse -scrcpy -M # versione breve -``` - -Si potrebbe anche aggiungere `--forward-all-clicks` a [inoltra tutti i pulsanti del mouse][forward_all_clicks]. - -[forward_all_clicks]: #click-destro-e-click-centrale - - -Quando questa modalità è attivata, il mouse del computer viene "catturato" (il puntatore del mouse scompare dal computer e appare invece sul dispositivo Android). - -I tasti speciali di cattura, Alt o Super, commutano (disabilitano o abilitano) la cattura del mouse. Usa uno di essi per ridare il controllo del mouse al computer. - - -#### OTG - -È possibile eseguire _scrcpy_ con la sola simulazione della tastiera fisica e del mouse (HID), come se la tastiera e il mouse del computer fossero collegati direttamente al dispositivo tramite un cavo OTG. - -In questa modalità, _adb_ (debug USB) non è necessario e il mirroring è disabilitato. - -Per attivare la modallità OTG: - -```bash -scrcpy --otg -# Passa la seriale se sono disponibili diversi dispositivi USB -scrcpy --otg -s 0123456789abcdef -``` - -È possibile abilitare solo la tastiera HID o il mouse HID: - -```bash -scrcpy --otg --hid-keyboard # solo la tastiera -scrcpy --otg --hid-mouse # solo mouse -scrcpy --otg --hid-keyboard --hid-mouse # tastiera e mouse -# per comodità, abilita entrambi per default -scrcpy --otg # tastiera e mouse -``` - -Come `--hid-keyboard` e `--hid-mouse`, funziona solo se il dispositivo è collegato via USB. - - -#### Preferenze di invio del testo - -Ci sono due tipi di [eventi][textevents] generati quando si scrive testo: - - _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato; - - _eventi di testo_, segnalano che del testo è stato inserito. - -In maniera predefinita le lettere sono inviate usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD). - -Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con: - -```bash -scrcpy --prefer-text -``` - -(ma questo romperà il normale funzionamento della tastiera nei giochi) - -Al contrario, si potrebbe forzare per inviare sempre eventi di pressione grezzi: - -```bash -scrcpy --raw-key-events -``` - -Queste opzioni non hanno effetto sulla tastiera HID (tutti gli eventi di pressione sono inviati come scancodes in questa modalità). - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### Ripetizione di tasti - -In maniera predefinita, tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati. - -Per prevenire l'inoltro ripetuto degli eventi di pressione: - -```bash -scrcpy --no-key-repeat -``` - -Questa opzione non ha effetto sulla tastiera HID (la ripetizione dei tasti è gestita da Android direttamente in questa modalità). - - -#### Click destro e click centrale - -In maniera predefinita, click destro aziona BACK (indietro) o POWER on (accensione) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo: - -```bash -scrcpy --forward-all-clicks -``` - - -### Rilascio di file - -#### Installare APK - -Per installare un APK, trascina e rilascia un file APK (finisce con `.apk`) nella finestra di _scrcpy_. - -Non c'è alcuna risposta visiva, un log è stampato nella console. - - -#### Trasferimento di file verso il dispositivo - -Per trasferire un file in `/sdcard/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/Movies/ -``` - - -### Inoltro dell'audio - -L'audio non è inoltrato da _scrcpy_. Usa [sndcpy]. - -Vedi anche la [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Scociatoie - -Nella lista seguente, MOD è il modificatore delle scorciatoie. In maniera predefinita è Alt (sinistro) o Super (sinistro). - -Può essere cambiato usando `--shortcut-mod`. I tasti possibili sono `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper` (`l` significa sinistro e `r` significa destro). Per esempio: - -```bash -# usa ctrl destro per le scorciatoie -scrcpy --shortcut-mod=rctrl - -# use sia "ctrl sinistro"+"alt sinistro" che "super sinistro" per le scorciatoie -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] è solitamente il pulsante Windows o Cmd._ - -[Super]: https://it.wikipedia.org/wiki/Tasto_Windows - - - | Azione | Scorciatoia - | ------------------------------------------- |:----------------------------- - | Schermo intero | MOD+f - | Rotazione schermo a sinistra | MOD+ _(sinistra)_ - | Rotazione schermo a destra | MOD+ _(destra)_ - | Ridimensiona finestra a 1:1 (pixel-perfect) | MOD+g - | Ridimensiona la finestra per rimuovere i bordi neri | MOD+w \| _Doppio click sinistro¹_ - | Premi il tasto `HOME` | MOD+h \| _Click centrale_ - | Premi il tasto `BACK` | MOD+b \| _Click destro²_ - | Premi il tasto `APP_SWITCH` | MOD+s \| _4° click³_ - | Premi il tasto `MENU` (sblocca lo schermo)⁴ | MOD+m - | Premi il tasto `VOLUME_UP` | MOD+ _(su)_ - | Premi il tasto `VOLUME_DOWN` | MOD+ _(giù)_ - | Premi il tasto `POWER` | MOD+p - | Accendi | _Click destro²_ - | Spegni lo schermo del dispositivo (continua a trasmettere) | MOD+o - | Accendi lo schermo del dispositivo | MOD+Shift+o - | Ruota lo schermo del dispositivo | MOD+r - | Espandi il pannello delle notifiche | MOD+n \| _5° click³_ - | Espandi il pannello delle impostazioni | MOD+n+n \| _Doppio 5° click³_ - | Chiudi pannelli | MOD+Shift+n - | Copia negli appunti⁵ | MOD+c - | Taglia negli appunti⁵ | MOD+x - | Sincronizza gli appunti e incolla⁵ | MOD+v - | Invia il testo degli appunti del computer | MOD+Shift+v - | Abilita/Disabilita il contatore FPS (su stdout) | MOD+i - | Pizzica per zoomare | Ctrl+_click e trascina_ - | Trascina file APK | Installa APK dal computer - | Trascina file non-APK | [Trasferisci file verso il dispositivo](#push-file-to-device) - -_¹Doppio click sui bordi neri per rimuoverli._ -_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ -_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._ -_⁴Per le app native react in sviluppo, `MENU` attiva il menu di sviluppo._ -_⁵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 MOD. -2. Poi premi due volte n. -3. Infine rilascia MOD. - -Tutte le scorciatoie Ctrl+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva. - -## Path personalizzati - -Per utilizzare dei binari _adb_ specifici, configura il suo path nella variabile d'ambente `ADB`: - -```bash -ADB=/percorso/per/adb scrcpy -``` - -Per sovrascrivere il percorso del file `scrcpy-server`, configura il percorso in `SCRCPY_SERVER_PATH`. - -## Perchè _scrcpy_? - -Un collega mi ha sfidato a trovare un nome tanto impronunciabile quanto [gnirehtet]. - -[`strcpy`] copia una **str**ing (stringa); `scrcpy` copia uno **scr**een (schermo). - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - -## Come compilare? - -Vedi [BUILD] (in inglese). - - -## Problemi comuni - -Vedi le [FAQ](FAQ.it.md). - - -## Sviluppatori - -Leggi la [pagina per sviluppatori]. - -[pagina per sviluppatori]: DEVELOP.md - - -## Licenza (in inglese) - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Articoli (in inglese) - -- [Introducendo scrcpy][article-intro] -- [Scrcpy ora funziona wireless][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ - -## Contatti - -Se incontri un bug, per favore leggi prima le [FAQ](FAQ.it.md), poi apri una [issue]. - -[issue]: https://github.com/Genymobile/scrcpy/issues - -Per domande generali o discussioni, puoi anche usare: - - - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) - - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) diff --git a/README.jp.md b/README.jp.md deleted file mode 100644 index 583582fd..00000000 --- a/README.jp.md +++ /dev/null @@ -1,799 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -# scrcpy (v1.19) - -このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux_ 、 _Windows_ そして _macOS_ 上で動作します。 - -![screenshot](assets/screenshot-debian-600.jpg) - -以下に焦点を当てています: - - - **軽量** (ネイティブ、デバイス画面表示のみ) - - **パフォーマンス** (30~60fps) - - **クオリティ** (1920x1080以上) - - **低遅延** ([35~70ms][lowlatency]) - - **短い起動時間** (初回画像を1秒以内に表示) - - **非侵入型** (デバイスに何もインストールされていない状態になる) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - - -## 必要要件 - -AndroidデバイスはAPI21(Android 5.0)以上。 - -Androidデバイスで[adbデバッグが有効][enable-adb]であること。 - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -一部のAndroidデバイスでは、キーボードとマウスを使用して制御する[追加オプション][control]を有効にする必要がある。 - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## アプリの取得 - -Packaging status - -### Linux - -Debian (_testing_ と _sid_) とUbuntu(20.04): - -``` -apt install scrcpy -``` - -[Snap]パッケージが利用可能: [`scrcpy`][snap-link] - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Fedora用[COPR]パッケージが利用可能: [`scrcpy`][copr-link] - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -Arch Linux用[AUR]パッケージが利用可能: [`scrcpy`][aur-link] - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -Gentoo用[Ebuild]が利用可能: [`scrcpy`][ebuild-link] - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -[自分でビルド][BUILD]も可能(心配しないでください、それほど難しくはありません。) - - -### Windows - -Windowsでは簡単に、(`adb`を含む)すべての依存関係を構築済みのアーカイブを利用可能です。 - - - [README](README.md#windows) - -[Chocolatey]でも利用可能です: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # まだ入手していない場合 -``` - -[Scoop]でも利用可能です: - -```bash -scoop install scrcpy -scoop install adb # まだ入手していない場合 -``` - -[Scoop]: https://scoop.sh - -また、[アプリケーションをビルド][BUILD]することも可能です。 - -### macOS - -アプリケーションは[Homebrew]で利用可能です。ただインストールするだけです。 - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -`PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。 - -```bash -brew install android-platform-tools -``` - -`adb`は[MacPorts]からでもインストールできます。 - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - -また、[アプリケーションをビルド][BUILD]することも可能です。 - -## 実行 - -Androidデバイスを接続し、実行: - -```bash -scrcpy -``` - -次のコマンドでリストされるコマンドライン引数も受け付けます: - -```bash -scrcpy --help -``` - -## 機能 - -### キャプチャ構成 - -#### サイズ削減 - -Androidデバイスを低解像度でミラーリングする場合、パフォーマンス向上に便利な場合があります。 - -幅と高さをある値(例:1024)に制限するには: - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # 短縮版 -``` - -一方のサイズはデバイスのアスペクト比が維持されるように計算されます。この方法では、1920x1080のデバイスでは1024x576にミラーリングされます。 - - -#### ビットレート変更 - -ビットレートの初期値は8Mbpsです。ビットレートを変更するには(例:2Mbpsに変更): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # 短縮版 -``` - -#### フレームレート制限 - -キャプチャするフレームレートを制限できます: - -```bash -scrcpy --max-fps 15 -``` - -この機能はAndroid 10からオフィシャルサポートとなっていますが、以前のバージョンでも動作する可能性があります。 - -#### トリミング - -デバイスの画面は、画面の一部のみをミラーリングするようにトリミングできます。 - -これは、例えばOculus Goの片方の目をミラーリングする場合に便利です。: - -```bash -scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440 -``` - -もし`--max-size`も指定されている場合、トリミング後にサイズ変更が適用されます。 - -#### ビデオの向きをロックする - -ミラーリングの向きをロックするには: - -```bash -scrcpy --lock-video-orientation # 現在の向き -scrcpy --lock-video-orientation=0 # 自然な向き -scrcpy --lock-video-orientation=1 # 90°反時計回り -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90°時計回り -``` - -この設定は録画の向きに影響します。 - -[ウィンドウは独立して回転することもできます](#回転)。 - - -#### エンコーダ - -いくつかのデバイスでは一つ以上のエンコーダを持ちます。それらのいくつかは、問題やクラッシュを引き起こします。別のエンコーダを選択することが可能です: - - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -利用可能なエンコーダをリストするために、無効なエンコーダ名を渡すことができます。エラー表示で利用可能なエンコーダを提供します。 - -```bash -scrcpy --encoder _ -``` - -### キャプチャ - -#### 録画 - -ミラーリング中に画面の録画をすることが可能です: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -録画中にミラーリングを無効にするには: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# Ctrl+Cで録画を中断する -``` - -"スキップされたフレーム"は(パフォーマンス上の理由で)リアルタイムで表示されなくても録画されます。 - -フレームはデバイス上で _タイムスタンプされる_ ため [パケット遅延のバリエーション] は録画されたファイルに影響を与えません。 - -[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation - -#### v4l2loopback - -Linuxでは、ビデオストリームをv4l2ループバックデバイスに送信することができます。 -v4l2loopbackのデバイスにビデオストリームを送信することで、Androidデバイスをウェブカメラのようにv4l2対応ツールで開くこともできます。 - -`v4l2loopback` モジュールのインストールが必要です。 - -```bash -sudo apt install v4l2loopback-dkms -``` - -v4l2デバイスを作成する。 - -```bash -sudo modprobe v4l2loopback -``` - -これにより、新しいビデオデバイスが `/dev/videoN` に作成されます。(`N` は整数) -(複数のデバイスや特定のIDのデバイスを作成するために、より多くの[オプション](https://github.com/umlaeute/v4l2loopback#options)が利用可能です。 -多くの[オプション]()が利用可能で複数のデバイスや特定のIDのデバイスを作成できます。 - - -有効なデバイスを一覧表示する: - -```bash -# v4l-utilsパッケージが必要 -v4l2-ctl --list-devices - -# シンプルですが十分これで確認できます -ls /dev/video* -``` - -v4l2シンクを使用してscrcpyを起動する。 - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # ミラーリングウィンドウを無効化する -scrcpy --v4l2-sink=/dev/videoN -N # 短縮版 -``` - -(`N` をデバイス ID に置き換えて、`ls /dev/video*` で確認してください) -有効にすると、v4l2対応のツールでビデオストリームを開けます。 - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLCではバッファリングの遅延が発生する場合があります -``` - -例えばですが [OBS]の中にこの映像を取り込めことができます。 - -[OBS]: https://obsproject.com/ - - -#### Buffering - -バッファリングを追加することも可能です。これによりレイテンシーは増加しますが、ジッターは減少します。(参照 -[#2464]) - -[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 - -このオプションでディスプレイバッファリングを設定できます。 - -```bash -scrcpy --display-buffer=50 # ディスプレイに50msのバッファリングを追加する -``` - -V4L2の場合はこちらのオプションで設定できます。 - -```bash -scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink -``` - -### 接続 - -#### ワイヤレス - -_Scrcpy_ はデバイスとの通信に`adb`を使用します。そして`adb`はTCP/IPを介しデバイスに[接続]することができます: - -1. あなたのコンピュータと同じWi-Fiに接続します。 -2. あなたのIPアドレスを取得します。設定 → 端末情報 → ステータス情報、もしくは、このコマンドを実行します: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. あなたのデバイスでTCP/IPを介したadbを有効にします: `adb tcpip 5555` -4. あなたのデバイスの接続を外します。 -5. あなたのデバイスに接続します: - `adb connect DEVICE_IP:5555` _(`DEVICE_IP`は置き換える)_ -6. 通常通り`scrcpy`を実行します。 - -この方法はビットレートと解像度を減らすのにおそらく有用です: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # 短縮版 -``` - -[接続]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### マルチデバイス - -もし`adb devices`でいくつかのデバイスがリストされる場合、 _シリアルナンバー_ を指定する必要があります: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # 短縮版 -``` - -デバイスがTCP/IPを介して接続されている場合: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # 短縮版 -``` - -複数のデバイスに対して、複数の _scrcpy_ インスタンスを開始することができます。 - -#### デバイス接続での自動起動 - -[AutoAdb]を使用可能です: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### SSHトンネル - -リモートデバイスに接続するため、ローカル`adb`クライアントからリモート`adb`サーバーへ接続することが可能です(同じバージョンの _adb_ プロトコルを使用している場合): - -```bash -adb kill-server # 5037ポートのローカルadbサーバーを終了する -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# オープンしたままにする -``` - -他の端末から: - -```bash -scrcpy -``` - -リモートポート転送の有効化を回避するためには、代わりに転送接続を強制することができます(`-R`の代わりに`-L`を使用することに注意): - -```bash -adb kill-server # 5037ポートのローカルadbサーバーを終了する -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer -# オープンしたままにする -``` - -他の端末から: - -```bash -scrcpy --force-adb-forward -``` - - -ワイヤレス接続と同様に、クオリティを下げると便利な場合があります: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### ウィンドウ構成 - -#### タイトル - -ウィンドウのタイトルはデバイスモデルが初期値です。これは変更できます: - -```bash -scrcpy --window-title 'My device' -``` - -#### 位置とサイズ - -ウィンドウの位置とサイズの初期値を指定できます: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### ボーダーレス - -ウィンドウの装飾を無効化するには: - -```bash -scrcpy --window-borderless -``` - -#### 常に画面のトップ - -scrcpyの画面を常にトップにするには: - -```bash -scrcpy --always-on-top -``` - -#### フルスクリーン - -アプリケーションを直接フルスクリーンで開始できます: - -```bash -scrcpy --fullscreen -scrcpy -f # 短縮版 -``` - -フルスクリーンは、次のコマンドで動的に切り替えることができます MOD+f - - -#### 回転 - -ウィンドウは回転することができます: - -```bash -scrcpy --rotation 1 -``` - -設定可能な値: - - `0`: 回転なし - - `1`: 90° 反時計回り - - `2`: 180° - - `3`: 90° 時計回り - -回転は次のコマンドで動的に変更することができます。 MOD+_(左)_ 、 MOD+_(右)_ - -_scrcpy_ は3つの回転を管理することに注意: - - MOD+rはデバイスに縦向きと横向きの切り替えを要求する(現在実行中のアプリで要求している向きをサポートしていない場合、拒否することがある) - - [`--lock-video-orientation`](#ビデオの向きをロックする)は、ミラーリングする向きを変更する(デバイスからPCへ送信される向き)。録画に影響します。 - - `--rotation` (もしくはMOD+/MOD+)は、ウィンドウのコンテンツのみを回転します。これは表示にのみに影響し、録画には影響しません。 - -### 他のミラーリングオプション - -#### Read-only リードオンリー - -制御を無効にするには(デバイスと対話する全てのもの:入力キー、マウスイベント、ファイルのドラッグ&ドロップ): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### ディスプレイ - -いくつか利用可能なディスプレイがある場合、ミラーリングするディスプレイを選択できます: - -```bash -scrcpy --display 1 -``` - -ディスプレイIDのリストは次の方法で取得できます: - -``` -adb shell dumpsys display # search "mDisplayId=" in the output -``` - -セカンダリディスプレイは、デバイスが少なくともAndroid 10の場合にコントロール可能です。(それ以外ではリードオンリーでミラーリングされます) - - -#### 起動状態にする - -デバイス接続時、少し遅れてからデバイスのスリープを防ぐには: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -scrcpyが閉じられた時、初期状態に復元されます。 - -#### 画面OFF - -コマンドラインオプションを使用することで、ミラーリングの開始時にデバイスの画面をOFFにすることができます: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -もしくは、MOD+oを押すことでいつでもできます。 - -元に戻すには、MOD+Shift+oを押します。 - -Androidでは、`POWER`ボタンはいつでも画面を表示します。便宜上、`POWER`がscrcpyを介して(右クリックもしくはMOD+pを介して)送信される場合、(ベストエフォートベースで)少し遅れて、強制的に画面を非表示にします。ただし、物理的な`POWER`ボタンを押した場合は、画面は表示されます。 - -このオプションはデバイスがスリープしないようにすることにも役立ちます: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - - -#### タッチを表示 - -プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。 - -Androidはこの機能を _開発者オプション_ で提供します。 - -_Scrcpy_ は開始時にこの機能を有効にし、終了時に初期値を復元するオプションを提供します: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -(デバイス上で指を使った) _物理的な_ タッチのみ表示されることに注意してください。 - - -#### スクリーンセーバー無効 - -初期状態では、scrcpyはコンピュータ上でスクリーンセーバーが実行される事を妨げません。 - -これを無効にするには: - -```bash -scrcpy --disable-screensaver -``` - - -### 入力制御 - -#### デバイス画面の回転 - -MOD+rを押すことで、縦向きと横向きを切り替えます。 - -フォアグラウンドのアプリケーションが要求された向きをサポートしている場合のみ回転することに注意してください。 - -#### コピー-ペースト - -Androidのクリップボードが変更される度に、コンピュータのクリップボードに自動的に同期されます。 - -Ctrlのショートカットは全てデバイスに転送されます。特に: - - Ctrl+c 通常はコピーします - - Ctrl+x 通常はカットします - - Ctrl+v 通常はペーストします(コンピュータとデバイスのクリップボードが同期された後) - -通常は期待通りに動作します。 - -しかしながら、実際の動作はアクティブなアプリケーションに依存します。例えば、_Termux_ は代わりにCtrl+cでSIGINTを送信します、そして、_K-9 Mail_ は新しいメッセージを作成します。 - -このようなケースでコピー、カットそしてペーストをするには(Android 7以上でのサポートのみですが): - - MOD+c `COPY`を挿入 - - MOD+x `CUT`を挿入 - - MOD+v `PASTE`を挿入(コンピュータとデバイスのクリップボードが同期された後) - -加えて、MOD+Shift+vはコンピュータのクリップボードテキストにキーイベントのシーケンスとして挿入することを許可します。これはコンポーネントがテキストのペーストを許可しない場合(例えば _Termux_)に有用ですが、非ASCIIコンテンツを壊す可能性があります。 - -**警告:** デバイスにコンピュータのクリップボードを(Ctrl+vまたはMOD+vを介して)ペーストすることは、デバイスのクリップボードにコンテンツをコピーします。結果としてどのAndoridアプリケーションもそのコンテンツを読み取ることができます。機密性の高いコンテンツ(例えばパスワードなど)をこの方法でペーストすることは避けてください。 - -プログラムでデバイスのクリップボードを設定した場合、一部のデバイスは期待どおりに動作しません。`--legacy-paste`オプションは、コンピュータのクリップボードテキストをキーイベントのシーケンスとして挿入するため(MOD+Shift+vと同じ方法)、Ctrl+vMOD+vの動作の変更を提供します。 - -#### ピンチしてズームする - -"ピンチしてズームする"をシミュレートするには: Ctrl+_クリック&移動_ - -より正確にするには、左クリックボタンを押している間、Ctrlを押したままにします。左クリックボタンを離すまで、全てのマウスの動きは、(アプリでサポートされている場合)画面の中心を基準として、コンテンツを拡大縮小および回転します。 - -具体的には、scrcpyは画面の中央を反転した位置にある"バーチャルフィンガー"から追加のタッチイベントを生成します。 - - -#### テキストインジェクション環境設定 - -テキストをタイプした時に生成される2種類の[イベント][textevents]があります: - - _key events_ はキーを押したときと離したことを通知します。 - - _text events_ はテキストが入力されたことを通知します。 - -初期状態で、文字はキーイベントで挿入されるため、キーボードはゲームで期待通りに動作します(通常はWASDキー)。 - -しかし、これは[問題を引き起こす][prefertext]かもしれません。もしこのような問題が発生した場合は、この方法で回避できます: - -```bash -scrcpy --prefer-text -``` - -(しかしこの方法はゲームのキーボードの動作を壊します) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### キーの繰り返し - -初期状態では、キーの押しっぱなしは繰り返しのキーイベントを生成します。これらのイベントが使われない場合でも、この方法は一部のゲームでパフォーマンスの問題を引き起す可能性があります。 - -繰り返しのキーイベントの転送を回避するためには: - -```bash -scrcpy --no-key-repeat -``` - - -#### 右クリックと真ん中クリック - -初期状態では、右クリックはバックの動作(もしくはパワーオン)を起こし、真ん中クリックではホーム画面へ戻ります。このショートカットを無効にし、代わりにデバイスへクリックを転送するには: - -```bash -scrcpy --forward-all-clicks -``` - - -### ファイルのドロップ - -#### APKのインストール - -APKをインストールするには、(`.apk`で終わる)APKファイルを _scrcpy_ の画面にドラッグ&ドロップします。 - -見た目のフィードバックはありません。コンソールにログが出力されます。 - - -#### デバイスにファイルを送る - -デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 - -見た目のフィードバックはありません。コンソールにログが出力されます。 - -転送先ディレクトリを起動時に変更することができます: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - - -### 音声転送 - -音声は _scrcpy_ では転送されません。[sndcpy]を使用します。 - -[issue #14]も参照ください。 - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## ショートカット - -次のリストでは、MODでショートカット変更します。初期状態では、(left)Altまたは(left)Superです。 - -これは`--shortcut-mod`で変更することができます。可能なキーは`lctrl`、`rctrl`、`lalt`、 `ralt`、 `lsuper`そして`rsuper`です。例えば: - -```bash -# RCtrlをショートカットとして使用します -scrcpy --shortcut-mod=rctrl - -# ショートカットにLCtrl+LAltまたはLSuperのいずれかを使用します -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super]は通常WindowsもしくはCmdキーです。_ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | アクション | ショートカット - | ------------------------------------------- |:----------------------------- - | フルスクリーンモードへの切り替え | MOD+f - | ディスプレイを左に回転 | MOD+ _(左)_ - | ディスプレイを右に回転 | MOD+ _(右)_ - | ウィンドウサイズを変更して1:1に変更(ピクセルパーフェクト) | MOD+g - | ウィンドウサイズを変更して黒い境界線を削除 | MOD+w \| _ダブルクリック¹_ - | `HOME`をクリック | MOD+h \| _真ん中クリック_ - | `BACK`をクリック | MOD+b \| _右クリック²_ - | `APP_SWITCH`をクリック | MOD+s \| _4クリック³_ - | `MENU` (画面のアンロック)をクリック | MOD+m - | `VOLUME_UP`をクリック | MOD+ _(上)_ - | `VOLUME_DOWN`をクリック | MOD+ _(下)_ - | `POWER`をクリック | MOD+p - | 電源オン | _右クリック²_ - | デバイス画面をオフにする(ミラーリングしたまま) | MOD+o - | デバイス画面をオンにする | MOD+Shift+o - | デバイス画面を回転する | MOD+r - | 通知パネルを展開する | MOD+n \| _5ボタンクリック³_ - | 設定パネルを展開する | MOD+n+n \| _5ダブルクリック³_ - | 通知パネルを折りたたむ | MOD+Shift+n - | クリップボードへのコピー³ | MOD+c - | クリップボードへのカット³ | MOD+x - | クリップボードの同期とペースト³ | MOD+v - | コンピュータのクリップボードテキストの挿入 | MOD+Shift+v - | FPSカウンタ有効/無効(標準入出力上) | MOD+i - | ピンチしてズームする | Ctrl+_クリック&移動_ - -_¹黒い境界線を削除するため、境界線上でダブルクリック_ -_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._ -_³4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._ -_⁴Android 7以上のみ._ - -キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。 - - 1. MOD キーを押し、押したままにする. - 2. その後に nキーを2回押す. - 3. 最後に MODキーを離す. - -全てのCtrl+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 - -## カスタムパス - -特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します: - - ADB=/path/to/adb scrcpy - -`scrcpy-server`ファイルのパスを上書きするには、`SCRCPY_SERVER_PATH`でそのパスを構成します。 - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - - -## なぜ _scrcpy_? - -同僚が私に、[gnirehtet]のように発音できない名前を見つけるように要求しました。 - -[`strcpy`]は**str**ingをコピーします。`scrcpy`は**scr**eenをコピーします。 - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## ビルド方法は? - -[BUILD]を参照してください。 - -[BUILD]: BUILD.md - - -## よくある質問 - -[FAQ](FAQ.md)を参照してください。 - - -## 開発者 - -[開発者のページ]を読んでください。 - -[開発者のページ]: DEVELOP.md - - -## ライセンス - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## 記事 - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.ko.md b/README.ko.md deleted file mode 100644 index a77cde48..00000000 --- a/README.ko.md +++ /dev/null @@ -1,498 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -# scrcpy (v1.11) - -This document will be updated frequently along with the original Readme file -이 문서는 원어 리드미 파일의 업데이트에 따라 종종 업데이트 될 것입니다 - - 이 어플리케이션은 UBS ( 혹은 [TCP/IP][article-tcpip] ) 로 연결된 Android 디바이스를 화면에 보여주고 관리하는 것을 제공합니다. - _GNU/Linux_, _Windows_ 와 _macOS_ 상에서 작동합니다. - (아래 설명에서 디바이스는 안드로이드 핸드폰을 의미합니다.) - -[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ - -![screenshot](https://github.com/Genymobile/scrcpy/blob/master/assets/screenshot-debian-600.jpg?raw=true) - -주요 기능은 다음과 같습니다. - - - **가벼움** (기본적이며 디바이스의 화면만을 보여줌) - - **뛰어난 성능** (30~60fps) - - **높은 품질** (1920×1080 이상의 해상도) - - **빠른 반응 속도** ([35~70ms][lowlatency]) - - **짧은 부팅 시간** (첫 사진을 보여주는데 최대 1초 소요됨) - - **장치 설치와는 무관함** (디바이스에 설치하지 않아도 됨) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - - -## 요구사항 - -안드로이드 장치는 최소 API 21 (Android 5.0) 을 필요로 합니다. - -디바이스에 [adb debugging][enable-adb]이 가능한지 확인하십시오. - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -어떤 디바이스에서는, 키보드와 마우스를 사용하기 위해서 [추가 옵션][control] 이 필요하기도 합니다. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## 앱 설치하기 - - -### Linux (리눅스) - -리눅스 상에서는 보통 [어플을 직접 설치][BUILD] 해야합니다. 어렵지 않으므로 걱정하지 않아도 됩니다. - -[BUILD]:https://github.com/Genymobile/scrcpy/blob/master/BUILD.md - -[Snap] 패키지가 가능합니다 : [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Arch Linux에서, [AUR] 패키지가 가능합니다 : [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - - -### Windows (윈도우) - -윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) : -해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다. - - [README](README.md#windows) - - -[어플을 직접 설치][BUILD] 할 수도 있습니다. - - -### macOS (맥 OS) - -이 어플리케이션은 아래 사항을 따라 설치한다면 [Homebrew] 에서도 사용 가능합니다 : - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -`PATH` 로부터 접근 가능한 `adb` 가 필요합니다. 아직 설치하지 않았다면 다음을 따라 설치해야 합니다 : - -```bash -brew cask install android-platform-tools -``` - -[어플을 직접 설치][BUILD] 할 수도 있습니다. - - -## 실행 - -안드로이드 디바이스를 연결하고 실행하십시오: - -```bash -scrcpy -``` - -다음과 같이 명령창 옵션 기능도 제공합니다. - -```bash -scrcpy --help -``` - -## 기능 - -### 캡쳐 환경 설정 - - -### 사이즈 재정의 - -가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다. - -너비와 높이를 제한하기 위해 특정 값으로 지정할 수 있습니다 (e.g. 1024) : - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # 축약 버전 -``` - -이 외의 크기도 디바이스의 가로 세로 비율이 유지된 상태에서 계산됩니다. -이러한 방식으로 디바이스 상에서 1920×1080 는 모니터 상에서1024×576로 미러링될 것 입니다. - - -### bit-rate 변경 - -기본 bit-rate 는 8 Mbps입니다. 비디오 bit-rate 를 변경하기 위해선 다음과 같이 입력하십시오 (e.g. 2 Mbps로 변경): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # 축약 버전 -``` - -### 프레임 비율 제한 - -안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다: - -```bash -scrcpy --max-fps 15 -``` - - -### Crop (잘라내기) - -디바이스 화면은 화면의 일부만 미러링하기 위해 잘라질 것입니다. - -예를 들어, *Oculus Go* 의 한 쪽 눈만 미러링할 때 유용합니다 : - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) -scrcpy -c 1224:1440:0:0 # 축약 버전 -``` - -만약 `--max-size` 도 지정하는 경우, 잘라낸 다음에 재정의된 크기가 적용될 것입니다. - - -### 화면 녹화 - -미러링하는 동안 화면 녹화를 할 수 있습니다 : - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -녹화하는 동안 미러링을 멈출 수 있습니다 : - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# Ctrl+C 로 녹화를 중단할 수 있습니다. -# 윈도우 상에서 Ctrl+C 는 정상정으로 종료되지 않을 수 있으므로, 디바이스 연결을 해제하십시오. -``` - -"skipped frames" 은 모니터 화면에 보여지지 않았지만 녹화되었습니다 ( 성능 문제로 인해 ). 프레임은 디바이스 상에서 _타임 스탬프 ( 어느 시점에 데이터가 존재했다는 사실을 증명하기 위해 특정 위치에 시각을 표시 )_ 되었으므로, [packet delay -variation] 은 녹화된 파일에 영향을 끼치지 않습니다. - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - -## 연결 - -### 무선연결 - -_Scrcpy_ 장치와 정보를 주고받기 위해 `adb` 를 사용합니다. `adb` 는 TCIP/IP 를 통해 디바이스와 [연결][connect] 할 수 있습니다 : - -1. 컴퓨터와 디바이스를 동일한 Wi-Fi 에 연결합니다. -2. 디바이스의 IP address 를 확인합니다 (설정 → 내 기기 → 상태 / 혹은 인터넷에 '내 IP'검색 시 확인 가능합니다. ). -3. TCP/IP 를 통해 디바이스에서 adb 를 사용할 수 있게 합니다: `adb tcpip 5555`. -4. 디바이스 연결을 해제합니다. -5. adb 를 통해 디바이스에 연결을 합니다\: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` 대신)_. -6. `scrcpy` 실행합니다. - -다음은 bit-rate 와 해상도를 줄이는데 유용합니다 : - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # 축약 버전 -``` - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - - - -### 여러 디바이스 사용 가능 - -만약에 여러 디바이스들이 `adb devices` 목록에 표시되었다면, _serial_ 을 명시해야합니다: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # 축약 버전 -``` - -_scrcpy_ 로 여러 디바이스를 연결해 사용할 수 있습니다. - - -#### SSH tunnel - -떨어져 있는 디바이스와 연결하기 위해서는, 로컬 `adb` client와 떨어져 있는 `adb` 서버를 연결해야 합니다. (디바이스와 클라이언트가 동일한 버전의 _adb_ protocol을 사용할 경우에 제공됩니다.): - -```bash -adb kill-server # 5037의 로컬 local adb server를 중단 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# 실행 유지 -``` - -다른 터미널에서는 : - -```bash -scrcpy -``` - -무선 연결과 동일하게, 화질을 줄이는 것이 나을 수 있습니다: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -## Window에서의 배치 - -### 맞춤형 window 제목 - -기본적으로, window의 이름은 디바이스의 모델명 입니다. -다음의 명령어를 통해 변경하세요. - -```bash -scrcpy --window-title 'My device' -``` - - -### 배치와 크기 - -초기 window창의 배치와 크기는 다음과 같이 설정할 수 있습니다: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - - -### 경계 없애기 - -윈도우 장식(경계선 등)을 다음과 같이 제거할 수 있습니다: - -```bash -scrcpy --window-borderless -``` - -### 항상 모든 윈도우 위에 실행창 고정 - -이 어플리케이션의 윈도우 창은 다음의 명령어로 다른 window 위에 디스플레이 할 수 있습니다: - -```bash -scrcpy --always-on-top -scrcpy -T # 축약 버전 -``` - -### 전체 화면 - -이 어플리케이션은 전체화면으로 바로 시작할 수 있습니다. - -```bash -scrcpy --fullscreen -scrcpy -f # short version -``` - -전체 화면은 `Ctrl`+`f`키로 끄거나 켤 수 있습니다. - - -## 다른 미러링 옵션 - -### 읽기 전용(Read-only) - -권한을 제한하기 위해서는 (디바이스와 관련된 모든 것: 입력 키, 마우스 이벤트 , 파일의 드래그 앤 드랍(drag&drop)): - -```bash -scrcpy --no-control -scrcpy -n -``` - -### 화면 끄기 - -미러링을 실행하는 와중에 디바이스의 화면을 끌 수 있게 하기 위해서는 -다음의 커맨드 라인 옵션을(command line option) 입력하세요: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -혹은 `Ctrl`+`o`을 눌러 언제든지 디바이스의 화면을 끌 수 있습니다. - -다시 화면을 켜기 위해서는`POWER` (혹은 `Ctrl`+`p`)를 누르세요. - - -### 유효기간이 지난 프레임 제공 (Render expired frames) - -디폴트로, 대기시간을 최소화하기 위해 _scrcpy_ 는 항상 마지막으로 디코딩된 프레임을 제공합니다 -과거의 프레임은 하나씩 삭제합니다. - -모든 프레임을 강제로 렌더링하기 위해서는 (대기 시간이 증가될 수 있습니다) -다음의 명령어를 사용하세요: - -```bash -scrcpy --render-expired-frames -``` - - -### 화면에 터치 나타내기 - -발표를 할 때, 물리적인 기기에 한 물리적 터치를 나타내는 것이 유용할 수 있습니다. - -안드로이드 운영체제는 이런 기능을 _Developers options_에서 제공합니다. - -_Scrcpy_ 는 이런 기능을 시작할 때와 종료할 때 옵션으로 제공합니다. - -```bash -scrcpy --show-touches -scrcpy -t -``` - -화면에 _물리적인 터치만_ 나타나는 것에 유의하세요 (손가락을 디바이스에 대는 행위). - - -### 입력 제어 - -#### 복사-붙여넣기 - -컴퓨터와 디바이스 양방향으로 클립보드를 복사하는 것이 가능합니다: - - - `Ctrl`+`c` 디바이스의 클립보드를 컴퓨터로 복사합니다; - - `Ctrl`+`Shift`+`v` 컴퓨터의 클립보드를 디바이스로 복사합니다; - - `Ctrl`+`v` 컴퓨터의 클립보드를 text event 로써 _붙여넣습니다_ ( 그러나, ASCII 코드가 아닌 경우 실행되지 않습니다 ) - -#### 텍스트 삽입 우선 순위 - -텍스트를 입력할 때 생성되는 두 가지의 [events][textevents] 가 있습니다: - - _key events_, 키가 눌려있는 지에 대한 신호; - - _text events_, 텍스트가 입력되었는지에 대한 신호. - -기본적으로, 글자들은 key event 를 이용해 입력되기 때문에, 키보드는 게임에서처럼 처리합니다 ( 보통 WASD 키에 대해서 ). - -그러나 이는 [issues 를 발생][prefertext]시킵니다. 이와 관련된 문제를 접할 경우, 아래와 같이 피할 수 있습니다: - -```bash -scrcpy --prefer-text -``` - -( 그러나 이는 게임에서의 처리를 중단할 수 있습니다 ) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -### 파일 드랍 - -### APK 실행하기 - -APK를 실행하기 위해서는, APK file(파일명이`.apk`로 끝나는 파일)을 드래그하고 _scrcpy_ window에 드랍하세요 (drag and drop) - -시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다. - -### 디바이스에 파일 push하기 - -디바이스의`/sdcard/`에 파일을 push하기 위해서는, -APK파일이 아닌 파일을_scrcpy_ window에 드래그하고 드랍하세요.(drag and drop). - -시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다. - -해당 디렉토리는 시작할 때 변경이 가능합니다: - -```bash -scrcpy --push-target /sdcard/foo/bar/ -``` - -### 오디오의 전달 - -_scrcpy_는 오디오를 직접 전달해주지 않습니다. [USBaudio] (Linux-only)를 사용하세요. - -추가적으로 [issue #14]를 참고하세요. - -[USBaudio]: https://github.com/rom1v/usbaudio -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - -## 단축키 - - | 실행내용 | 단축키 | 단축키 (macOS) - | -------------------------------------- |:----------------------------- |:----------------------------- - | 전체화면 모드로 전환 | `Ctrl`+`f` | `Cmd`+`f` - | window를 1:1비율로 전환하기(픽셀 맞춤) | `Ctrl`+`g` | `Cmd`+`g` - | 검은 공백 제거 위한 window 크기 조정 | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ - |`HOME` 클릭 | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ - | `BACK` 클릭 | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ - | `APP_SWITCH` 클릭 | `Ctrl`+`s` | `Cmd`+`s` - | `MENU` 클릭 | `Ctrl`+`m` | `Ctrl`+`m` - | `VOLUME_UP` 클릭 | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ - | `VOLUME_DOWN` 클릭 | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ - | `POWER` 클릭 | `Ctrl`+`p` | `Cmd`+`p` - | 전원 켜기 | _Right-click²_ | _Right-click²_ - | 미러링 중 디바이스 화면 끄기 | `Ctrl`+`o` | `Cmd`+`o` - | 알림 패널 늘리기 | `Ctrl`+`n` | `Cmd`+`n` - | 알림 패널 닫기 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` - | 디바이스의 clipboard 컴퓨터로 복사하기 | `Ctrl`+`c` | `Cmd`+`c` - | 컴퓨터의 clipboard 디바이스에 붙여넣기 | `Ctrl`+`v` | `Cmd`+`v` - | Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` - -_¹검은 공백을 제거하기 위해서는 그 부분을 더블 클릭하세요_ -_²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상태에서는 뒤로 돌아갑니다. - -## 맞춤 경로 (custom path) - -특정한 _adb_ binary를 사용하기 위해서는, 그것의 경로를 환경변수로 설정하세요. -`ADB`: - - ADB=/path/to/adb scrcpy - -`scrcpy-server.jar`파일의 경로에 오버라이드 하기 위해서는, 그것의 경로를 `SCRCPY_SERVER_PATH`에 저장하세요. - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - - -## _scrcpy_ 인 이유? - -한 동료가 [gnirehtet]와 같이 발음하기 어려운 이름을 찾을 수 있는지 도발했습니다. - -[`strcpy`] 는 **str**ing을 copy하고; `scrcpy`는 **scr**een을 copy합니다. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - - -## 빌드하는 방법? - -[BUILD]을 참고하세요. - -[BUILD]: BUILD.md - -## 흔한 issue - -[FAQ](FAQ.md)을 참고하세요. - - -## 개발자들 - -[developers page]를 참고하세요. - -[developers page]: DEVELOP.md - - -## 라이선스 - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## 관련 글 (articles) - -- [scrcpy 소개][article-intro] -- [무선으로 연결하는 Scrcpy][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.md b/README.md index db531c61..2ae50c4b 100644 --- a/README.md +++ b/README.md @@ -1209,17 +1209,8 @@ For general questions or discussions, you can also use: ## Translations -This README is available in other languages: - -- [Deutsch (German, `de`) - v1.22](README.de.md) -- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) -- [Italiano (Italiano, `it`) - v1.23](README.it.md) -- [日本語 (Japanese, `jp`) - v1.19](README.jp.md) -- [한국어 (Korean, `ko`) - v1.11](README.ko.md) -- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) -- [Español (Spanish, `sp`) - v1.21](README.sp.md) -- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md) -- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) -- [Turkish (Turkish, `tr`) - v1.18](README.tr.md) +Translations of this README in other languages are available in the [wiki]. + +[wiki]: https://github.com/Genymobile/scrcpy/wiki Only this README file is guaranteed to be up-to-date. diff --git a/README.pt-br.md b/README.pt-br.md deleted file mode 100644 index cc7e5f0b..00000000 --- a/README.pt-br.md +++ /dev/null @@ -1,880 +0,0 @@ -_Apenas o [README](README.md) original é garantido estar atualizado._ - -# 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_. -Funciona em _GNU/Linux_, _Windows_ e _macOS_. - -![screenshot](assets/screenshot-debian-600.jpg) - -Foco em: - - - **leveza** (nativo, mostra apenas a tela do dispositivo) - - **performance** (30~60fps) - - **qualidade** (1920×1080 ou acima) - - **baixa latência** ([35~70ms][lowlatency]) - - **baixo tempo de inicialização** (~1 segundo para mostrar a primeira imagem) - - **não intrusivo** (nada é deixado instalado no dispositivo) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - - -## Requisitos - -O dispositivo Android requer pelo menos a API 21 (Android 5.0). - -Tenha certeza de ter [ativado a depuração adb][enable-adb] no(s) seu(s) dispositivo(s). - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -Em alguns dispositivos, você também precisa ativar [uma opção adicional][control] para -controlá-lo usando teclado e mouse. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## Obter o app - -Packaging status - -### 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): - -``` -apt install scrcpy -``` - -Um pacote [Snap] está disponível: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Para Fedora, um pacote [COPR] está disponível: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -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] ([processo simplificado][BUILD_simple]). - - -### Windows - -Para Windows, por simplicidade, um arquivo pré-compilado com todas as dependências -(incluindo `adb`) está disponível: - - - [README](README.md#windows) - -Também está disponível em [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # se você ainda não o tem -``` - -E no [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # se você ainda não o tem -``` - -[Scoop]: https://scoop.sh - -Você também pode [compilar o app manualmente][BUILD]. - - -### macOS - -A aplicação está disponível em [Homebrew]. Apenas instale-a: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem: - -```bash -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]. - - -## Executar - -Conecte um dispositivo Android e execute: - -```bash -scrcpy -``` - -Também aceita argumentos de linha de comando, listados por: - -```bash -scrcpy --help -``` - -## Funcionalidades - -### Configuração de captura - -#### Reduzir tamanho - -Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para -aumentar a performance. - -Para limitar ambos (largura e altura) para algum valor (ex: 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # versão curta -``` - -A outra dimensão é calculada para que a proporção do dispositivo seja preservada. -Dessa forma, um dispositivo de 1920x1080 será espelhado em 1024x576. - - -#### Mudar bit-rate - -O bit-rate padrão é 8 Mbps. Para mudar o bit-rate do vídeo (ex: para 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # versão curta -``` - -#### Limitar frame rate - -O frame rate de captura pode ser limitado: - -```bash -scrcpy --max-fps 15 -``` - -Isso é oficialmente suportado desde o Android 10, mas pode funcionar em versões anteriores. - -#### Cortar - -A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela. - -Isso é útil por exemplo, para espelhar apenas um olho do Oculus Go: - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0) -``` - -Se `--max-size` também for especificado, o redimensionamento é aplicado após o corte. - - -#### Travar orientação do vídeo - - -Para travar a orientação do espelhamento: - -```bash -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. - -A [janela também pode ser rotacionada](#rotação) independentemente. - - -#### Encoder - -Alguns dispositivos têm mais de um encoder, e alguns deles podem causar problemas ou -travar. É possível selecionar um encoder diferente: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -Para listar os encoders disponíveis, você pode passar um nome de encoder inválido, o -erro dará os encoders disponíveis: - -```bash -scrcpy --encoder _ -``` - -### Captura - -#### Gravando - -É possível gravar a tela enquanto ocorre o espelhamento: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Para desativar o espelhamento durante a gravação: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# interrompa a gravação com Ctrl+C -``` - -"Frames pulados" são gravados, mesmo que não sejam exibidos em tempo real (por -motivos de performance). Frames têm seu _horário carimbado_ no dispositivo, então [variação de atraso nos -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 - -_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se][connect] a um -dispositivo via TCP/IP: - -1. Conecte o dispositivo no mesmo Wi-Fi do seu computador. -2. Pegue o endereço IP do seu dispositivo, em Configurações → Sobre o telefone → Status, ou - executando este comando: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`. -4. Desconecte seu dispositivo. -5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua `DEVICE_IP`)_. -6. Execute `scrcpy` como de costume. - -Pode ser útil diminuir o bit-rate e a resolução: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versão curta -``` - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### Múltiplos dispositivos - -Se vários dispositivos são listados em `adb devices`, você deve especificar o _serial_: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # versão curta -``` - -Se o dispositivo está conectado via TCP/IP: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # versão curta -``` - -Você pode iniciar várias instâncias do _scrcpy_ para vários dispositivos. - -#### Iniciar automaticamente quando dispositivo é conectado - -Você pode usar [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Túnel SSH - -Para conectar-se a um dispositivo remoto, é possível conectar um cliente `adb` local a -um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo -_adb_): - -```bash -adb kill-server # encerra o servidor adb local em 5037 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# mantenha isso aberto -``` - -De outro terminal: - -```bash -scrcpy -``` - -Para evitar ativar o encaminhamento de porta remota, você pode forçar uma conexão -de encaminhamento (note o `-L` em vez de `-R`): - -```bash -adb kill-server # encerra o servidor adb local em 5037 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer -# mantenha isso aberto -``` - -De outro terminal: - -```bash -scrcpy --force-adb-forward -``` - - -Igual a conexões sem fio, pode ser útil reduzir a qualidade: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Configuração de janela - -#### Título - -Por padrão, o título da janela é o modelo do dispositivo. Isso pode ser mudado: - -```bash -scrcpy --window-title 'Meu dispositivo' -``` - -#### Posição e tamanho - -A posição e tamanho iniciais da janela podem ser especificados: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Sem bordas - -Para desativar decorações de janela: - -```bash -scrcpy --window-borderless -``` - -#### Sempre no topo - -Para manter a janela do scrcpy sempre no topo: - -```bash -scrcpy --always-on-top -``` - -#### Tela cheia - -A aplicação pode ser iniciada diretamente em tela cheia: - -```bash -scrcpy --fullscreen -scrcpy -f # versão curta -``` - -Tela cheia pode ser alternada dinamicamente com MOD+f. - -#### Rotação - -A janela pode ser rotacionada: - -```bash -scrcpy --rotation 1 -``` - -Valores possíveis são: - - `0`: sem rotação - - `1`: 90 graus sentido anti-horário - - `2`: 180 graus - - `3`: 90 graus sentido horário - -A rotação também pode ser mudada dinamicamente com MOD+ -_(esquerda)_ e MOD+ _(direita)_. - -Note que _scrcpy_ controla 3 rotações diferentes: - - MOD+r requisita ao dispositivo para mudar entre retrato - e paisagem (a aplicação em execução pode se recusar, se ela não suporta a - orientação requisitada). - - [`--lock-video-orientation`](#travar-orientação-do-vídeo) muda a orientação de - espelhamento (a orientação do vídeo enviado pelo dispositivo para o - computador). Isso afeta a gravação. - - `--rotation` (ou MOD+/MOD+) - rotaciona apenas o conteúdo da janela. Isso afeta apenas a exibição, não a - gravação. - - -### Outras opções de espelhamento - -#### Apenas leitura - -Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, -eventos de mouse, arrastar e soltar arquivos): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Display - -Se vários displays estão disponíveis, é possível selecionar o display para -espelhar: - -```bash -scrcpy --display 1 -``` - -A lista de IDs dos displays pode ser obtida por: - -``` -adb shell dumpsys display # busca "mDisplayId=" na saída -``` - -O display secundário pode apenas ser controlado se o dispositivo roda pelo menos Android -10 (caso contrário é espelhado como apenas leitura). - - -#### Permanecer ativo - -Para evitar que o dispositivo seja suspenso após um delay quando o dispositivo é conectado: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -O estado inicial é restaurado quando o scrcpy é fechado. - - -#### Desligar tela - -É possível desligar a tela do dispositivo durante o início do espelhamento com uma -opção de linha de comando: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Ou apertando MOD+o a qualquer momento. - -Para ligar novamente, pressione MOD+Shift+o. - -No Android, o botão de `POWER` sempre liga a tela. Por conveniência, se -`POWER` é enviado via scrcpy (via clique-direito ou MOD+p), ele -forçará a desligar a tela após um delay pequeno (numa base de melhor esforço). -O botão `POWER` físico ainda causará a tela ser ligada. - -Também pode ser útil evitar que o dispositivo seja suspenso: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - - -#### Mostrar toques - -Para apresentações, pode ser útil mostrar toques físicos (no dispositivo -físico). - -Android fornece esta funcionalidade nas _Opções do desenvolvedor_. - -_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e restaurar o -valor inicial no encerramento: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo). - - -#### Desativar descanso de tela - -Por padrão, scrcpy não evita que o descanso de tela rode no computador. - -Para desativá-lo: - -```bash -scrcpy --disable-screensaver -``` - - -### Controle de entrada - -#### Rotacionar a tela do dispositivo - -Pressione MOD+r para mudar entre os modos retrato e -paisagem. - -Note que só será rotacionado se a aplicação em primeiro plano suportar a -orientação requisitada. - -#### Copiar-colar - -Sempre que a área de transferência do Android muda, é automaticamente sincronizada com a -área de transferência do computador. - -Qualquer atalho com Ctrl é encaminhado para o dispositivo. Em particular: - - Ctrl+c tipicamente copia - - Ctrl+x tipicamente recorta - - Ctrl+v tipicamente cola (após a sincronização de área de transferência - computador-para-dispositivo) - -Isso tipicamente funciona como esperado. - -O comportamento de fato depende da aplicação ativa, no entanto. Por exemplo, -_Termux_ envia SIGINT com Ctrl+c, e _K-9 Mail_ -compõe uma nova mensagem. - -Para copiar, recortar e colar em tais casos (mas apenas suportado no Android >= 7): - - MOD+c injeta `COPY` - - MOD+x injeta `CUT` - - MOD+v injeta `PASTE` (após a sincronização de área de transferência - computador-para-dispositivo) - -Em adição, MOD+Shift+v permite injetar o -texto da área de transferência do computador como uma sequência de eventos de tecla. Isso é útil quando o -componente não aceita colar texto (por exemplo no _Termux_), mas pode -quebrar conteúdo não-ASCII. - -**ADVERTÊNCIA:** Colar a área de transferência do computador para o dispositivo (tanto via -Ctrl+v quanto MOD+v) copia o conteúdo -para a área de transferência do dispositivo. Como consequência, qualquer aplicação Android pode ler -o seu conteúdo. Você deve evitar colar conteúdo sensível (como senhas) dessa -forma. - -Alguns dispositivos não se comportam como esperado quando a área de transferência é definida -programaticamente. Uma opção `--legacy-paste` é fornecida para mudar o comportamento -de Ctrl+v e MOD+v para que eles -também injetem o texto da área de transferência do computador como uma sequência de eventos de tecla (da mesma -forma que MOD+Shift+v). - -#### Pinçar para dar zoom - -Para simular "pinçar para dar zoom": Ctrl+_clicar-e-mover_. - -Mais precisamente, segure Ctrl enquanto pressiona o botão de clique-esquerdo. Até que -o botão de clique-esquerdo seja liberado, todos os movimentos do mouse ampliar e rotacionam o -conteúdo (se suportado pelo app) relativo ao centro da tela. - -Concretamente, scrcpy gera eventos adicionais de toque de um "dedo virtual" em -uma posição invertida em relação ao centro da tela. - - -#### Preferência de injeção de texto - -Existem dois tipos de [eventos][textevents] gerados ao digitar um texto: - - _eventos de tecla_, sinalizando que a tecla foi pressionada ou solta; - - _eventos de texto_, sinalizando que o texto foi inserido. - -Por padrão, letras são injetadas usando eventos de tecla, assim o teclado comporta-se -como esperado em jogos (normalmente para teclas WASD). - -Mas isso pode [causar problemas][prefertext]. Se você encontrar tal problema, você -pode evitá-lo com: - -```bash -scrcpy --prefer-text -``` - -(mas isso vai quebrar o comportamento do teclado em jogos) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### Repetir tecla - -Por padrão, segurar uma tecla gera eventos de tecla repetidos. Isso pode causar -problemas de performance em alguns jogos, onde esses eventos são inúteis de qualquer forma. - -Para evitar o encaminhamento eventos de tecla repetidos: - -```bash -scrcpy --no-key-repeat -``` - - -#### Clique-direito e clique-do-meio - -Por padrão, clique-direito dispara BACK (ou POWER) e clique-do-meio dispara -HOME. Para desabilitar esses atalhos e encaminhar os cliques para o dispositivo: - -```bash -scrcpy --forward-all-clicks -``` - - -### Soltar arquivo - -#### Instalar APK - -Para instalar um APK, arraste e solte o arquivo APK (com extensão `.apk`) na janela -_scrcpy_. - -Não existe feedback visual, um log é imprimido no console. - - -#### Enviar arquivo para dispositivo - -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. - -O diretório alvo pode ser mudado ao iniciar: - -```bash -scrcpy --push-target /sdcard/foo/bar/ -``` - - -### Encaminhamento de áudio - -Áudio não é encaminhado pelo _scrcpy_. Use [sndcpy]. - -Também veja [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Atalhos - -Na lista a seguir, MOD é o modificador de atalho. Por padrão, é -Alt (esquerdo) ou Super (esquerdo). - -Ele pode ser mudado usando `--shortcut-mod`. Possíveis teclas são `lctrl`, `rctrl`, -`lalt`, `ralt`, `lsuper` e `rsuper`. Por exemplo: - -```bash -# usar RCtrl para atalhos -scrcpy --shortcut-mod=rctrl - -# usar tanto LCtrl+LAlt quanto LSuper para atalhos -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] é tipicamente a tecla Windows ou Cmd._ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | Ação | Atalho - | ------------------------------------------- |:----------------------------- - | Mudar modo de tela cheia | MOD+f - | Rotacionar display para esquerda | MOD+ _(esquerda)_ - | Rotacionar display para direita | MOD+ _(direita)_ - | Redimensionar janela para 1:1 (pixel-perfeito) | MOD+g - | Redimensionar janela para remover bordas pretas | MOD+w \| _Clique-duplo-esquerdo¹_ - | Clicar em `HOME` | MOD+h \| _Clique-do-meio_ - | Clicar em `BACK` | MOD+b \| _Clique-direito²_ - | Clicar em `APP_SWITCH` | MOD+s \| _Clique-do-4.°³_ - | Clicar em `MENU` (desbloquear tela) | MOD+m - | Clicar em `VOLUME_UP` | MOD+ _(cima)_ - | Clicar em `VOLUME_DOWN` | MOD+ _(baixo)_ - | Clicar em `POWER` | MOD+p - | Ligar | _Clique-direito²_ - | Desligar tela do dispositivo (continuar espelhando) | MOD+o - | Ligar tela do dispositivo | MOD+Shift+o - | Rotacionar tela do dispositivo | MOD+r - | Expandir painel de notificação | MOD+n \| _Clique-do-5.°³_ - | Expandir painel de configurção | MOD+n+n \| _Clique-duplo-do-5.°³_ - | Colapsar paineis | MOD+Shift+n - | Copiar para área de transferência⁴ | MOD+c - | Recortar para área de transferência⁴ | MOD+x - | Sincronizar áreas de transferência e colar⁴ | MOD+v - | Injetar texto da área de transferência do computador | MOD+Shift+v - | Ativar/desativar contador de FPS (em stdout) | MOD+i - | Pinçar para dar zoom | Ctrl+_Clicar-e-mover_ - -_¹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 MOD. - 2. Depois click duas vezes n. - 3. Finalmente, solte MOD. - -Todos os atalhos Ctrl+_tecla_ são encaminhados para o dispositivo, para que eles sejam -tratados pela aplicação ativa. - - -## Caminhos personalizados - -Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente -`ADB`: - -```bash -ADB=/caminho/para/adb scrcpy -``` - -Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em -`SCRCPY_SERVER_PATH`. - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - - -## Por quê _scrcpy_? - -Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet]. - -[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## Como compilar? - -Veja [BUILD]. - - -## Problemas comuns - -Veja o [FAQ](FAQ.md). - - -## Desenvolvedores - -Leia a [página dos desenvolvedores][developers page]. - -[developers page]: DEVELOP.md - - -## Licença - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Artigos - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.sp.md b/README.sp.md deleted file mode 100644 index 2fc3eb53..00000000 --- a/README.sp.md +++ /dev/null @@ -1,974 +0,0 @@ -Solo se garantiza que el archivo [README](README.md) original esté actualizado. - -# scrcpy (v1.21) - -scrcpy - -Esta aplicación proporciona control e imagen de un dispositivo Android conectado -por USB (o [por TCP/IP](#conexión)). No requiere acceso _root_. -Compatible con _GNU/Linux_, _Windows_ y _macOS_. - -![screenshot](assets/screenshot-debian-600.jpg) - -Se enfoca en: - - **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo - - **rendimiento**: 30~120fps, dependiendo del dispositivo - - **calidad**: 1920×1080 o superior - - **baja latencia**: [35~70ms][lowlatency] - - **inicio rápido**: ~1 segundo para mostrar la primera imagen - - **no intrusivo**: no deja nada instalado en el dispositivo - - **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet - - **libertad**: software gratis y de código abierto - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - -Con la aplicación puede: - - [grabar la pantalla](#capturas-y-grabaciones) - - duplicar la imagen con [la pantalla apagada](#apagar-la-pantalla) - - [copiar y pegar](#copiar-y-pegar) en ambos sentidos - - [configurar la calidad](#configuración-de-captura) - - usar la pantalla del dispositivo [como webcam (V4L2)](#v4l2loopback) (solo en Linux) - - [emular un teclado físico (HID)](#emular-teclado-físico-hid) - (solo en Linux) - - y mucho más… - -## Requisitos - -El dispositivo Android requiere como mínimo API 21 (Android 5.0). - -Asegurate de [habilitar el adb debugging][enable-adb] en tu(s) dispositivo(s). - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -En algunos dispositivos, también necesitas habilitar [una opción adicional][control] para controlarlo con el teclado y ratón. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## Consigue la app - -Packaging status - -### Resumen - - - Linux: `apt install scrcpy` - - Windows: [download](README.md#windows) - - macOS: `brew install scrcpy` - -Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple]) - -[BUILD]: BUILD.md -[BUILD_simple]: BUILD.md#simple - - -### Linux - -En Debian y Ubuntu: - -``` -apt install scrcpy -``` - -Hay un paquete [Snap]: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Para Fedora, hay un paquete [COPR]: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -Para Arch Linux, hay un paquete [AUR]: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -Para Gentoo, hay un paquete [Ebuild]: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -También puedes [construir la aplicación manualmente][BUILD] ([proceso simplificado][BUILD_simple]). - - -### Windows - -Para Windows, por simplicidad, hay un pre-compilado con todas las dependencias -(incluyendo `adb`): - - - [README](README.md#windows) - -También está disponible en [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # si aún no está instalado -``` - -Y en [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # si aún no está instalado -``` - -[Scoop]: https://scoop.sh - -También puedes [construir la aplicación manualmente][BUILD]. - - -### macOS - -La aplicación está disponible en [Homebrew]. Solo instalala: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes: - -```bash -brew install android-platform-tools -``` - -También está disponible en [MacPorts], que configura el adb automáticamente: - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - - -También puedes [construir la aplicación manualmente][BUILD]. - - -## Ejecutar - -Enchufa el dispositivo Android, y ejecuta: - -```bash -scrcpy -``` - -Acepta argumentos desde la línea de comandos, listados en: - -```bash -scrcpy --help -``` - -## Características - -### Configuración de captura - -#### Reducir la definición - -A veces es útil reducir la definición de la imagen del dispositivo Android para aumentar el desempeño. - -Para limitar el ancho y la altura a un valor específico (ej. 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # versión breve -``` - -La otra dimensión es calculada para conservar el aspect ratio del dispositivo. -De esta forma, un dispositivo en 1920×1080 será transmitido a 1024×576. - - -#### Cambiar el bit-rate - -El bit-rate por defecto es 8 Mbps. Para cambiar el bit-rate del video (ej. a 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # versión breve -``` - -#### Limitar los fps - -El fps puede ser limitado: - -```bash -scrcpy --max-fps 15 -``` - -Es oficialmente soportado desde Android 10, pero puede funcionar en versiones anteriores. - -#### Recortar - -La imagen del dispositivo puede ser recortada para transmitir solo una parte de la pantalla. - -Por ejemplo, puede ser útil para transmitir la imagen de un solo ojo del Oculus Go: - -```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 con coordenadas de origen en (0,0) -``` - -Si `--max-size` también está especificado, el cambio de tamaño es aplicado después de cortar. - - -#### Fijar la rotación del video - - -Para fijar la rotación de la transmisión: - -```bash -scrcpy --lock-video-orientation # orientación inicial -scrcpy --lock-video-orientation=0 # orientación normal -scrcpy --lock-video-orientation=1 # 90° contrarreloj -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj -``` - -Esto afecta la rotación de la grabación. - -La [ventana también puede ser rotada](#rotación) independientemente. - - -#### Codificador - -Algunos dispositivos pueden tener más de una rotación, y algunos pueden causar problemas o errores. Es posible seleccionar un codificador diferente: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -Para listar los codificadores disponibles, puedes pasar un nombre de codificador inválido, el error te dará los codificadores disponibles: - -```bash -scrcpy --encoder _ -``` - -### Capturas y grabaciones - - -#### Grabación - -Es posible grabar la pantalla mientras se transmite: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Para grabar sin transmitir la pantalla: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# interrumpe la grabación con Ctrl+C -``` - -Los "skipped frames" son grabados, incluso si no se mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay -variation]" no impacta el archivo grabado. - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -#### v4l2loopback - -En Linux se puede mandar el stream del video a un dispositivo loopback v4l2, por -lo que se puede abrir el dispositivo Android como una webcam con cualquier -programa compatible con v4l2. - -Se debe instalar el modulo `v4l2loopback`: - -```bash -sudo apt install v4l2loopback-dkms -``` - -Para crear un dispositivo v4l2: - -```bash -sudo modprobe v4l2loopback -``` - -Esto va a crear un nuevo dispositivo de video en `/dev/videoN`, donde `N` es un número -(hay más [opciones](https://github.com/umlaeute/v4l2loopback#options) disponibles -para crear múltiples dispositivos o usar un ID en específico). - -Para ver los dispositivos disponibles: - -```bash -# requiere el paquete v4l-utils -v4l2-ctl --list-devices -# simple pero generalmente suficiente -ls /dev/video* -``` - -Para iniciar scrcpy usando una fuente v4l2: - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # deshabilita la transmisión de imagen -scrcpy --v4l2-sink=/dev/videoN -N # más corto -``` - -(reemplace `N` con el ID del dispositivo, compruebe con `ls /dev/video*`) - -Una vez habilitado, podés abrir el stream del video con una herramienta compatible con v4l2: - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLC puede agregar un delay por buffering -``` - -Por ejemplo, podrías capturar el video usando [OBS]. - -[OBS]: https://obsproject.com/ - - -#### Buffering - -Es posible agregar buffering al video. Esto reduce el ruido en la imagen ("jitter") -pero aumenta la latencia (vea [#2464]). - -[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 - -La opción de buffering está disponible para la transmisión de imagen: - -```bash -scrcpy --display-buffer=50 # agrega 50 ms de buffering a la imagen -``` - -y las fuentes V4L2: - -```bash -scrcpy --v4l2-buffer=500 # agrega 500 ms de buffering a la fuente v4l2 -``` - - -### Conexión - -#### TCP/IP (Inalámbrica) - -_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP. -El dispositivo debe estar conectado a la misma red que la computadora: - -##### Automático - -La opción `--tcpip` permite configurar la conexión automáticamente. Hay 2 variables. - -Si el dispositivo (accesible en 192.168.1.1 para este ejemplo) ya está escuchando -en un puerto (generalmente 5555) esperando una conexión adb entrante, entonces corré: - -```bash -scrcpy --tcpip=192.168.1.1 # el puerto default es 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -Si el dispositivo no tiene habilitado el modo adb TCP/IP (o si no sabés la dirección IP), -entonces conectá el dispositivo por USB y corré: - -```bash -scrcpy --tcpip # sin argumentos -``` - -El programa buscará automáticamente la IP del dispositivo, habilitará el modo TCP/IP, y -se conectará al dispositivo antes de comenzar a transmitir la imagen. - -##### Manual - -Como alternativa, se puede habilitar la conexión TCP/IP manualmente usando `adb`: - -1. Conecta el dispositivo al mismo Wi-Fi que tu computadora. -2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. Habilita adb vía TCP/IP en el dispositivo: `adb tcpip 5555`. -4. Desenchufa el dispositivo. -5. Conéctate a tu dispositivo: `adb connect IP_DEL_DISPOSITIVO:5555` _(reemplaza `IP_DEL_DISPOSITIVO`)_. -6. Ejecuta `scrcpy` con normalidad. - -Podría resultar útil reducir el bit-rate y la definición: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # versión breve -``` - -[conectarse]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### Múltiples dispositivos - -Si hay muchos dispositivos listados en `adb devices`, será necesario especificar el _número de serie_: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # versión breve -``` - -Si el dispositivo está conectado por TCP/IP: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # versión breve -``` - -Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos. - -#### Iniciar automáticamente al detectar dispositivo - -Puedes utilizar [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Túneles - -Para conectarse a un dispositivo remoto, es posible conectar un cliente local `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_). - -##### Servidor ADB remoto - -Para conectarse a un servidor ADB remoto, haz que el servidor escuche en todas las interfaces: - -```bash -adb kill-server -adb -a nodaemon server start -# conserva este servidor abierto -``` - -**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.** - -Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra -terminal, corré scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -Por default, scrcpy usa el puerto local que se usó para establecer el tunel -`adb forward` (típicamente `27183`, vea `--port`). También es posible forzar un -puerto diferente (puede resultar útil en situaciones más complejas, donde haya -múltiples redirecciones): - -``` -scrcpy --tunnel-port=1234 -``` - - -##### Túnel SSH - -Para comunicarse con un servidor ADB remoto de forma segura, es preferible usar un túnel SSH. - -Primero, asegurate que el servidor ADB está corriendo en la computadora remota: - -```bash -adb start-server -``` - -Después, establecé el túnel SSH: - -```bash -# local 5038 --> remoto 5037 -# local 27183 <-- remoto 27183 -ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer -# conserva este servidor abierto -``` - -Desde otra terminal, corré scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`): - -```bash -# local 5038 --> remoto 5037 -# local 27183 --> remoto 27183 -ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer -# conserva este servidor abierto -``` - -Desde otra terminal, corré scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - -Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Configuración de la ventana - -#### Título - -Por defecto, el título de la ventana es el modelo del dispositivo. Puede ser modificado: - -```bash -scrcpy --window-title 'My device' -``` - -#### Posición y tamaño - -La posición y tamaño inicial de la ventana puede ser especificado: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Sin bordes - -Para deshabilitar el diseño de la ventana: - -```bash -scrcpy --window-borderless -``` - -#### Siempre adelante - -Para mantener la ventana de scrcpy siempre adelante: - -```bash -scrcpy --always-on-top -``` - -#### Pantalla completa - -La aplicación puede ser iniciada en pantalla completa: - -```bash -scrcpy --fullscreen -scrcpy -f # versión breve -``` - -Puede entrar y salir de la pantalla completa con la combinación MOD+f. - -#### Rotación - -Se puede rotar la ventana: - -```bash -scrcpy --rotation 1 -``` - -Los posibles valores son: - - `0`: sin rotación - - `1`: 90 grados contrarreloj - - `2`: 180 grados - - `3`: 90 grados en sentido de las agujas del reloj - -La rotación también puede ser modificada con la combinación de teclas MOD+ _(izquierda)_ y MOD+ _(derecha)_. - -Nótese que _scrcpy_ maneja 3 diferentes rotaciones: - - MOD+r solicita al dispositivo cambiar entre vertical y horizontal (la aplicación en uso puede rechazarlo si no soporta la orientación solicitada). - - [`--lock-video-orientation`](#fijar-la-rotación-del-video) cambia la rotación de la transmisión (la orientación del video enviado a la PC). Esto afecta a la grabación. - - `--rotation` (o MOD+/MOD+) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación. - - -### Otras opciones - -#### Solo lectura ("Read-only") - -Para deshabilitar los controles (todo lo que interactúe con el dispositivo: eventos del teclado, eventos del mouse, arrastrar y soltar archivos): - -```bash -scrcpy --no-control -scrcpy -n # versión breve -``` - -#### Pantalla - -Si múltiples pantallas están disponibles, es posible elegir cual transmitir: - -```bash -scrcpy --display 1 -``` - -Los ids de las pantallas se pueden obtener con el siguiente comando: - -```bash -adb shell dumpsys display # busque "mDisplayId=" en la respuesta -``` - -La segunda pantalla solo puede ser manejada si el dispositivo cuenta con Android 10 (en caso contrario será transmitida en el modo solo lectura). - - -#### Permanecer activo - -Para evitar que el dispositivo descanse después de un tiempo mientras está conectado: - -```bash -scrcpy --stay-awake -scrcpy -w # versión breve -``` - -La configuración original se restaura al cerrar scrcpy. - - -#### Apagar la pantalla - -Es posible apagar la pantalla mientras se transmite al iniciar con el siguiente comando: - -```bash -scrcpy --turn-screen-off -scrcpy -S # versión breve -``` - -O presionando MOD+o en cualquier momento. - -Para volver a prenderla, presione MOD+Shift+o. - -En Android, el botón de `POWER` siempre prende la pantalla. Por conveniencia, si `POWER` es enviado vía scrcpy (con click-derecho o MOD+p), esto forzará a apagar la pantalla con un poco de atraso (en la mejor de las situaciones). El botón físico `POWER` seguirá prendiendo la pantalla. - -También puede resultar útil para evitar que el dispositivo entre en inactividad: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw # versión breve -``` - - -#### Apagar al cerrar la aplicación - -Para apagar la pantalla del dispositivo al cerrar scrcpy: - -```bash -scrcpy --power-off-on-close -``` - -#### Mostrar clicks - -Para presentaciones, puede resultar útil mostrar los clicks físicos (en el dispositivo físicamente). - -Android provee esta opción en _Opciones para desarrolladores_. - -_Scrcpy_ provee una opción para habilitar esta función al iniciar la aplicación y restaurar el valor original al salir: - -```bash -scrcpy --show-touches -scrcpy -t # versión breve -``` - -Nótese que solo muestra los clicks _físicos_ (con el dedo en el dispositivo). - - -#### Desactivar protector de pantalla - -Por defecto, scrcpy no evita que el protector de pantalla se active en la computadora. - -Para deshabilitarlo: - -```bash -scrcpy --disable-screensaver -``` - - -### Control - -#### Rotar pantalla del dispositivo - -Presione MOD+r para cambiar entre posición vertical y horizontal. - -Nótese que solo rotará si la aplicación activa soporta la orientación solicitada. - -#### Copiar y pegar - -Cuando que el portapapeles de Android cambia, automáticamente se sincroniza al portapapeles de la computadora. - -Cualquier shortcut con Ctrl es enviado al dispositivo. En particular: - - Ctrl+c normalmente copia - - Ctrl+x normalmente corta - - Ctrl+v normalmente pega (después de la sincronización de portapapeles entre la computadora y el dispositivo) - -Esto normalmente funciona como es esperado. - -Sin embargo, este comportamiento depende de la aplicación en uso. Por ejemplo, _Termux_ envía SIGINT con Ctrl+c, y _K-9 Mail_ crea un nuevo mensaje. - -Para copiar, cortar y pegar, en tales casos (solo soportado en Android >= 7): - - MOD+c inyecta `COPY` - - MOD+x inyecta `CUT` - - MOD+v inyecta `PASTE` (después de la sincronización de portapapeles entre la computadora y el dispositivo) - -Además, MOD+Shift+v permite inyectar el texto en el portapapeles de la computadora como una secuencia de teclas. Esto es útil cuando el componente no acepta pegado de texto (por ejemplo en _Termux_), pero puede romper caracteres no pertenecientes a ASCII. - -**AVISO:** Pegar de la computadora al dispositivo (tanto con Ctrl+v o MOD+v) copia el contenido al portapapeles del dispositivo. Como consecuencia, cualquier aplicación de Android puede leer su contenido. Debería evitar pegar contenido sensible (como contraseñas) de esta forma. - -Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de Ctrl+v y MOD+v para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que MOD+Shift+v). - -Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`. - -#### Pellizcar para zoom - -Para simular "pinch-to-zoom": Ctrl+_click-y-mover_. - -Más precisamente, mantén Ctrl mientras presionas botón izquierdo. Hasta que no se suelte el botón, todos los movimientos del mouse cambiarán el tamaño y rotación del contenido (si es soportado por la app en uso) respecto al centro de la pantalla. - -Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla. - -#### Emular teclado físico (HID) - -Por default, scrcpy usa el sistema de Android para la injección de teclas o texto: -funciona en todas partes, pero está limitado a ASCII. - -En Linux, scrcpy puede emular un teclado USB físico en Android para proveer -una mejor experiencia al enviar _inputs_ (usando [USB HID vía AOAv2][hid-aoav2]): -deshabilita el teclado virtual y funciona para todos los caracteres y IME. - -[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support - -Sin embargo, solo funciona si el dispositivo está conectado por USB, y por ahora -solo funciona en Linux. - -Para habilitar este modo: - -```bash -scrcpy --hid-keyboard -scrcpy -K # más corto -``` - -Si por alguna razón falla (por ejemplo si el dispositivo no está conectado vía -USB), automáticamente vuelve al modo default (un mensaje se escribirá en la consola). -Se puede usar los mismos argumentos en la línea de comandos tanto si se conecta con -USB o vía TCP/IP. - -En este modo, los _raw key events_ (_scancodes_) se envían al dispositivo, independientemente -del mapeo del teclado en el host. Por eso, si el diseño de tu teclado no concuerda, debe ser -configurado en el dispositivo Android, en Ajustes → Sistema → Idioma y Entrada de Texto -→ [Teclado Físico]. - -Se puede iniciar automáticamente en esta página de ajustes: - -```bash -adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -``` - -Sin embargo, la opción solo está disponible cuando el teclado HID está activo -(o cuando se conecta un teclado físico). - -[Teclado Físico]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - - -#### Preferencias de inyección de texto - -Existen dos tipos de [eventos][textevents] generados al escribir texto: - - _key events_, marcando si la tecla es presionada o soltada; - - _text events_, marcando si un texto fue introducido. - -Por defecto, las letras son inyectadas usando _key events_, para que el teclado funcione como es esperado en juegos (típicamente las teclas WASD). - -Pero esto puede [causar problemas][prefertext]. Si encuentras tales problemas, los puedes evitar con: - -```bash -scrcpy --prefer-text -``` - -(Pero esto romperá el comportamiento del teclado en los juegos) - -Por el contrario, se puede forzar scrcpy para siempre injectar _raw key events_: - -```bash -scrcpy --raw-key-events -``` - -Estas opciones no tienen efecto en los teclados HID (todos los _key events_ son enviados como -_scancodes_ en este modo). - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### Repetir tecla - -Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede -causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos. - -Para evitar enviar _key events_ repetidos: - -```bash -scrcpy --no-key-repeat -``` - -Estas opciones no tienen efecto en los teclados HID (Android maneja directamente -las repeticiones de teclas en este modo) - - -#### Botón derecho y botón del medio - -Por defecto, botón derecho ejecuta RETROCEDER (o ENCENDIDO) y botón del medio INICIO. Para inhabilitar estos atajos y enviar los clicks al dispositivo: - -```bash -scrcpy --forward-all-clicks -``` - - -### Arrastrar y soltar archivos - -#### Instalar APKs - -Para instalar un APK, arrastre y suelte el archivo APK (terminado en `.apk`) a la ventana de _scrcpy_. - -No hay respuesta visual, un mensaje se escribirá en la consola. - - -#### Enviar archivos al dispositivo - -Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte -un archivo (no APK) a la ventana de _scrcpy_. - -No hay ninguna respuesta visual, un mensaje se escribirá en la consola. - -El directorio de destino puede ser modificado al iniciar: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - - -### Envío de Audio - -_Scrcpy_ no envía el audio. Use [sndcpy]. - -También lea [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Atajos - -En la siguiente lista, MOD es el atajo modificador. Por defecto es Alt (izquierdo) o Super (izquierdo). - -Se puede modificar usando `--shortcut-mod`. Las posibles teclas son `lctrl` (izquierdo), `rctrl` (derecho), `lalt` (izquierdo), `ralt` (derecho), `lsuper` (izquierdo) y `rsuper` (derecho). Por ejemplo: - -```bash -# use RCtrl para los atajos -scrcpy --shortcut-mod=rctrl - -# use tanto LCtrl+LAlt o LSuper para los atajos -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] es generalmente la tecla Windows o Cmd._ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | Acción | Atajo - | ------------------------------------------- |:----------------------------- - | Alterne entre pantalla compelta | MOD+f - | Rotar pantalla hacia la izquierda | MOD+ _(izquierda)_ - | Rotar pantalla hacia la derecha | MOD+ _(derecha)_ - | Ajustar ventana a 1:1 ("pixel-perfect") | MOD+g - | Ajustar ventana para quitar los bordes negros| MOD+w \| _Doble click izquierdo¹_ - | Click en `INICIO` | MOD+h \| _Click medio_ - | Click en `RETROCEDER` | MOD+b \| _Click derecho²_ - | Click en `CAMBIAR APLICACIÓN` | MOD+s \| _Cuarto botón³_ - | Click en `MENÚ` (desbloquear pantalla)⁴ | MOD+m - | Click en `SUBIR VOLUMEN` | MOD+ _(arriba)_ - | Click en `BAJAR VOLUME` | MOD+ _(abajo)_ - | Click en `ENCENDIDO` | MOD+p - | Encendido | _Botón derecho²_ - | Apagar pantalla (manteniendo la transmisión) | MOD+o - | Encender pantalla | MOD+Shift+o - | Rotar pantalla del dispositivo | MOD+r - | Abrir panel de notificaciones | MOD+n \| _Quinto botón³_ - | Abrir panel de configuración | MOD+n+n \| _Doble quinto botón³_ - | Cerrar paneles | MOD+Shift+n - | Copiar al portapapeles⁵ | MOD+c - | Cortar al portapapeles⁵ | MOD+x - | Synchronizar portapapeles y pegar⁵ | MOD+v - | Inyectar texto del portapapeles de la PC | MOD+Shift+v - | Habilitar/Deshabilitar contador de FPS (en stdout) | MOD+i - | Pellizcar para zoom | Ctrl+_click-y-mover_ - | Arrastrar y soltar un archivo (APK) | Instalar APK desde la computadora - | Arrastrar y soltar un archivo (no APK) | [Mover archivo al dispositivo](#enviar-archivos-al-dispositivo) - -_¹Doble click en los bordes negros para eliminarlos._ -_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._ -_³Cuarto y quinto botón del mouse, si tu mouse los tiene._ -_⁴Para las apps react-native en desarrollo, `MENU` activa el menú de desarrollo._ -_⁵Solo en Android >= 7._ - -Los shortcuts con teclas repetidas se ejecutan soltando y volviendo a apretar la tecla -por segunda vez. Por ejemplo, para ejecutar "Abrir panel de configuración": - - 1. Apretá y mantené apretado MOD. - 2. Después apretá dos veces la tecla n. - 3. Por último, soltá la tecla MOD. - -Todos los atajos Ctrl+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa. - - -## Path personalizado - -Para usar un binario de _adb_ en particular, configure el path `ADB` en las variables de entorno: - -```bash -ADB=/path/to/adb scrcpy -``` - -Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`. - -Para sobreescribir el ícono, configure el path en `SCRCPY_ICON_PATH`. - - -## ¿Por qué _scrcpy_? - -Un colega me retó a encontrar un nombre tan impronunciable como [gnirehtet]. - -[`strcpy`] copia un **str**ing; `scrcpy` copia un **scr**een. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## ¿Cómo construir (BUILD)? - -Véase [BUILD] (en inglés). - - -## Problemas generales - -Vea las [preguntas frecuentes (en inglés)](FAQ.md). - - -## Desarrolladores - -Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md). - - -## Licencia - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Artículos - -- [Introducing scrcpy][article-intro] (en inglés) -- [Scrcpy now works wirelessly][article-tcpip] (en inglés) - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.tr.md b/README.tr.md deleted file mode 100644 index 9501a889..00000000 --- a/README.tr.md +++ /dev/null @@ -1,824 +0,0 @@ -# scrcpy (v1.18) - -Bu uygulama Android cihazların USB (ya da [TCP/IP][article-tcpip]) üzerinden -görüntülenmesini ve kontrol edilmesini sağlar. _root_ erişimine ihtiyaç duymaz. -_GNU/Linux_, _Windows_ ve _macOS_ sistemlerinde çalışabilir. - -![screenshot](assets/screenshot-debian-600.jpg) - -Öne çıkan özellikler: - -- **hafiflik** (doğal, sadece cihazın ekranını gösterir) -- **performans** (30~60fps) -- **kalite** (1920×1080 ya da üzeri) -- **düşük gecikme süresi** ([35~70ms][lowlatency]) -- **düşük başlangıç süresi** (~1 saniye ilk kareyi gösterme süresi) -- **müdaheleci olmama** (cihazda kurulu yazılım kalmaz) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - -## Gereksinimler - -Android cihaz en düşük API 21 (Android 5.0) olmalıdır. - -[Adb hata ayıklamasının][enable-adb] cihazınızda aktif olduğundan emin olun. - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -Bazı cihazlarda klavye ve fare ile kontrol için [ilave bir seçenek][control] daha -etkinleştirmeniz gerekebilir. - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - -## Uygulamayı indirin - -Packaging status - -### Özet - -- Linux: `apt install scrcpy` -- Windows: [indir][direct-win64] -- macOS: `brew install scrcpy` - -Kaynak kodu derle: [BUILD] ([basitleştirilmiş süreç][build_simple]) - -[build]: BUILD.md -[build_simple]: BUILD.md#simple - -### Linux - -Debian (şimdilik _testing_ ve _sid_) ve Ubuntu (20.04) için: - -``` -apt install scrcpy -``` - -[Snap] paketi: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -Fedora için, [COPR] paketi: [`scrcpy`][copr-link]. - -[copr]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -Arch Linux için, [AUR] paketi: [`scrcpy`][aur-link]. - -[aur]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -Gentoo için, [Ebuild] mevcut: [`scrcpy/`][ebuild-link]. - -[ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -Ayrıca [uygulamayı el ile de derleyebilirsiniz][build] ([basitleştirilmiş süreç][build_simple]). - -### Windows - -Windows için (`adb` dahil) tüm gereksinimleri ile derlenmiş bir arşiv mevcut: - - - [README](README.md#windows) - -[Chocolatey] ile kurulum: - -[chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # if you don't have it yet -``` - -[Scoop] ile kurulum: - -```bash -scoop install scrcpy -scoop install adb # if you don't have it yet -``` - -[scoop]: https://scoop.sh - -Ayrıca [uygulamayı el ile de derleyebilirsiniz][build]. - -### macOS - -Uygulama [Homebrew] içerisinde mevcut. Sadece kurun: - -[homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -`adb`, `PATH` içerisinden erişilebilir olmalıdır. Eğer değilse: - -```bash -brew install android-platform-tools -``` - -[MacPorts] kullanılarak adb ve uygulamanın birlikte kurulumu yapılabilir: - -```bash -sudo port install scrcpy -``` - -[macports]: https://www.macports.org/ - -Ayrıca [uygulamayı el ile de derleyebilirsiniz][build]. - -## Çalıştırma - -Android cihazınızı bağlayın ve aşağıdaki komutu çalıştırın: - -```bash -scrcpy -``` - -Komut satırı argümanları aşağıdaki komut ile listelenebilir: - -```bash -scrcpy --help -``` - -## Özellikler - -### Ekran yakalama ayarları - -#### Boyut azaltma - -Bazen, Android cihaz ekranını daha düşük seviyede göstermek performansı artırabilir. - -Hem genişliği hem de yüksekliği bir değere sabitlemek için (ör. 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # kısa versiyon -``` - -Diğer boyut en-boy oranı korunacak şekilde hesaplanır. -Bu şekilde ekran boyutu 1920x1080 olan bir cihaz 1024x576 olarak görünür. - -#### Bit-oranı değiştirme - -Varsayılan bit-oranı 8 Mbps'dir. Değiştirmek için (ör. 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # kısa versiyon -``` - -#### Çerçeve oranı sınırlama - -Ekran yakalama için maksimum çerçeve oranı için sınır koyulabilir: - -```bash -scrcpy --max-fps 15 -``` - -Bu özellik Android 10 ve sonrası sürümlerde resmi olarak desteklenmektedir, -ancak daha önceki sürümlerde çalışmayabilir. - -#### Kesme - -Cihaz ekranının sadece bir kısmı görünecek şekilde kesilebilir. - -Bu özellik Oculus Go'nun bir gözünü yakalamak gibi durumlarda kullanışlı olur: - -```bash -scrcpy --crop 1224:1440:0:0 # (0,0) noktasından 1224x1440 -``` - -Eğer `--max-size` belirtilmişse yeniden boyutlandırma kesme işleminden sonra yapılır. - -#### Video yönünü kilitleme - -Videonun yönünü kilitlemek için: - -```bash -scrcpy --lock-video-orientation # başlangıç yönü -scrcpy --lock-video-orientation=0 # doğal yön -scrcpy --lock-video-orientation=1 # 90° saatin tersi yönü -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90° saat yönü -``` - -Bu özellik kaydetme yönünü de etkiler. - -[Pencere ayrı olarak döndürülmüş](#rotation) olabilir. - -#### Kodlayıcı - -Bazı cihazlar birden fazla kodlayıcıya sahiptir, ve bunların bazıları programın -kapanmasına sebep olabilir. Bu durumda farklı bir kodlayıcı seçilebilir: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -Mevcut kodlayıcıları listelemek için geçerli olmayan bir kodlayıcı ismi girebilirsiniz, -hata mesajı mevcut kodlayıcıları listeleyecektir: - -```bash -scrcpy --encoder _ -``` - -### Yakalama - -#### Kaydetme - -Ekran yakalama sırasında kaydedilebilir: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -Yakalama olmadan kayıt için: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# Ctrl+C ile kayıt kesilebilir -``` - -"Atlanan kareler" gerçek zamanlı olarak gösterilmese (performans sebeplerinden ötürü) dahi kaydedilir. -Kareler cihazda _zamandamgası_ ile saklanır, bu sayede [paket gecikme varyasyonu] -kayıt edilen dosyayı etkilemez. - -[paket gecikme varyasyonu]: https://en.wikipedia.org/wiki/Packet_delay_variation - -#### v4l2loopback - -Linux'ta video akışı bir v4l2 loopback cihazına gönderilebilir. Bu sayede Android -cihaz bir web kamerası gibi davranabilir. - -Bu işlem için `v4l2loopback` modülü kurulu olmalıdır: - -```bash -sudo apt install v4l2loopback-dkms -``` - -v4l2 cihazı oluşturmak için: - -```bash -sudo modprobe v4l2loopback -``` - -Bu komut `/dev/videoN` adresinde `N` yerine bir tamsayı koyarak yeni bir video -cihazı oluşturacaktır. -(birden fazla cihaz oluşturmak veya spesifik ID'ye sahip cihazlar için -diğer [seçenekleri](https://github.com/umlaeute/v4l2loopback#options) inceleyebilirsiniz.) - -Aktif cihazları listelemek için: - -```bash -# v4l-utils paketi ile -v4l2-ctl --list-devices - -# daha basit ama yeterli olabilecek şekilde -ls /dev/video* -``` - -v4l2 kullanarak scrpy kullanmaya başlamak için: - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # ayna penceresini kapatarak -scrcpy --v4l2-sink=/dev/videoN -N # kısa versiyon -``` - -(`N` harfini oluşturulan cihaz ID numarası ile değiştirin. `ls /dev/video*` cihaz ID'lerini görebilirsiniz.) - -Aktifleştirildikten sonra video akışını herhangi bir v4l2 özellikli araçla açabilirsiniz: - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLC kullanırken yükleme gecikmesi olabilir -``` - -Örneğin, [OBS] ile video akışını kullanabilirsiniz. - -[obs]: https://obsproject.com/ - -### Bağlantı - -#### Kablosuz - -_Scrcpy_ cihazla iletişim kurmak için `adb`'yi kullanır, Ve `adb` -bir cihaza TCP/IP kullanarak [bağlanabilir]. - -1. Cihazınızı bilgisayarınızla aynı Wi-Fi ağına bağlayın. -2. Cihazınızın IP adresini bulun. Ayarlar → Telefon Hakkında → Durum sekmesinden veya - aşağıdaki komutu çalıştırarak öğrenebilirsiniz: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. Cihazınızda TCP/IP üzerinden adb kullanımını etkinleştirin: `adb tcpip 5555`. -4. Cihazınızı bilgisayarınızdan sökün. -5. Cihazınıza bağlanın: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` değerini değiştirin)_. -6. `scrcpy` komutunu normal olarak çalıştırın. - -Bit-oranını ve büyüklüğü azaltmak yararlı olabilir: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # kısa version -``` - -[bağlanabilir]: https://developer.android.com/studio/command-line/adb.html#wireless - -#### Birden fazla cihaz - -Eğer `adb devices` komutu birden fazla cihaz listeliyorsa _serial_ değerini belirtmeniz gerekir: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # kısa versiyon -``` - -Eğer cihaz TCP/IP üzerinden bağlanmışsa: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # kısa version -``` - -Birden fazla cihaz için birden fazla _scrcpy_ uygulaması çalıştırabilirsiniz. - -#### Cihaz bağlantısı ile otomatik başlatma - -[AutoAdb] ile yapılabilir: - -```bash -autoadb scrcpy -s '{}' -``` - -[autoadb]: https://github.com/rom1v/autoadb - -#### SSH Tünel - -Uzaktaki bir cihaza erişmek için lokal `adb` istemcisi, uzaktaki bir `adb` sunucusuna -(aynı _adb_ sürümünü kullanmak şartı ile) bağlanabilir : - -```bash -adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# bunu açık tutun -``` - -Başka bir terminalde: - -```bash -scrcpy -``` - -Uzaktan port yönlendirme ileri yönlü bağlantı kullanabilirsiniz -(`-R` yerine `-L` olduğuna dikkat edin): - -```bash -adb kill-server # 5037 portunda çalışan lokal adb sunucusunu kapat -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer -# bunu açık tutun -``` - -Başka bir terminalde: - -```bash -scrcpy --force-adb-forward -``` - -Kablosuz bağlantı gibi burada da kalite düşürmek faydalı olabilir: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### Pencere ayarları - -#### İsim - -Cihaz modeli varsayılan pencere ismidir. Değiştirmek için: - -```bash -scrcpy --window-title 'Benim cihazım' -``` - -#### Konum ve - -Pencerenin başlangıç konumu ve boyutu belirtilebilir: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### Kenarlıklar - -Pencere dekorasyonunu kapatmak için: - -```bash -scrcpy --window-borderless -``` - -#### Her zaman üstte - -Scrcpy penceresini her zaman üstte tutmak için: - -```bash -scrcpy --always-on-top -``` - -#### Tam ekran - -Uygulamayı tam ekran başlatmak için: - -```bash -scrcpy --fullscreen -scrcpy -f # kısa versiyon -``` - -Tam ekran MOD+f ile dinamik olarak değiştirilebilir. - -#### Döndürme - -Pencere döndürülebilir: - -```bash -scrcpy --rotation 1 -``` - -Seçilebilecek değerler: - -- `0`: döndürme yok -- `1`: 90 derece saat yönünün tersi -- `2`: 180 derece -- `3`: 90 derece saat yönü - -Döndürme MOD+_(sol)_ ve -MOD+ _(sağ)_ ile dinamik olarak değiştirilebilir. - -_scrcpy_'de 3 farklı döndürme olduğuna dikkat edin: - -- MOD+r cihazın yatay veya dikey modda çalışmasını sağlar. - (çalışan uygulama istenilen oryantasyonda çalışmayı desteklemiyorsa döndürme - işlemini reddedebilir.) -- [`--lock-video-orientation`](#lock-video-orientation) görüntü yakalama oryantasyonunu - (cihazdan bilgisayara gelen video akışının oryantasyonu) değiştirir. Bu kayıt işlemini - etkiler. -- `--rotation` (or MOD+/MOD+) - pencere içeriğini dönderir. Bu sadece canlı görüntüyü etkiler, kayıt işlemini etkilemez. - -### Diğer ekran yakalama seçenekleri - -#### Yazma korumalı - -Kontrolleri devre dışı bırakmak için (cihazla etkileşime geçebilecek her şey: klavye ve -fare girdileri, dosya sürükleyip bırakma): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Ekran - -Eğer cihazın birden fazla ekranı varsa hangi ekranın kullanılacağını seçebilirsiniz: - -```bash -scrcpy --display 1 -``` - -Kullanılabilecek ekranları listelemek için: - -```bash -adb shell dumpsys display # çıktı içerisinde "mDisplayId=" terimini arayın -``` - -İkinci ekran ancak cihaz Android sürümü 10 veya üzeri olmalıdır (değilse yazma korumalı -olarak görüntülenir). - -#### Uyanık kalma - -Cihazın uyku moduna girmesini engellemek için: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -scrcpy kapandığında cihaz başlangıç durumuna geri döner. - -#### Ekranı kapatma - -Ekran yakalama sırasında cihazın ekranı kapatılabilir: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Ya da MOD+o kısayolunu kullanabilirsiniz. - -Tekrar açmak için ise MOD+Shift+o tuşlarına basın. - -Android'de, `GÜÇ` tuşu her zaman ekranı açar. Eğer `GÜÇ` sinyali scrcpy ile -gönderilsiyse (sağ tık veya MOD+p), ekran kısa bir gecikme -ile kapanacaktır. Fiziksel `GÜÇ` tuşuna basmak hala ekranın açılmasına sebep olacaktır. - -Bu cihazın uykuya geçmesini engellemek için kullanılabilir: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### Dokunuşları gösterme - -Sunumlar sırasında fiziksel dokunuşları (fiziksel cihazdaki) göstermek -faydalı olabilir. - -Android'de bu özellik _Geliştici seçenekleri_ içerisinde bulunur. - -_Scrcpy_ bu özelliği çalışırken etkinleştirebilir ve kapanırken eski -haline geri getirebilir: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Bu opsiyon sadece _fiziksel_ dokunuşları (cihaz ekranındaki) gösterir. - -#### Ekran koruyucuyu devre dışı bırakma - -Scrcpy varsayılan ayarlarında ekran koruyucuyu devre dışı bırakmaz. - -Bırakmak için: - -```bash -scrcpy --disable-screensaver -``` - -### Girdi kontrolü - -#### Cihaz ekranını dönderme - -MOD+r tuşları ile yatay ve dikey modlar arasında -geçiş yapabilirsiniz. - -Bu kısayol ancak çalışan uygulama desteklediği takdirde ekranı döndürecektir. - -#### Kopyala yapıştır - -Ne zaman Android cihazdaki pano değişse bilgisayardaki pano otomatik olarak -senkronize edilir. - -Tüm Ctrl kısayolları cihaza iletilir: - -- Ctrl+c genelde kopyalar -- Ctrl+x genelde keser -- Ctrl+v genelde yapıştırır (bilgisayar ve cihaz arasındaki - pano senkronizasyonundan sonra) - -Bu kısayollar genelde beklediğiniz gibi çalışır. - -Ancak kısayolun gerçekten yaptığı eylemi açık olan uygulama belirler. -Örneğin, _Termux_ Ctrl+c ile kopyalama yerine -SIGINT sinyali gönderir, _K-9 Mail_ ise yeni mesaj oluşturur. - -Bu tip durumlarda kopyalama, kesme ve yapıştırma için (Android versiyon 7 ve -üstü): - -- MOD+c `KOPYALA` -- MOD+x `KES` -- MOD+v `YAPIŞTIR` (bilgisayar ve cihaz arasındaki - pano senkronizasyonundan sonra) - -Bunlara ek olarak, MOD+Shift+v tuşları -bilgisayar pano içeriğini tuş basma eylemleri şeklinde gönderir. Bu metin -yapıştırmayı desteklemeyen (_Termux_ gibi) uygulamar için kullanışlıdır, -ancak ASCII olmayan içerikleri bozabilir. - -**UYARI:** Bilgisayar pano içeriğini cihaza yapıştırmak -(Ctrl+v ya da MOD+v tuşları ile) -içeriği cihaz panosuna kopyalar. Sonuç olarak, herhangi bir Android uygulaması -içeriğe erişebilir. Hassas içerikler (parolalar gibi) için bu özelliği kullanmaktan -kaçının. - -Bazı cihazlar pano değişikleri konusunda beklenilen şekilde çalışmayabilir. -Bu durumlarda `--legacy-paste` argümanı kullanılabilir. Bu sayede -Ctrl+v ve MOD+v tuşları da -pano içeriğini tuş basma eylemleri şeklinde gönderir -(MOD+Shift+v ile aynı şekilde). - -#### İki parmak ile yakınlaştırma - -"İki parmak ile yakınlaştırma" için: Ctrl+_tıkla-ve-sürükle_. - -Daha açıklayıcı şekilde, Ctrl tuşuna sol-tık ile birlikte basılı -tutun. Sol-tık serbest bırakılıncaya kadar yapılan tüm fare hareketleri -ekran içeriğini ekranın merkezini baz alarak dönderir, büyütür veya küçültür -(eğer uygulama destekliyorsa). - -Scrcpy ekranın merkezinde bir "sanal parmak" varmış gibi davranır. - -#### Metin gönderme tercihi - -Metin girilirken ili çeşit [eylem][textevents] gerçekleştirilir: - -- _tuş eylemleri_, bir tuşa basıldığı sinyalini verir; -- _metin eylemleri_, bir metin girildiği sinyalini verir. - -Varsayılan olarak, harfler tuş eylemleri kullanılarak gönderilir. Bu sayede -klavye oyunlarda beklenilene uygun olarak çalışır (Genelde WASD tuşları). - -Ancak bu [bazı problemlere][prefertext] yol açabilir. Eğer bu problemler ile -karşılaşırsanız metin eylemlerini tercih edebilirsiniz: - -```bash -scrcpy --prefer-text -``` - -(Ama bu oyunlardaki klavye davranışlarını bozacaktır) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - -#### Tuş tekrarı - -Varsayılan olarak, bir tuşa basılı tutmak tuş eylemini tekrarlar. Bu durum -bazı oyunlarda problemlere yol açabilir. - -Tuş eylemlerinin tekrarını kapatmak için: - -```bash -scrcpy --no-key-repeat -``` - -#### Sağ-tık ve Orta-tık - -Varsayılan olarak, sağ-tık GERİ (ya da GÜÇ açma) eylemlerini, orta-tık ise -ANA EKRAN eylemini tetikler. Bu kısayolları devre dışı bırakmak için: - -```bash -scrcpy --forward-all-clicks -``` - -### Dosya bırakma - -#### APK kurulumu - -APK kurmak için, bilgisayarınızdaki APK dosyasını (`.apk` ile biten) _scrcpy_ -penceresine sürükleyip bırakın. - -Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır. - -#### Dosyayı cihaza gönderme - -Bir dosyayı cihazdaki `/sdcard/Download/` dizinine atmak için, (APK olmayan) -bir dosyayı _scrcpy_ penceresine sürükleyip bırakın. - -Bu eylem görsel bir geri dönüt oluşturmaz, konsola log yazılır. - -Hedef dizin uygulama başlatılırken değiştirilebilir: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - -### Ses iletimi - -_Scrcpy_ ses iletimi yapmaz. Yerine [sndcpy] kullanabilirsiniz. - -Ayrıca bakınız [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - -## Kısayollar - -Aşağıdaki listede, MOD kısayol tamamlayıcısıdır. Varsayılan olarak -(sol) Alt veya (sol) Super tuşudur. - -Bu tuş `--shortcut-mod` argümanı kullanılarak `lctrl`, `rctrl`, -`lalt`, `ralt`, `lsuper` ve `rsuper` tuşlarından biri ile değiştirilebilir. -Örneğin: - -```bash -# Sağ Ctrl kullanmak için -scrcpy --shortcut-mod=rctrl - -# Sol Ctrl, Sol Alt veya Sol Super tuşlarından birini kullanmak için -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] tuşu genelde Windows veya Cmd tuşudur._ - -[super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - -| Action | Shortcut | -| ------------------------------------------------ | :-------------------------------------------------------- | -| Tam ekran modunu değiştirme | MOD+f | -| Ekranı sola çevirme | MOD+ _(sol)_ | -| Ekranı sağa çevirme | MOD+ _(sağ)_ | -| Pencereyi 1:1 oranına çevirme (pixel-perfect) | MOD+g | -| Penceredeki siyah kenarlıkları kaldırma | MOD+w \| _Çift-sol-tık¹_ | -| `ANA EKRAN` tuşu | MOD+h \| _Orta-tık_ | -| `GERİ` tuşu | MOD+b \| _Sağ-tık²_ | -| `UYGULAMA_DEĞİŞTİR` tuşu | MOD+s \| _4.tık³_ | -| `MENÜ` tuşu (ekran kilidini açma) | MOD+m | -| `SES_AÇ` tuşu | MOD+ _(yukarı)_ | -| `SES_KIS` tuşu | MOD+ _(aşağı)_ | -| `GÜÇ` tuşu | MOD+p | -| Gücü açma | _Sağ-tık²_ | -| Cihaz ekranını kapatma (ekran yakalama durmadan) | MOD+o | -| Cihaz ekranını açma | MOD+Shift+o | -| Cihaz ekranını dönderme | MOD+r | -| Bildirim panelini genişletme | MOD+n \| _5.tık³_ | -| Ayarlar panelini genişletme | MOD+n+n \| _Çift-5.tık³_ | -| Panelleri kapatma | MOD+Shift+n | -| Panoya kopyalama⁴ | MOD+c | -| Panoya kesme⁴ | MOD+x | -| Panoları senkronize ederek yapıştırma⁴ | MOD+v | -| Bilgisayar panosundaki metini girme | MOD+Shift+v | -| FPS sayacını açma/kapatma (terminalde) | MOD+i | -| İki parmakla yakınlaştırma | Ctrl+_tıkla-ve-sürükle_ | - -_¹Siyah kenarlıkları silmek için üzerine çift tıklayın._ -_²Sağ-tık ekran kapalıysa açar, değilse GERİ sinyali gönderir._ -_³4. ve 5. fare tuşları (eğer varsa)._ -_⁴Sadece Android 7 ve üzeri versiyonlarda._ - -Tekrarlı tuşu olan kısayollar tuş bırakılıp tekrar basılarak tekrar çalıştırılır. -Örneğin, "Ayarlar panelini genişletmek" için: - -1. MOD tuşuna basın ve basılı tutun. -2. n tuşuna iki defa basın. -3. MOD tuşuna basmayı bırakın. - -Tüm Ctrl+_tuş_ kısayolları cihaza gönderilir. Bu sayede istenilen komut -uygulama tarafından çalıştırılır. - -## Özel dizinler - -Varsayılandan farklı bir _adb_ programı çalıştırmak için `ADB` ortam değişkenini -ayarlayın: - -```bash -ADB=/path/to/adb scrcpy -``` - -`scrcpy-server` programının dizinini değiştirmek için `SCRCPY_SERVER_PATH` -değişkenini ayarlayın. - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - -## Neden _scrcpy_? - -Bir meslektaşım [gnirehtet] gibi söylenmesi zor bir isim bulmam için bana meydan okudu. - -[`strcpy`] **str**ing kopyalıyor; `scrcpy` **scr**een kopyalıyor. - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - -## Nasıl derlenir? - -Bakınız [BUILD]. - -## Yaygın problemler - -Bakınız [FAQ](FAQ.md). - -## Geliştiriciler - -[Geliştiriciler sayfası]nı okuyun. - -[geliştiriciler sayfası]: DEVELOP.md - -## Lisans - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## Makaleler - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.zh-Hans.md b/README.zh-Hans.md deleted file mode 100644 index ac0713a6..00000000 --- a/README.zh-Hans.md +++ /dev/null @@ -1,993 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -_只有原版的 [README](README.md)是保证最新的。_ - -Current version is based on [f4c7044] - -本文根据[f4c7044]进行翻译。 - -[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md - -# scrcpy (v1.22) - -scrcpy - -_发音为 "**scr**een **c**o**py**"_ - -本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。 - -![screenshot](assets/screenshot-debian-600.jpg) - -本应用专注于: - - - **轻量**: 原生,仅显示设备屏幕 - - **性能**: 30~120fps,取决于设备 - - **质量**: 分辨率可达 1920×1080 或更高 - - **低延迟**: [35~70ms][lowlatency] - - **快速启动**: 最快 1 秒内即可显示第一帧 - - **无侵入性**: 不会在设备上遗留任何程序 - - **用户利益**: 无需帐号,无广告,无需联网 - - **自由**: 自由和开源软件 - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - -功能: - - [屏幕录制](#屏幕录制) - - 镜像时[关闭设备屏幕](#关闭设备屏幕) - - 双向[复制粘贴](#复制粘贴) - - [可配置显示质量](#采集设置) - - 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux) - - [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux) - - [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux) - - [OTG模式](#otg) (仅限 Linux) - - 更多 …… - -## 系统要求 - -安卓设备最低需要支持 API 21 (Android 5.0)。 - -确保设备已[开启 adb 调试][enable-adb]。 - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - -在某些设备上,还需要开启[额外的选项][control]以使用鼠标和键盘进行控制。 - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## 获取本程序 - -Packaging status - -### 概要 - - - Linux: `apt install scrcpy` - - Windows: [下载][direct-win64] - - macOS: `brew install scrcpy` - -从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple]) - -[BUILD]: BUILD.md -[BUILD_simple]: BUILD.md#simple - -### Linux - -在 Debian 和 Ubuntu 上: - -``` -apt install scrcpy -``` - -在 Arch Linux 上: - -``` -pacman -S scrcpy -``` - -我们也提供 [Snap] 包: [`scrcpy`][snap-link]。 - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -对 Fedora 我们提供 [COPR] 包: [`scrcpy`][copr-link]。 - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。 - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。 - - -### Windows - -在 Windows 上,为简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 - - - [README](README.md#windows) - -也可以使用 [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # 如果还没有 adb -``` - -或者 [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # 如果还没有 adb -``` - -[Scoop]: https://scoop.sh - -您也可以[自行构建][BUILD]。 - - -### macOS - -本程序已发布到 [Homebrew]。直接安装即可: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -你还需要在 `PATH` 内有 `adb`。如果还没有: - -```bash -brew install android-platform-tools -``` - -或者通过 [MacPorts],该方法同时设置好 adb: - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - -您也可以[自行构建][BUILD]。 - - -## 运行 - -连接安卓设备,然后执行: - -```bash -scrcpy -``` - -本程序支持命令行参数,查看参数列表: - -```bash -scrcpy --help -``` - -## 功能介绍 - -### 采集设置 - -#### 降低分辨率 - -有时候,可以通过降低镜像的分辨率来提高性能。 - -要同时限制宽度和高度到某个值 (例如 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # 简写 -``` - -另一边会被按比例缩小以保持设备的显示比例。这样,1920×1080 分辨率的设备会以 1024×576 的分辨率进行镜像。 - - -#### 修改码率 - -默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # 简写 -``` - -#### 限制帧率 - -要限制采集的帧率: - -```bash -scrcpy --max-fps 15 -``` - -本功能从 Android 10 开始才被官方支持,但在一些旧版本中也能生效。 - -#### 画面裁剪 - -可以对设备屏幕进行裁剪,只镜像屏幕的一部分。 - -例如可以只镜像 Oculus Go 的一只眼睛。 - -```bash -scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素 -``` - -如果同时指定了 `--max-size`,会先进行裁剪,再进行缩放。 - - -#### 锁定屏幕方向 - - -要锁定镜像画面的方向: - -```bash -scrcpy --lock-video-orientation # 初始(目前)方向 -scrcpy --lock-video-orientation=0 # 自然方向 -scrcpy --lock-video-orientation=1 # 逆时针旋转 90° -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 顺时针旋转 90° -``` - -只影响录制的方向。 - -[窗口可以独立旋转](#旋转)。 - - -#### 编码器 - -一些设备内置了多种编码器,但是有的编码器会导致问题或崩溃。可以手动选择其它编码器: - -```bash -scrcpy --encoder OMX.qcom.video.encoder.avc -``` - -要列出可用的编码器,可以指定一个不存在的编码器名称,错误信息中会包含所有的编码器: - -```bash -scrcpy --encoder _ -``` - -### 采集 - -#### 屏幕录制 - -可以在镜像的同时录制视频: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -仅录制,不显示镜像: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# 按 Ctrl+C 停止录制 -``` - -录制时会包含“被跳过的帧”,即使它们由于性能原因没有实时显示。设备会为每一帧打上 _时间戳_ ,所以 [包时延抖动][packet delay variation] 不会影响录制的文件。 - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -#### v4l2loopback - -在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。 - -需安装 `v4l2loopback` 模块: - -```bash -sudo apt install v4l2loopback-dkms -``` - -创建一个 v4l2 设备: - -```bash -sudo modprobe v4l2loopback -``` - -这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。 - -列出已启用的设备: - -```bash -# 需要 v4l-utils 包 -v4l2-ctl --list-devices - -# 简单但或许足够 -ls /dev/video* -``` - -使用一个 v4l2 漏开启 scrcpy: - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像 -scrcpy --v4l2-sink=/dev/videoN -N # 简写 -``` - -(将 `N` 替换为设备 ID,使用 `ls /dev/video*` 命令查看) - -启用之后,可以使用 v4l2 工具打开视频流: - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟 -``` - -例如,可以在 [OBS] 中采集视频。 - -[OBS]: https://obsproject.com/ - - -#### 缓冲 - -可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。 - -[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 - -对于显示缓冲: - -```bash -scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲 -``` - -对于 V4L2 漏: - -```bash -scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲 -``` - - -### 连接 - -#### TCP/IP (无线) - -_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。 - -##### 自动配置 - -参数 `--tcpip` 允许自动配置连接。这里有两种方式。 - -对于传入的 adb 连接,如果设备(在这个例子中以192.168.1.1为可用地址)已经监听了一个端口(通常是5555),运行: - -```bash -scrcpy --tcpip=192.168.1.1 # 默认端口是5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -如果adb TCP/IP(无线) 模式在某些设备上不被启用(或者你不知道IP地址),用USB连接设备,然后运行: - -```bash -scrcpy --tcpip # 无需其他参数 -``` - -这将会自动寻找设备IP地址,启用TCP/IP模式,然后在启动之前连接到设备。 - -##### 手动配置 - -或者,可以通过 `adb` 使用手动启用 TCP/IP 连接: - -1. 将设备和电脑连接至同一 Wi-Fi。 -2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -3. 启用设备的网络 adb 功能:`adb tcpip 5555`。 -4. 断开设备的 USB 连接。 -5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。 -6. 正常运行 `scrcpy`。 - -降低比特率和分辨率可能很有用: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # 简写 -``` - -[连接]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### 多设备 - -如果 `adb devices` 列出了多个设备,您必须指定设备的 _序列号_ : - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # 简写 -``` - -如果设备通过 TCP/IP 连接: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # 简写 -``` - -您可以同时启动多个 _scrcpy_ 实例以同时显示多个设备的画面。 - -#### 在设备连接时自动启动 - -您可以使用 [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### 隧道 - -要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)。 - -##### 远程ADB服务器 - -要连接到一个远程ADB服务器,让服务器在所有接口上监听: - -```bash -adb kill-server -adb -a nodaemon server start -# 保持该窗口开启 -``` - -**警告:所有客户端与ADB服务器的交流都是未加密的。** - -假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -默认情况下,scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用): - -``` -scrcpy --tunnel-port=1234 -``` - - -##### SSH 隧道 - -为了安全地与远程ADB服务器通信,最好使用SSH隧道。 - -首先,确保ADB服务器正在远程计算机上运行: - -```bash -adb start-server -``` - -然后,建立一个SSH隧道: - -```bash -# 本地 5038 --> 远程 5037 -# 本地 27183 <-- 远程 27183 -ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer -# 保持该窗口开启 -``` - -在另一个终端上,运行scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -若要不使用远程端口转发,可以强制使用正向连接(注意是 `-L` 而不是 `-R` ): - -```bash -# 本地 5038 --> 远程 5037 -# 本地 27183 <-- 远程 27183 -ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer -# 保持该窗口开启 -``` - -在另一个终端上,运行scrcpy: - -```bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - - -类似地,对于无线连接,可能需要降低画面质量: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### 窗口设置 - -#### 标题 - -窗口的标题默认为设备型号。可以通过如下命令修改: - -```bash -scrcpy --window-title "我的设备" -``` - -#### 位置和大小 - -您可以指定初始的窗口位置和大小: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### 无边框 - -禁用窗口边框: - -```bash -scrcpy --window-borderless -``` - -#### 保持窗口在最前 - -您可以通过如下命令保持窗口在最前面: - -```bash -scrcpy --always-on-top -``` - -#### 全屏 - -您可以通过如下命令直接全屏启动 scrcpy: - -```bash -scrcpy --fullscreen -scrcpy -f # 简写 -``` - -全屏状态可以通过 MOD+f 随时切换。 - -#### 旋转 - -可以通过以下命令旋转窗口: - -```bash -scrcpy --rotation 1 -``` - -可选的值有: - - `0`: 无旋转 - - `1`: 逆时针旋转 90° - - `2`: 旋转 180° - - `3`: 顺时针旋转 90° - -也可以使用 MOD+ _(左箭头)_ 和 MOD+ _(右箭头)_ 随时更改。 - -需要注意的是, _scrcpy_ 中有三类旋转方向: - - MOD+r 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。 - - [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。 - - `--rotation` (或 MOD+/MOD+) 只旋转窗口的内容。这只影响显示,不影响录制。 - - -### 其他镜像设置 - -#### 只读 - -禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### 显示屏 - -如果设备有多个显示屏,可以选择要镜像的显示屏: - -```bash -scrcpy --display 1 -``` - -可以通过如下命令列出所有显示屏的 id: - -``` -adb shell dumpsys display # 在输出中搜索 “mDisplayId=” -``` - -控制第二显示屏需要设备运行 Android 10 或更高版本 (否则将在只读状态下镜像)。 - - -#### 保持常亮 - -阻止设备在连接时一段时间后休眠: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -scrcpy 关闭时会恢复设备原来的设置。 - - -#### 关闭设备屏幕 - -可以通过以下的命令行参数在关闭设备屏幕的状态下进行镜像: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -或者在任何时候按 MOD+o。 - -要重新打开屏幕,按下 MOD+Shift+o。 - -在Android上,`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 MOD+p),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。 - -还可以同时阻止设备休眠: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### 退出时息屏 - -scrcpy 退出时关闭设备屏幕: - -```bash -scrcpy --power-off-on-close -``` - -#### 显示触摸 - -在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。 - -Android 在 _开发者选项_ 中提供了这项功能。 - -_Scrcpy_ 提供一个选项可以在启动时开启这项功能并在退出时恢复初始设置: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -请注意这项功能只能显示 _物理_ 触摸 (用手指在屏幕上的触摸)。 - - -#### 关闭屏保 - -_Scrcpy_ 默认不会阻止电脑上开启的屏幕保护。 - -关闭屏幕保护: - -```bash -scrcpy --disable-screensaver -``` - - -### 输入控制 - -#### 旋转设备屏幕 - -使用 MOD+r 在竖屏和横屏模式之间切换。 - -需要注意的是,只有在前台应用程序支持所要求的模式时,才会进行切换。 - -#### 复制粘贴 - -每次安卓的剪贴板变化时,其内容都会被自动同步到电脑的剪贴板上。 - -所有的 Ctrl 快捷键都会被转发至设备。其中: - - Ctrl+c 通常执行复制 - - Ctrl+x 通常执行剪切 - - Ctrl+v 通常执行粘贴 (在电脑到设备的剪贴板同步完成之后) - -大多数时候这些按键都会执行以上的功能。 - -但实际的行为取决于设备上的前台程序。例如,_Termux_ 会在按下 Ctrl+c 时发送 SIGINT,又如 _K-9 Mail_ 会新建一封邮件。 - -要在这种情况下进行剪切,复制和粘贴 (仅支持 Android >= 7): - - MOD+c 注入 `COPY` (复制) - - MOD+x 注入 `CUT` (剪切) - - MOD+v 注入 `PASTE` (粘贴) (在电脑到设备的剪贴板同步完成之后) - -另外,MOD+Shift+v 会将电脑的剪贴板内容转换为一串按键事件输入到设备。在应用程序不接受粘贴时 (比如 _Termux_),这项功能可以派上一定的用场。不过这项功能可能会导致非 ASCII 编码的内容出现错误。 - -**警告:** 将电脑剪贴板的内容粘贴至设备 (无论是通过 Ctrl+v 还是 MOD+v) 都会将内容复制到设备的剪贴板。如此,任何安卓应用程序都能读取到。您应避免将敏感内容 (如密码) 通过这种方式粘贴。 - -一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 Ctrl+vMOD+v 的工作方式,使它们通过按键事件 (同 MOD+Shift+v) 来注入电脑剪贴板内容。 - -要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。 - -#### 双指缩放 - -模拟“双指缩放”:Ctrl+_按下并拖动鼠标_。 - -在按住 Ctrl 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。 - -具体来说,_scrcpy_ 会在鼠标位置,以及鼠标以屏幕中心镜像的位置分别生成触摸事件。 - -#### 物理键盘模拟 (HID) - -默认情况下,scrcpy 使用安卓按键或文本注入,这在任何情况都可以使用,但仅限于ASCII字符。 - -在 Linux 上,scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。 - -[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support - -不过,这种方法仅支持 USB 连接以及 Linux平台。 - -启用 HID 模式: - -```bash -scrcpy --hid-keyboard -scrcpy -K # 简写 -``` - -如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。 - -在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。 - -[实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - -#### 物理鼠标模拟 (HID) - -与物理键盘模拟类似,可以模拟一个物理鼠标。 同样,它仅在设备通过 USB 连接时才有效,并且目前仅在 Linux 上受支持。 - -默认情况下,scrcpy 使用 Android 鼠标事件注入,使用绝对坐标。 通过模拟物理鼠标,在Android设备上出现鼠标指针,并注入鼠标相对运动、点击和滚动。 - -启用此模式: - -```bash -scrcpy --hid-mouse -scrcpy -M # 简写 -``` - -您还可以将 `--forward-all-clicks` 添加到 [转发所有点击][forward_all_clicks]. - -[forward_all_clicks]: #右键和中键 - -启用此模式后,计算机鼠标将被“捕获”(鼠标指针从计算机上消失并出现在 Android 设备上)。 - -特殊的捕获键,AltSuper,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。 - - -#### OTG - -可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_,就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。 - -在这个模式下,_adb_ (USB 调试)是不必要的,且镜像被禁用。 - -启用 OTG 模式: - -```bash -scrcpy --otg -# 如果有多个 USB 设备可用,则通过序列号选择 -scrcpy --otg -s 0123456789abcdef -``` - -只开启 HID 键盘 或 HID 鼠标 是可行的: - -```bash -scrcpy --otg --hid-keyboard # 只开启 HID 键盘 -scrcpy --otg --hid-mouse # 只开启 HID 鼠标 -scrcpy --otg --hid-keyboard --hid-mouse # 开启 HID 键盘 和 HID 鼠标 -# 为了方便,默认两者都开启 -scrcpy --otg # 开启 HID 键盘 和 HID 鼠标 -``` - -像 `--hid-keyboard` 和 `--hid-mouse` 一样,它只在设备通过 USB 连接时才有效,且目前仅在 Linux 上支持。 - - -#### 文本注入偏好 - -输入文字的时候,系统会产生两种[事件][textevents]: - - _按键事件_ ,代表一个按键被按下或松开。 - - _文本事件_ ,代表一个字符被输入。 - -程序默认使用按键事件来输入字母。只有这样,键盘才会在游戏中正常运作 (例如 WASD 键)。 - -但这也有可能[造成一些问题][prefertext]。如果您遇到了问题,可以通过以下方式避免: - -```bash -scrcpy --prefer-text -``` - -(但这会导致键盘在游戏中工作不正常) - -相反,您可以强制始终注入原始按键事件: - -```bash -scrcpy --raw-key-events -``` - -该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。 - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### 按键重复 - -默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。 - -避免转发重复按键事件: - -```bash -scrcpy --no-key-repeat -``` - -该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。 - -#### 右键和中键 - -默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: - -```bash -scrcpy --forward-all-clicks -``` - - -### 文件拖放 - -#### 安装APK - -将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。 - -不会有视觉反馈,终端会输出一条日志。 - - -#### 将文件推送至设备 - -要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 - -不会有视觉反馈,终端会输出一条日志。 - -在启动时可以修改目标目录: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - - -### 音频转发 - -_Scrcpy_ 不支持音频。请使用 [sndcpy]。 - -另见 [issue #14]。 - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## 快捷键 - -在以下列表中, MOD 是快捷键的修饰键。 -默认是 (左) Alt 或 (左) Super。 - -您可以使用 `--shortcut-mod` 来修改。可选的按键有 `lctrl`、`rctrl`、`lalt`、`ralt`、`lsuper` 和 `rsuper`。例如: - -```bash -# 使用右 Ctrl 键 -scrcpy --shortcut-mod=rctrl - -# 使用左 Ctrl 键 + 左 Alt 键,或 Super 键 -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] 键通常是指 WindowsCmd 键。_ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | 操作 | 快捷键 - | --------------------------------- | :------------------------------------------- - | 全屏 | MOD+f - | 向左旋转屏幕 | MOD+ _(左箭头)_ - | 向右旋转屏幕 | MOD+ _(右箭头)_ - | 将窗口大小重置为1:1 (匹配像素) | MOD+g - | 将窗口大小重置为消除黑边 | MOD+w \| _双击左键¹_ - | 点按 `主屏幕` | MOD+h \| _中键_ - | 点按 `返回` | MOD+b \| _右键²_ - | 点按 `切换应用` | MOD+s \| _第4键³_ - | 点按 `菜单` (解锁屏幕)⁴ | MOD+m - | 点按 `音量+` | MOD+ _(上箭头)_ - | 点按 `音量-` | MOD+ _(下箭头)_ - | 点按 `电源` | MOD+p - | 打开屏幕 | _鼠标右键²_ - | 关闭设备屏幕 (但继续在电脑上显示) | MOD+o - | 打开设备屏幕 | MOD+Shift+o - | 旋转设备屏幕 | MOD+r - | 展开通知面板 | MOD+n \| _第5键³_ - | 展开设置面板 | MOD+n+n \| _双击第5键³_ - | 收起通知面板 | MOD+Shift+n - | 复制到剪贴板⁵ | MOD+c - | 剪切到剪贴板⁵ | MOD+x - | 同步剪贴板并粘贴⁵ | MOD+v - | 注入电脑剪贴板文本 | MOD+Shift+v - | 打开/关闭FPS显示 (至标准输出) | MOD+i - | 捏拉缩放 | Ctrl+_按住并移动鼠标_ - | 拖放 APK 文件 | 从电脑安装 APK 文件 - | 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device) - -_¹双击黑边可以去除黑边。_ -_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_ -_³鼠标的第4键和第5键。_ -_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_ -_⁵需要安卓版本 Android >= 7。_ - -有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”: - - 1. 按下 MOD 不放。 - 2. 双击 n。 - 3. 松开 MOD。 - -所有的 Ctrl+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。 - - -## 自定义路径 - -要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`: - -```bash -ADB=/path/to/adb scrcpy -``` - -要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。 - -要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。 - - -## 为什么叫 _scrcpy_ ? - -一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。 - -[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。 - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## 如何构建? - -请查看 [BUILD]。 - - -## 常见问题 - -请查看 [FAQ](FAQ.md)。 - - -## 开发者 - -请查看[开发者页面]。 - -[开发者页面]: DEVELOP.md - - -## 许可协议 - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## 相关文章 - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ diff --git a/README.zh-Hant.md b/README.zh-Hant.md deleted file mode 100644 index 87c0a8dd..00000000 --- a/README.zh-Hant.md +++ /dev/null @@ -1,702 +0,0 @@ -_Only the original [README](README.md) is guaranteed to be up-to-date._ - -_只有原版的 [README](README.md)是保證最新的。_ - - -本文件翻譯時點: [521f2fe](https://github.com/Genymobile/scrcpy/commit/521f2fe994019065e938aa1a54b56b4f10a4ac4a#diff-04c6e90faac2675aa89e2176d2eec7d8) - - -# scrcpy (v1.15) - -Scrcpy 可以透過 USB、或是 [TCP/IP][article-tcpip] 來顯示或控制 Android 裝置。且 scrcpy 不需要 _root_ 權限。 - -Scrcpy 目前支援 _GNU/Linux_、_Windows_ 和 _macOS_。 - -![screenshot](assets/screenshot-debian-600.jpg) - -特色: - - - **輕量** (只顯示裝置螢幕) - - **效能** (30~60fps) - - **品質** (1920×1080 或更高) - - **低延遲** ([35~70ms][lowlatency]) - - **快速啟動** (~1 秒就可以顯示第一個畫面) - - **非侵入性** (不安裝、留下任何東西在裝置上) - -[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 - - -## 需求 - -Android 裝置必須是 API 21+ (Android 5.0+)。 - -請確認裝置上的 [adb 偵錯 (通常位於開發者模式內)][enable-adb] 已啟用。 - -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling - - -在部分的裝置上,你也必須啟用[特定的額外選項][control]才能使用鍵盤和滑鼠控制。 - -[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 - - -## 下載/獲取軟體 - - -### Linux - -Debian (目前支援 _testing_ 和 _sid_) 和 Ubuntu (20.04): - -``` -apt install scrcpy -``` - -[Snap] 上也可以下載: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -在 Fedora 上也可以使用 [COPR] 下載: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - -在 Arch Linux 上也可以使用 [AUR] 下載: [`scrcpy`][aur-link]. - -[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository -[aur-link]: https://aur.archlinux.org/packages/scrcpy/ - -在 Gentoo 上也可以使用 [Ebuild] 下載: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -你也可以自己[編譯 _Scrcpy_][BUILD]。別擔心,並沒有想像中的難。 - - - -### Windows - -為了保持簡單,Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包: - - - [README](README.md#windows) - -[Chocolatey] 上也可以下載: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # 如果你還沒有安裝的話 -``` - -[Scoop] 上也可以下載: - -```bash -scoop install scrcpy -scoop install adb # 如果你還沒有安裝的話 -``` - -[Scoop]: https://scoop.sh - -你也可以自己[編譯 _Scrcpy_][BUILD]。 - - -### macOS - -_Scrcpy_ 可以在 [Homebrew] 上直接安裝: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -由於執行期間需要可以藉由 `PATH` 存取 `adb` 。如果還沒有安裝 `adb` 可以使用下列方式安裝: - -```bash -brew cask install android-platform-tools -``` - -你也可以自己[編譯 _Scrcpy_][BUILD]。 - - -## 執行 - -將電腦和你的 Android 裝置連線,然後執行: - -```bash -scrcpy -``` - -_Scrcpy_ 可以接受命令列參數。輸入下列指令就可以瀏覽可以使用的命令列參數: - -```bash -scrcpy --help -``` - - -## 功能 - -> 以下說明中,有關快捷鍵的說明可能會出現 MOD 按鈕。相關說明請參見[快捷鍵]內的說明。 - -[快捷鍵]: #快捷鍵 - -### 畫面擷取 - -#### 縮小尺寸 - -使用比較低的解析度來投放 Android 裝置在某些情況可以提升效能。 - -限制寬和高的最大值(例如: 1024): - -```bash -scrcpy --max-size 1024 -scrcpy -m 1024 # 縮短版本 -``` - -比較小的參數會根據螢幕比例重新計算。 -根據上面的範例,1920x1080 會被縮小成 1024x576。 - - -#### 更改 bit-rate - -預設的 bit-rate 是 8 Mbps。如果要更改 bit-rate (例如: 2 Mbps): - -```bash -scrcpy --bit-rate 2M -scrcpy -b 2M # 縮短版本 -``` - -#### 限制 FPS - -限制畫面最高的 FPS: - -```bash -scrcpy --max-fps 15 -``` - -僅在 Android 10 後正式支援,不過也有可能可以在 Android 10 以前的版本使用。 - -#### 裁切 - -裝置的螢幕可以裁切。如此一來,鏡像出來的螢幕就只會是原本的一部份。 - -假如只要鏡像 Oculus Go 的其中一隻眼睛: - -```bash -scrcpy --crop 1224:1440:0:0 # 位於 (0,0),大小1224x1440 -``` - -如果 `--max-size` 也有指定的話,裁切後才會縮放。 - - -#### 鎖定影像方向 - - -如果要鎖定鏡像影像方向: - -```bash -scrcpy --lock-video-orientation 0 # 原本的方向 -scrcpy --lock-video-orientation 1 # 逆轉 90° -scrcpy --lock-video-orientation 2 # 180° -scrcpy --lock-video-orientation 3 # 順轉 90° -``` - -這會影響錄影結果的影像方向。 - - -### 錄影 - -鏡像投放螢幕的同時也可以錄影: - -```bash -scrcpy --record file.mp4 -scrcpy -r file.mkv -``` - -如果只要錄影,不要投放螢幕鏡像的話: - -```bash -scrcpy --no-display --record file.mp4 -scrcpy -Nr file.mkv -# 用 Ctrl+C 停止錄影 -``` - -就算有些幀為了效能而被跳過,它們還是一樣會被錄製。 - -裝置上的每一幀都有時間戳記,所以 [封包延遲 (Packet Delay Variation, PDV)][packet delay variation] 並不會影響錄影的檔案。 - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -### 連線 - -#### 無線 - -_Scrcpy_ 利用 `adb` 和裝置通訊,而 `adb` 可以[透過 TCP/IP 連結][connect]: - -1. 讓電腦和裝置連到同一個 Wi-Fi。 -2. 獲取手機的 IP 位址(設定 → 關於手機 → 狀態). -3. 啟用裝置上的 `adb over TCP/IP`: `adb tcpip 5555`. -4. 拔掉裝置上的線。 -5. 透過 TCP/IP 連接裝置: `adb connect DEVICE_IP:5555` _(把 `DEVICE_IP` 換成裝置的IP位址)_. -6. 和平常一樣執行 `scrcpy`。 - -如果效能太差,可以降低 bit-rate 和解析度: - -```bash -scrcpy --bit-rate 2M --max-size 800 -scrcpy -b2M -m800 # 縮短版本 -``` - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### 多裝置 - -如果 `adb devices` 內有多個裝置,則必須附上 _serial_: - -```bash -scrcpy --serial 0123456789abcdef -scrcpy -s 0123456789abcdef # 縮短版本 -``` - -如果裝置是透過 TCP/IP 連線: - -```bash -scrcpy --serial 192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # 縮短版本 -``` - -你可以啟用復數個對應不同裝置的 _scrcpy_。 - -#### 裝置連結後自動啟動 - -你可以使用 [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### SSH tunnel - -本地的 `adb` 可以連接到遠端的 `adb` 伺服器(假設兩者使用相同版本的 _adb_ 通訊協定),以連接到遠端裝置: - -```bash -adb kill-server # 停止在 Port 5037 的本地 adb 伺服 -ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer -# 保持開啟 -``` - -從另外一個終端機: - -```bash -scrcpy -``` - -如果要避免啟用 remote port forwarding,你可以強制它使用 forward connection (注意 `-L` 和 `-R` 的差別): - -```bash -adb kill-server # 停止在 Port 5037 的本地 adb 伺服 -ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer -# 保持開啟 -``` - -從另外一個終端機: - -```bash -scrcpy --force-adb-forward -``` - - -和無線連接一樣,有時候降低品質會比較好: - -``` -scrcpy -b2M -m800 --max-fps 15 -``` - -### 視窗調整 - -#### 標題 - -預設標題是裝置的型號,不過可以透過以下方式修改: - -```bash -scrcpy --window-title 'My device' -``` - -#### 位置 & 大小 - -初始的視窗位置和大小也可以指定: - -```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 -``` - -#### 無邊框 - -如果要停用視窗裝飾: - -```bash -scrcpy --window-borderless -``` - -#### 保持最上層 - -如果要保持 `scrcpy` 的視窗在最上層: - -```bash -scrcpy --always-on-top -``` - -#### 全螢幕 - -這個軟體可以直接在全螢幕模式下起動: - -```bash -scrcpy --fullscreen -scrcpy -f # 縮短版本 -``` - -全螢幕可以使用 MOD+f 開關。 - -#### 旋轉 - -視窗可以旋轉: - -```bash -scrcpy --rotation 1 -``` - -可用的數值: - - `0`: 不旋轉 - - `1`: 90 度**逆**轉 - - `2`: 180 度 - - `3`: 90 度**順**轉 - -旋轉方向也可以使用 MOD+ _(左方向鍵)_ 和 MOD+ _(右方向鍵)_ 調整。 - -_scrcpy_ 有 3 種不同的旋轉: - - MOD+r 要求裝置在垂直、水平之間旋轉 (目前運行中的 App 有可能會因為不支援而拒絕)。 - - `--lock-video-orientation` 修改鏡像的方向 (裝置傳給電腦的影像)。這會影響錄影結果的影像方向。 - - `--rotation` (或是 MOD+ / MOD+) 只旋轉視窗的內容。這只會影響鏡像結果,不會影響錄影結果。 - - -### 其他鏡像選項 - -#### 唯讀 - -停用所有控制,包含鍵盤輸入、滑鼠事件、拖放檔案: - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### 顯示螢幕 - -如果裝置有複數個螢幕,可以指定要鏡像哪個螢幕: - -```bash -scrcpy --display 1 -``` - -可以透過下列指令獲取螢幕 ID: - -``` -adb shell dumpsys display # 找輸出結果中的 "mDisplayId=" -``` - -第二螢幕只有在 Android 10+ 時可以控制。如果不是 Android 10+,螢幕就會在唯讀狀態下投放。 - - -#### 保持清醒 - -如果要避免裝置在連接狀態下進入睡眠: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -_scrcpy_ 關閉後就會回復成原本的設定。 - - -#### 關閉螢幕 - -鏡像開始時,可以要求裝置關閉螢幕: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -或是在任何時候輸入 MOD+o。 - -如果要開啟螢幕,輸入 MOD+Shift+o。 - -在 Android 上,`POWER` 按鈕總是開啟螢幕。 - -為了方便,如果 `POWER` 是透過 scrcpy 轉送 (右鍵 或 MOD+p)的話,螢幕將會在短暫的延遲後關閉。 - -實際在手機上的 `POWER` 還是會開啟螢幕。 - -防止裝置進入睡眠狀態: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - - -#### 顯示過期的幀 - -為了降低延遲, _scrcpy_ 預設只會顯示最後解碼的幀,並且拋棄所有在這之前的幀。 - -如果要強制顯示所有的幀 (有可能會拉高延遲),輸入: - -```bash -scrcpy --render-expired-frames -``` - -#### 顯示觸控點 - -對於要報告的人來說,顯示裝置上的實際觸控點有時候是有幫助的。 - -Android 在_開發者選項_中有提供這個功能。 - -_Scrcpy_ 可以在啟動時啟用這個功能,並且在停止後恢復成原本的設定: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -這個選項只會顯示**實際觸碰在裝置上的觸碰點**。 - - -### 輸入控制 - - -#### 旋轉裝置螢幕 - -輸入 MOD+r 以在垂直、水平之間切換。 - -如果使用中的程式不支援,則不會切換。 - - -#### 複製/貼上 - -如果 Android 剪貼簿上的內容有任何更動,電腦的剪貼簿也會一起更動。 - -任何與 Ctrl 相關的快捷鍵事件都會轉送到裝置上。特別來說: - - Ctrl+c 通常是複製 - - Ctrl+x 通常是剪下 - - Ctrl+v 通常是貼上 (在電腦的剪貼簿與裝置上的剪貼簿同步之後) - -這些跟你通常預期的行為一樣。 - -但是,實際上的行為是根據目前運行中的應用程式而定。 - -舉例來說, _Termux_ 在收到 Ctrl+c 後,會傳送 SIGINT;而 _K-9 Mail_ 則是建立新訊息。 - -如果在這情況下,要剪下、複製或貼上 (只有在Android 7+時才支援): - - MOD+c 注入 `複製` - - MOD+x 注入 `剪下` - - MOD+v 注入 `貼上` (在電腦的剪貼簿與裝置上的剪貼簿同步之後) - -另外,MOD+Shift+v 則是以一連串的按鍵事件貼上電腦剪貼簿中的內容。當元件不允許文字貼上 (例如 _Termux_) 時,這就很有用。不過,這在非 ASCII 內容上就無法使用。 - -**警告:** 貼上電腦的剪貼簿內容 (無論是從 Ctrl+vMOD+v) 時,會複製剪貼簿中的內容至裝置的剪貼簿上。這會讓所有 Android 程式讀取剪貼簿的內容。請避免貼上任何敏感內容 (像是密碼)。 - - -#### 文字輸入偏好 - -輸入文字時,有兩種[事件][textevents]會被觸發: - - _鍵盤事件 (key events)_,代表有一個按鍵被按下或放開 - - _文字事件 (text events)_,代表有一個文字被輸入 - -預設上,文字是被以鍵盤事件 (key events) 輸入的,所以鍵盤和遊戲內所預期的一樣 (通常是指 WASD)。 - -但是這可能造成[一些問題][prefertext]。如果在這輸入這方面遇到了問題,你可以試試: - -```bash -scrcpy --prefer-text -``` - -(不過遊戲內鍵盤就會不可用) - -[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input -[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 - - -#### 重複輸入 - -通常來說,長時間按住一個按鍵會重複觸發按鍵事件。這會在一些遊戲中造成效能問題,而且這個重複的按鍵事件是沒有意義的。 - -如果不要轉送這些重複的按鍵事件: - -```bash -scrcpy --no-key-repeat -``` - - -### 檔案 - -#### 安裝 APK - -如果要安裝 APK ,拖放一個 APK 檔案 (以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。 - -視窗上不會有任何反饋;結果會顯示在命令列中。 - - -#### 推送檔案至裝置 - -如果要推送檔案到裝置上的 `/sdcard/` ,拖放一個非 APK 檔案 (**不**以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。 - -視窗上不會有任何反饋;結果會顯示在命令列中。 - -推送檔案的目標路徑可以在啟動時指定: - -```bash -scrcpy --push-target /sdcard/foo/bar/ -``` - - -### 音訊轉送 - -_scrcpy_ **不**轉送音訊。請使用 [sndcpy]。另外,參見 [issue #14]。 - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## 快捷鍵 - -在以下的清單中,MOD 是快捷鍵的特殊按鍵。通常來說,這個按鍵是 (左) Alt 或是 (左) Super。 - -這個是可以使用 `--shortcut-mod` 更改的。可以用的選項有: -- `lctrl`: 左邊的 Ctrl -- `rctrl`: 右邊的 Ctrl -- `lalt`: 左邊的 Alt -- `ralt`: 右邊的 Alt -- `lsuper`: 左邊的 Super -- `rsuper`: 右邊的 Super - -```bash -# 以 右邊的 Ctrl 為快捷鍵特殊按鍵 -scrcpy --shortcut-mod=rctrl - -# 以 左邊的 Ctrl 和左邊的 Alt 或是 左邊的 Super 為快捷鍵特殊按鍵 -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] 通常是 WindowsCmd 鍵。_ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | Action | Shortcut - | ------------------------------------------------- |:----------------------------- - | 切換至全螢幕 | MOD+f - | 左旋顯示螢幕 | MOD+ _(左)_ - | 右旋顯示螢幕 | MOD+ _(右)_ - | 縮放視窗成 1:1 (pixel-perfect) | MOD+g - | 縮放視窗到沒有黑邊框為止 | MOD+w \| _雙擊¹_ - | 按下 `首頁` 鍵 | MOD+h \| _中鍵_ - | 按下 `返回` 鍵 | MOD+b \| _右鍵²_ - | 按下 `切換 APP` 鍵 | MOD+s - | 按下 `選單` 鍵 (或解鎖螢幕) | MOD+m - | 按下 `音量+` 鍵 | MOD+ _(上)_ - | 按下 `音量-` 鍵 | MOD+ _(下)_ - | 按下 `電源` 鍵 | MOD+p - | 開啟 | _右鍵²_ - | 關閉裝置螢幕(持續鏡像) | MOD+o - | 開啟裝置螢幕 | MOD+Shift+o - | 旋轉裝置螢幕 | MOD+r - | 開啟通知列 | MOD+n - | 關閉通知列 | MOD+Shift+n - | 複製至剪貼簿³ | MOD+c - | 剪下至剪貼簿³ | MOD+x - | 同步剪貼簿並貼上³ | MOD+v - | 複製電腦剪貼簿中的文字至裝置並貼上 | MOD+Shift+v - | 啟用/停用 FPS 計數器(顯示於 stdout - 通常是命令列) | MOD+i - -_¹在黑邊框上雙擊以移除它們。_ -_²右鍵會返回。如果螢幕是關閉狀態,則會打開螢幕。_ -_³只支援 Android 7+。_ - -所有 Ctrl+_按鍵_ 快捷鍵都會傳送到裝置上,所以它們是由目前運作的應用程式處理的。 - - -## 自訂路徑 - -如果要使用特定的 _adb_ ,將它設定到環境變數中的 `ADB`: - - ADB=/path/to/adb scrcpy - -如果要覆寫 `scrcpy-server` 檔案的路徑,則將路徑設定到環境變數中的 `SCRCPY_SERVER_PATH`。 - -[相關連結][useful] - -[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345 - - -## 為何叫 _scrcpy_ ? - -有一個同事要我找一個跟 [gnirehtet] 一樣難念的名字。 - -[`strcpy`] 複製一個字串 (**str**ing);`scrcpy` 複製一個螢幕 (**scr**een)。 - -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html - - -## 如何編譯? - -請看[這份文件 (英文)][BUILD]。 - -[BUILD]: BUILD.md - - -## 常見問題 - -請看[這份文件 (英文)][FAQ]。 - -[FAQ]: FAQ.md - - -## 開發者文件 - -請看[這個頁面 (英文)][developers page]. - -[developers page]: DEVELOP.md - - -## Licence - - Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -## 相關文章 - -- [Scrcpy 簡介 (英文)][article-intro] -- [Scrcpy 可以無線連線了 (英文)][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ From 3a66b5fd01f23ea24c1c540267fdd86a68a958a8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Aug 2022 00:15:25 +0200 Subject: [PATCH 0554/1133] Remove deprecated meson.source_root() This method is deprecated since Meson 0.56.0: We could replace it with meson.project_source_root(), but this would make Meson 0.56 or above mandatory. Since the path in always computed from the server/ directory, just add '..' to reference the root project directory. Refs c456e3826470753bdaefcffc93e9ae1bf25b12f7 --- server/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/meson.build b/server/meson.build index 984daf3b..42b97981 100644 --- a/server/meson.build +++ b/server/meson.build @@ -13,8 +13,8 @@ if prebuilt_server == '' install_dir: 'share/scrcpy') else if not prebuilt_server.startswith('/') - # relative path needs some trick - prebuilt_server = meson.source_root() + '/' + prebuilt_server + # prebuilt server path is relative to the root scrcpy directory + prebuilt_server = '../' + prebuilt_server endif custom_target('scrcpy-server-prebuilt', input: prebuilt_server, From 5b8e9aa0e95dd5e4aa234dfcbd0445005d197bf0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 14:51:07 +0200 Subject: [PATCH 0555/1133] Move toUnsigned() to a Binary util class PR #3369 --- .../java/com/genymobile/scrcpy/Binary.java | 15 +++++++++++++ .../scrcpy/ControlMessageReader.java | 22 ++++++------------- 2 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/Binary.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Binary.java b/server/src/main/java/com/genymobile/scrcpy/Binary.java new file mode 100644 index 00000000..1a096de9 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Binary.java @@ -0,0 +1,15 @@ +package com.genymobile.scrcpy; + +public final class Binary { + private Binary() { + // not instantiable + } + + public static int toUnsigned(short value) { + return value & 0xffff; + } + + public static int toUnsigned(byte value) { + return value & 0xff; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 24dc5e50..07ef1f7c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -103,7 +103,7 @@ public class ControlMessageReader { if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) { return null; } - int action = toUnsigned(buffer.get()); + int action = Binary.toUnsigned(buffer.get()); int keycode = buffer.getInt(); int repeat = buffer.getInt(); int metaState = buffer.getInt(); @@ -136,11 +136,11 @@ public class ControlMessageReader { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { return null; } - int action = toUnsigned(buffer.get()); + int action = Binary.toUnsigned(buffer.get()); long pointerId = buffer.getLong(); Position position = readPosition(buffer); // 16 bits fixed-point - int pressureInt = toUnsigned(buffer.getShort()); + int pressureInt = Binary.toUnsigned(buffer.getShort()); // convert it to a float between 0 and 1 (0x1p16f is 2^16 as float) float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f); int buttons = buffer.getInt(); @@ -162,7 +162,7 @@ public class ControlMessageReader { if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) { return null; } - int action = toUnsigned(buffer.get()); + int action = Binary.toUnsigned(buffer.get()); return ControlMessage.createBackOrScreenOn(action); } @@ -170,7 +170,7 @@ public class ControlMessageReader { if (buffer.remaining() < GET_CLIPBOARD_LENGTH) { return null; } - int copyKey = toUnsigned(buffer.get()); + int copyKey = Binary.toUnsigned(buffer.get()); return ControlMessage.createGetClipboard(copyKey); } @@ -198,16 +198,8 @@ public class ControlMessageReader { private static Position readPosition(ByteBuffer buffer) { int x = buffer.getInt(); int y = buffer.getInt(); - int screenWidth = toUnsigned(buffer.getShort()); - int screenHeight = toUnsigned(buffer.getShort()); + int screenWidth = Binary.toUnsigned(buffer.getShort()); + int screenHeight = Binary.toUnsigned(buffer.getShort()); return new Position(x, y, screenWidth, screenHeight); } - - private static int toUnsigned(short value) { - return value & 0xffff; - } - - private static int toUnsigned(byte value) { - return value & 0xff; - } } From 3848ce86f118496467ebd91b40dbf571e2a29e24 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 24 Jul 2022 16:00:26 +0200 Subject: [PATCH 0556/1133] Extract conversion from u16 fixed-point to float PR #3369 --- .../src/main/java/com/genymobile/scrcpy/Binary.java | 12 ++++++++++++ .../com/genymobile/scrcpy/ControlMessageReader.java | 5 +---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Binary.java b/server/src/main/java/com/genymobile/scrcpy/Binary.java index 1a096de9..db946664 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Binary.java +++ b/server/src/main/java/com/genymobile/scrcpy/Binary.java @@ -12,4 +12,16 @@ public final class Binary { public static int toUnsigned(byte value) { return value & 0xff; } + + /** + * Convert unsigned 16-bit fixed-point to a float between 0 and 1 + * + * @param value encoded value + * @return Float value between 0 and 1 + */ + public static float u16FixedPointToFloat(short value) { + int unsignedShort = Binary.toUnsigned(value); + // 0x1p16f is 2^16 as float + return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 07ef1f7c..b2d500c2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -139,10 +139,7 @@ public class ControlMessageReader { int action = Binary.toUnsigned(buffer.get()); long pointerId = buffer.getLong(); Position position = readPosition(buffer); - // 16 bits fixed-point - int pressureInt = Binary.toUnsigned(buffer.getShort()); - // convert it to a float between 0 and 1 (0x1p16f is 2^16 as float) - float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f); + float pressure = Binary.u16FixedPointToFloat(buffer.getShort()); int buttons = buffer.getInt(); return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons); } From 136ab8c19902e1b41915dabb23e36945e5b6e394 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 14:55:35 +0200 Subject: [PATCH 0557/1133] Add unit test for float decoding PR #3369 --- .../com/genymobile/scrcpy/BinaryTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 server/src/test/java/com/genymobile/scrcpy/BinaryTest.java diff --git a/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java b/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java new file mode 100644 index 00000000..b2d3d72d --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java @@ -0,0 +1,20 @@ +package com.genymobile.scrcpy; + +import org.junit.Assert; +import org.junit.Test; + +public class BinaryTest { + + @Test + public void testU16FixedPointToFloat() { + final float delta = 0.0f; // on these values, there MUST be no rounding error + Assert.assertEquals(0.0f, Binary.u16FixedPointToFloat((short) 0), delta); + Assert.assertEquals(0.03125f, Binary.u16FixedPointToFloat((short) 0x800), delta); + Assert.assertEquals(0.0625f, Binary.u16FixedPointToFloat((short) 0x1000), delta); + Assert.assertEquals(0.125f, Binary.u16FixedPointToFloat((short) 0x2000), delta); + Assert.assertEquals(0.25f, Binary.u16FixedPointToFloat((short) 0x4000), delta); + Assert.assertEquals(0.5f, Binary.u16FixedPointToFloat((short) 0x8000), delta); + Assert.assertEquals(0.75f, Binary.u16FixedPointToFloat((short) 0xc000), delta); + Assert.assertEquals(1.0f, Binary.u16FixedPointToFloat((short) 0xffff), delta); + } +} From 041cdf6cf5a5afa0d7794c89c5e477c67b31af24 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 15:13:16 +0200 Subject: [PATCH 0558/1133] Rename buffer_util.h to binary.h It will allow to expose more binary util functions not related to buffers. PR #3369 --- app/meson.build | 4 +-- app/src/control_msg.c | 2 +- app/src/demuxer.c | 2 +- app/src/device_msg.c | 2 +- app/src/util/{buffer_util.h => binary.h} | 4 +-- .../{test_buffer_util.c => test_binary.c} | 26 +++++++++---------- 6 files changed, 20 insertions(+), 20 deletions(-) rename app/src/util/{buffer_util.h => binary.h} (94%) rename app/tests/{test_buffer_util.c => test_binary.c} (72%) diff --git a/app/meson.build b/app/meson.build index f5d76c61..e41b02ba 100644 --- a/app/meson.build +++ b/app/meson.build @@ -245,8 +245,8 @@ if get_option('buildtype') == 'debug' 'src/util/str.c', 'src/util/strbuf.c', ]], - ['test_buffer_util', [ - 'tests/test_buffer_util.c', + ['test_binary', [ + 'tests/test_binary.c', ]], ['test_cbuf', [ 'tests/test_cbuf.c', diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 3c398016..0d68c45c 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -5,7 +5,7 @@ #include #include -#include "util/buffer_util.h" +#include "util/binary.h" #include "util/log.h" #include "util/str.h" diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 9412eda7..2c0c64a8 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -7,7 +7,7 @@ #include "decoder.h" #include "events.h" #include "recorder.h" -#include "util/buffer_util.h" +#include "util/binary.h" #include "util/log.h" #define SC_PACKET_HEADER_SIZE 12 diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 4b4a4974..265c7505 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -4,7 +4,7 @@ #include #include -#include "util/buffer_util.h" +#include "util/binary.h" #include "util/log.h" ssize_t diff --git a/app/src/util/buffer_util.h b/app/src/util/binary.h similarity index 94% rename from app/src/util/buffer_util.h rename to app/src/util/binary.h index a5456abf..e77f5e82 100644 --- a/app/src/util/buffer_util.h +++ b/app/src/util/binary.h @@ -1,5 +1,5 @@ -#ifndef SC_BUFFER_UTIL_H -#define SC_BUFFER_UTIL_H +#ifndef SC_BINARY_H +#define SC_BINARY_H #include "common.h" diff --git a/app/tests/test_buffer_util.c b/app/tests/test_binary.c similarity index 72% rename from app/tests/test_buffer_util.c rename to app/tests/test_binary.c index a9fec896..93aeccc8 100644 --- a/app/tests/test_buffer_util.c +++ b/app/tests/test_binary.c @@ -2,9 +2,9 @@ #include -#include "util/buffer_util.h" +#include "util/binary.h" -static void test_buffer_write16be(void) { +static void test_write16be(void) { uint16_t val = 0xABCD; uint8_t buf[2]; @@ -14,7 +14,7 @@ static void test_buffer_write16be(void) { assert(buf[1] == 0xCD); } -static void test_buffer_write32be(void) { +static void test_write32be(void) { uint32_t val = 0xABCD1234; uint8_t buf[4]; @@ -26,7 +26,7 @@ static void test_buffer_write32be(void) { assert(buf[3] == 0x34); } -static void test_buffer_write64be(void) { +static void test_write64be(void) { uint64_t val = 0xABCD1234567890EF; uint8_t buf[8]; @@ -42,7 +42,7 @@ static void test_buffer_write64be(void) { assert(buf[7] == 0xEF); } -static void test_buffer_read16be(void) { +static void test_read16be(void) { uint8_t buf[2] = {0xAB, 0xCD}; uint16_t val = sc_read16be(buf); @@ -50,7 +50,7 @@ static void test_buffer_read16be(void) { assert(val == 0xABCD); } -static void test_buffer_read32be(void) { +static void test_read32be(void) { uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34}; uint32_t val = sc_read32be(buf); @@ -58,7 +58,7 @@ static void test_buffer_read32be(void) { assert(val == 0xABCD1234); } -static void test_buffer_read64be(void) { +static void test_read64be(void) { uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78, 0x90, 0xEF}; @@ -71,11 +71,11 @@ int main(int argc, char *argv[]) { (void) argc; (void) argv; - test_buffer_write16be(); - test_buffer_write32be(); - test_buffer_write64be(); - test_buffer_read16be(); - test_buffer_read32be(); - test_buffer_read64be(); + test_write16be(); + test_write32be(); + test_write64be(); + test_read16be(); + test_read32be(); + test_read64be(); return 0; } From fd3483c83730a0a59f7b731e9cceabacb759ab20 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 15:17:44 +0200 Subject: [PATCH 0559/1133] Extract conversion from float to u16 fixed-point PR #3369 --- app/src/control_msg.c | 12 +----------- app/src/util/binary.h | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 0d68c45c..4aa0944c 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -78,16 +78,6 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) { return 4 + len; } -static uint16_t -to_fixed_point_16(float f) { - assert(f >= 0.0f && f <= 1.0f); - uint32_t u = f * 0x1p16f; // 2^16 - if (u >= 0xffff) { - u = 0xffff; - } - return (uint16_t) u; -} - size_t sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { buf[0] = msg->type; @@ -109,7 +99,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { sc_write64be(&buf[2], msg->inject_touch_event.pointer_id); write_position(&buf[10], &msg->inject_touch_event.position); uint16_t pressure = - to_fixed_point_16(msg->inject_touch_event.pressure); + sc_float_to_u16fp(msg->inject_touch_event.pressure); sc_write16be(&buf[22], pressure); sc_write32be(&buf[24], msg->inject_touch_event.buttons); return 28; diff --git a/app/src/util/binary.h b/app/src/util/binary.h index e77f5e82..734ab1d0 100644 --- a/app/src/util/binary.h +++ b/app/src/util/binary.h @@ -3,6 +3,7 @@ #include "common.h" +#include #include #include @@ -43,4 +44,18 @@ sc_read64be(const uint8_t *buf) { return ((uint64_t) msb << 32) | lsb; } +/** + * Convert a float between 0 and 1 to an unsigned 16-bit fixed-point value + */ +static inline uint16_t +sc_float_to_u16fp(float f) { + assert(f >= 0.0f && f <= 1.0f); + uint32_t u = f * 0x1p16f; // 2^16 + if (u >= 0xffff) { + assert(u == 0x10000); // for f == 1.0f + u = 0xffff; + } + return (uint16_t) u; +} + #endif From 1ab6c19486b43c55a14c0a309f34cd081bfab85d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 15:23:39 +0200 Subject: [PATCH 0560/1133] Add unit test for float encoding PR #3369 --- app/tests/test_binary.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/tests/test_binary.c b/app/tests/test_binary.c index 93aeccc8..6e52da72 100644 --- a/app/tests/test_binary.c +++ b/app/tests/test_binary.c @@ -67,6 +67,17 @@ static void test_read64be(void) { assert(val == 0xABCD1234567890EF); } +static void test_float_to_u16fp(void) { + assert(sc_float_to_u16fp(0.0f) == 0); + assert(sc_float_to_u16fp(0.03125f) == 0x800); + assert(sc_float_to_u16fp(0.0625f) == 0x1000); + assert(sc_float_to_u16fp(0.125f) == 0x2000); + assert(sc_float_to_u16fp(0.25f) == 0x4000); + assert(sc_float_to_u16fp(0.5f) == 0x8000); + assert(sc_float_to_u16fp(0.75f) == 0xc000); + assert(sc_float_to_u16fp(1.0f) == 0xffff); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -77,5 +88,7 @@ int main(int argc, char *argv[]) { test_read16be(); test_read32be(); test_read64be(); + + test_float_to_u16fp(); return 0; } From 1f138aef41de651668043b32c4effc2d4adbfc44 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Aug 2022 13:04:15 +0200 Subject: [PATCH 0561/1133] Add conversion from float to fixed-point i16 To encode float values between -1 and 1. PR #3369 --- app/src/util/binary.h | 15 +++++++++++++ app/tests/test_binary.c | 20 +++++++++++++++++ .../java/com/genymobile/scrcpy/Binary.java | 11 ++++++++++ .../com/genymobile/scrcpy/BinaryTest.java | 22 +++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/app/src/util/binary.h b/app/src/util/binary.h index 734ab1d0..6dc1b58e 100644 --- a/app/src/util/binary.h +++ b/app/src/util/binary.h @@ -58,4 +58,19 @@ sc_float_to_u16fp(float f) { return (uint16_t) u; } +/** + * Convert a float between -1 and 1 to a signed 16-bit fixed-point value + */ +static inline int16_t +sc_float_to_i16fp(float f) { + assert(f >= -1.0f && f <= 1.0f); + int32_t i = f * 0x1p15f; // 2^15 + assert(i >= -0x8000); + if (i >= 0x7fff) { + assert(i == 0x8000); // for f == 1.0f + i = 0x7fff; + } + return (int16_t) i; +} + #endif diff --git a/app/tests/test_binary.c b/app/tests/test_binary.c index 6e52da72..82a9c1e0 100644 --- a/app/tests/test_binary.c +++ b/app/tests/test_binary.c @@ -78,6 +78,25 @@ static void test_float_to_u16fp(void) { assert(sc_float_to_u16fp(1.0f) == 0xffff); } +static void test_float_to_i16fp(void) { + assert(sc_float_to_i16fp(0.0f) == 0); + assert(sc_float_to_i16fp(0.03125f) == 0x400); + assert(sc_float_to_i16fp(0.0625f) == 0x800); + assert(sc_float_to_i16fp(0.125f) == 0x1000); + assert(sc_float_to_i16fp(0.25f) == 0x2000); + assert(sc_float_to_i16fp(0.5f) == 0x4000); + assert(sc_float_to_i16fp(0.75f) == 0x6000); + assert(sc_float_to_i16fp(1.0f) == 0x7fff); + + assert(sc_float_to_i16fp(-0.03125f) == -0x400); + assert(sc_float_to_i16fp(-0.0625f) == -0x800); + assert(sc_float_to_i16fp(-0.125f) == -0x1000); + assert(sc_float_to_i16fp(-0.25f) == -0x2000); + assert(sc_float_to_i16fp(-0.5f) == -0x4000); + assert(sc_float_to_i16fp(-0.75f) == -0x6000); + assert(sc_float_to_i16fp(-1.0f) == -0x8000); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -90,5 +109,6 @@ int main(int argc, char *argv[]) { test_read64be(); test_float_to_u16fp(); + test_float_to_i16fp(); return 0; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Binary.java b/server/src/main/java/com/genymobile/scrcpy/Binary.java index db946664..29534f59 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Binary.java +++ b/server/src/main/java/com/genymobile/scrcpy/Binary.java @@ -24,4 +24,15 @@ public final class Binary { // 0x1p16f is 2^16 as float return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f); } + + /** + * Convert signed 16-bit fixed-point to a float between -1 and 1 + * + * @param value encoded value + * @return Float value between -1 and 1 + */ + public static float i16FixedPointToFloat(short value) { + // 0x1p15f is 2^15 as float + return value == 0x7fff ? 1f : (value / 0x1p15f); + } } diff --git a/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java b/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java index b2d3d72d..569a2f2c 100644 --- a/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/BinaryTest.java @@ -17,4 +17,26 @@ public class BinaryTest { Assert.assertEquals(0.75f, Binary.u16FixedPointToFloat((short) 0xc000), delta); Assert.assertEquals(1.0f, Binary.u16FixedPointToFloat((short) 0xffff), delta); } + + @Test + public void testI16FixedPointToFloat() { + final float delta = 0.0f; // on these values, there MUST be no rounding error + + Assert.assertEquals(0.0f, Binary.i16FixedPointToFloat((short) 0), delta); + Assert.assertEquals(0.03125f, Binary.i16FixedPointToFloat((short) 0x400), delta); + Assert.assertEquals(0.0625f, Binary.i16FixedPointToFloat((short) 0x800), delta); + Assert.assertEquals(0.125f, Binary.i16FixedPointToFloat((short) 0x1000), delta); + Assert.assertEquals(0.25f, Binary.i16FixedPointToFloat((short) 0x2000), delta); + Assert.assertEquals(0.5f, Binary.i16FixedPointToFloat((short) 0x4000), delta); + Assert.assertEquals(0.75f, Binary.i16FixedPointToFloat((short) 0x6000), delta); + Assert.assertEquals(1.0f, Binary.i16FixedPointToFloat((short) 0x7fff), delta); + + Assert.assertEquals(-0.03125f, Binary.i16FixedPointToFloat((short) -0x400), delta); + Assert.assertEquals(-0.0625f, Binary.i16FixedPointToFloat((short) -0x800), delta); + Assert.assertEquals(-0.125f, Binary.i16FixedPointToFloat((short) -0x1000), delta); + Assert.assertEquals(-0.25f, Binary.i16FixedPointToFloat((short) -0x2000), delta); + Assert.assertEquals(-0.5f, Binary.i16FixedPointToFloat((short) -0x4000), delta); + Assert.assertEquals(-0.75f, Binary.i16FixedPointToFloat((short) -0x6000), delta); + Assert.assertEquals(-1.0f, Binary.i16FixedPointToFloat((short) -0x8000), delta); + } } From 57056d078ddcac73fe10fcb41395ed21f3d486b6 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 3 Jul 2022 07:02:17 +0000 Subject: [PATCH 0562/1133] Use precise scrolling values Since SDL 2.0.18, the amount scrolled horizontally or vertically is exposed as a float (between 0 and 1). Forward a precise value to the Android device when possible. Refs Fixes #3363 PR #3369 Signed-off-by: Romain Vimont --- app/src/control_msg.c | 18 ++++++++++-------- app/src/control_msg.h | 4 ++-- app/src/input_events.h | 4 ++-- app/src/input_manager.c | 9 +++++++-- app/tests/test_control_msg_serialize.c | 6 +++--- .../com/genymobile/scrcpy/ControlMessage.java | 10 +++++----- .../scrcpy/ControlMessageReader.java | 6 +++--- .../java/com/genymobile/scrcpy/Controller.java | 2 +- .../scrcpy/ControlMessageReaderTest.java | 8 ++++---- 9 files changed, 37 insertions(+), 30 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 4aa0944c..3513abc7 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -105,12 +105,14 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { return 28; case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: write_position(&buf[1], &msg->inject_scroll_event.position); - sc_write32be(&buf[13], - (uint32_t) msg->inject_scroll_event.hscroll); - sc_write32be(&buf[17], - (uint32_t) msg->inject_scroll_event.vscroll); - sc_write32be(&buf[21], msg->inject_scroll_event.buttons); - return 25; + int16_t hscroll = + sc_float_to_i16fp(msg->inject_scroll_event.hscroll); + int16_t vscroll = + sc_float_to_i16fp(msg->inject_scroll_event.vscroll); + sc_write16be(&buf[13], (uint16_t) hscroll); + sc_write16be(&buf[15], (uint16_t) vscroll); + sc_write32be(&buf[17], msg->inject_scroll_event.buttons); + return 21; case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: buf[1] = msg->inject_keycode.action; return 2; @@ -181,8 +183,8 @@ sc_control_msg_log(const struct sc_control_msg *msg) { break; } case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: - LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 - " vscroll=%" PRIi32 " buttons=%06lx", + LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f" + " vscroll=%f buttons=%06lx", msg->inject_scroll_event.position.point.x, msg->inject_scroll_event.position.point.y, msg->inject_scroll_event.hscroll, diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 1463fddc..f51bdecd 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -68,8 +68,8 @@ struct sc_control_msg { } inject_touch_event; struct { struct sc_position position; - int32_t hscroll; - int32_t vscroll; + float hscroll; + float vscroll; enum android_motionevent_buttons buttons; } inject_scroll_event; struct { diff --git a/app/src/input_events.h b/app/src/input_events.h index 9bf3c421..15d22910 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -358,8 +358,8 @@ struct sc_mouse_click_event { struct sc_mouse_scroll_event { struct sc_position position; - int32_t hscroll; - int32_t vscroll; + float hscroll; + float vscroll; uint8_t buttons_state; // bitwise-OR of sc_mouse_button values }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index bba3665c..42b49a13 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -747,8 +747,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, .point = sc_screen_convert_window_to_frame_coords(im->screen, mouse_x, mouse_y), }, - .hscroll = event->x, - .vscroll = event->y, +#if SDL_VERSION_ATLEAST(2, 0, 18) + .hscroll = CLAMP(event->preciseX, -1.0f, 1.0f), + .vscroll = CLAMP(event->preciseY, -1.0f, 1.0f), +#else + .hscroll = CLAMP(event->x, -1, 1), + .vscroll = CLAMP(event->y, -1, 1), +#endif .buttons_state = sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks), }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 72e3d87a..87117e3a 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -132,14 +132,14 @@ static void test_serialize_inject_scroll_event(void) { unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); - assert(size == 25); + assert(size == 21); const unsigned char expected[] = { SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x04, 0x38, 0x07, 0x80, // 1080 1920 - 0x00, 0x00, 0x00, 0x01, // 1 - 0xFF, 0xFF, 0xFF, 0xFF, // -1 + 0x7F, 0xFF, // 1 (float encoded as i16) + 0x80, 0x00, // -1 (float encoded as i16) 0x00, 0x00, 0x00, 0x01, // 1 }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 99eb805f..0b05b22a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -33,8 +33,8 @@ public final class ControlMessage { private long pointerId; private float pressure; private Position position; - private int hScroll; - private int vScroll; + private float hScroll; + private float vScroll; private int copyKey; private boolean paste; private int repeat; @@ -71,7 +71,7 @@ public final class ControlMessage { return msg; } - public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) { + public static ControlMessage createInjectScrollEvent(Position position, float hScroll, float vScroll, int buttons) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_INJECT_SCROLL_EVENT; msg.position = position; @@ -156,11 +156,11 @@ public final class ControlMessage { return position; } - public int getHScroll() { + public float getHScroll() { return hScroll; } - public int getVScroll() { + public float getVScroll() { return vScroll; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index b2d500c2..a52fd134 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -10,7 +10,7 @@ public class ControlMessageReader { static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; - static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24; + static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1; @@ -149,8 +149,8 @@ public class ControlMessageReader { return null; } Position position = readPosition(buffer); - int hScroll = buffer.getInt(); - int vScroll = buffer.getInt(); + float hScroll = Binary.i16FixedPointToFloat(buffer.getShort()); + float vScroll = Binary.i16FixedPointToFloat(buffer.getShort()); int buttons = buffer.getInt(); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 913371ee..95b64711 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -223,7 +223,7 @@ public class Controller { return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } - private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) { + private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) { long now = SystemClock.uptimeMillis(); Point point = device.getPhysicalPoint(position); if (point == null) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 2a4ffe75..1fc009ce 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -126,8 +126,8 @@ public class ControlMessageReaderTest { dos.writeInt(1026); dos.writeShort(1080); dos.writeShort(1920); - dos.writeInt(1); - dos.writeInt(-1); + dos.writeShort(0); // 0.0f encoded as i16 + dos.writeShort(0x8000); // -1.0f encoded as i16 dos.writeInt(1); byte[] packet = bos.toByteArray(); @@ -143,8 +143,8 @@ public class ControlMessageReaderTest { Assert.assertEquals(1026, event.getPosition().getPoint().getY()); Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); - Assert.assertEquals(1, event.getHScroll()); - Assert.assertEquals(-1, event.getVScroll()); + Assert.assertEquals(0f, event.getHScroll(), 0f); + Assert.assertEquals(-1f, event.getVScroll(), 0f); Assert.assertEquals(1, event.getButtons()); } From 121bb71dfe15bbb8797acf47f02f60a043817762 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 Sep 2022 14:31:15 +0200 Subject: [PATCH 0563/1133] Move from jcenter() to mavenCentral() Refs Refs --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c7d31ef7..2f76a75a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.3' @@ -17,7 +17,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:deprecation" From fccfc43b9ed702480dcf89cc446b35f90b673b18 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 Sep 2022 14:35:05 +0200 Subject: [PATCH 0564/1133] Upgrade gradle build tools to 7.2.2 Plugin version 7.2.2. Gradle version 7.3.3. Refs: --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2f76a75a..ecc7972e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 29e41345..669386b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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-7.3.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 0a0a446ea6d790420c3d52eb1e6d4afebd086b35 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 Sep 2022 14:41:23 +0200 Subject: [PATCH 0565/1133] Upgrade Android SDK to 33 --- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/build.gradle b/server/build.gradle index 9725089e..00590381 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdkVersion 33 defaultConfig { applicationId "com.genymobile.scrcpy" minSdkVersion 21 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 12400 versionName "1.24" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index c881e38a..1444e72c 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,8 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=1.24 -PLATFORM=${ANDROID_PLATFORM:-31} -BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} +PLATFORM=${ANDROID_PLATFORM:-33} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" From c1ec1d1023e9056195a386200fedf5f038c3ea6f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Jun 2022 12:15:37 +0200 Subject: [PATCH 0566/1133] Replace hardcoded 'share/' by datadir variable Meson defines a variable for the data directory. PR #3351 --- app/meson.build | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/meson.build b/app/meson.build index e41b02ba..e57a8428 100644 --- a/app/meson.build +++ b/app/meson.build @@ -223,14 +223,17 @@ executable('scrcpy', src, install: true, c_args: []) +# +datadir = get_option('datadir') # by default 'share' + install_man('scrcpy.1') install_data('data/icon.png', rename: 'scrcpy.png', - install_dir: 'share/icons/hicolor/256x256/apps') + install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps')) install_data('data/zsh-completion/_scrcpy', - install_dir: 'share/zsh/site-functions') + install_dir: join_paths(datadir, 'zsh/site-functions')) install_data('data/bash-completion/scrcpy', - install_dir: 'share/bash-completion/completions') + install_dir: join_paths(datadir, 'bash-completion/completions')) ### TESTS From 51a1762cbd3fc522bdb81afe58962277b8af8a04 Mon Sep 17 00:00:00 2001 From: Addison Snelling Date: Sun, 14 Oct 2018 03:50:35 -0500 Subject: [PATCH 0567/1133] Add desktop entry file for Linux app launchers Refs PR #3351 Replaces PR #296 Fixes #295 Fixes #748 Fixes #1636 Co-authored-by: Chih-Hsuan Yen Signed-off-by: Romain Vimont --- app/data/scrcpy.desktop | 10 ++++++++++ app/meson.build | 7 +++++++ 2 files changed, 17 insertions(+) create mode 100644 app/data/scrcpy.desktop diff --git a/app/data/scrcpy.desktop b/app/data/scrcpy.desktop new file mode 100644 index 00000000..5933272d --- /dev/null +++ b/app/data/scrcpy.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=scrcpy +GenericName=Android Remote Control +Comment=Display and control your Android device +Exec=scrcpy +Icon=scrcpy +Terminal=false +Type=Application +Categories=Utility;RemoteAccess; +StartupNotify=false diff --git a/app/meson.build b/app/meson.build index e57a8428..0551011e 100644 --- a/app/meson.build +++ b/app/meson.build @@ -235,6 +235,13 @@ install_data('data/zsh-completion/_scrcpy', install_data('data/bash-completion/scrcpy', install_dir: join_paths(datadir, 'bash-completion/completions')) +# Desktop entry file for application launchers +if host_machine.system() == 'linux' + # Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop) + install_data('data/scrcpy.desktop', + install_dir: join_paths(datadir, 'applications')) +endif + ### TESTS From a2a22f497f38daa35b5dc42b5e93f06f3b37a3cc Mon Sep 17 00:00:00 2001 From: Chih-Hsuan Yen Date: Sat, 11 Dec 2021 11:47:57 +0800 Subject: [PATCH 0568/1133] Use shell environment to execute launcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make Exec= compatible with $PATH configured in .bashrc or .zshrc… PR #3351 Refs #296 Signed-off-by: Romain Vimont --- app/data/scrcpy.desktop | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/data/scrcpy.desktop b/app/data/scrcpy.desktop index 5933272d..082b75e0 100644 --- a/app/data/scrcpy.desktop +++ b/app/data/scrcpy.desktop @@ -2,7 +2,10 @@ Name=scrcpy GenericName=Android Remote Control Comment=Display and control your Android device -Exec=scrcpy +# For some users, the PATH or ADB environment variables are set from the shell +# startup file, like .bashrc or .zshrc… Run an interactive shell to get +# environment correctly initialized. +Exec=/bin/sh -c '"$SHELL" -i -c scrcpy' Icon=scrcpy Terminal=false Type=Application From e5e210506f62ded9417b252d059ed79505e3b4eb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Jun 2022 13:32:40 +0200 Subject: [PATCH 0569/1133] Add scrcpy-console.desktop Add a launcher which opens a terminal, and keep it open in case of errors (so that the user has time to read error messages). The behavior is the same as scrcpy-console.bat on Windows. PR #3351 --- app/data/scrcpy-console.desktop | 13 +++++++++++++ app/meson.build | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 app/data/scrcpy-console.desktop diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop new file mode 100644 index 00000000..47a63ec9 --- /dev/null +++ b/app/data/scrcpy-console.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Name=scrcpy (console) +GenericName=Android Remote Control +Comment=Display and control your Android device +# For some users, the PATH or ADB environment variables are set from the shell +# startup file, like .bashrc or .zshrc… Run an interactive shell to get +# environment correctly initialized. +Exec=/bin/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."' +Icon=scrcpy +Terminal=true +Type=Application +Categories=Utility;RemoteAccess; +StartupNotify=false diff --git a/app/meson.build b/app/meson.build index 0551011e..fd5418e3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -240,6 +240,8 @@ if host_machine.system() == 'linux' # Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop) install_data('data/scrcpy.desktop', install_dir: join_paths(datadir, 'applications')) + install_data('data/scrcpy-console.desktop', + install_dir: join_paths(datadir, 'applications')) endif From 4a5cdcd3908b5bdce7e8528d9045553a6019faa6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Jun 2022 15:19:53 +0200 Subject: [PATCH 0570/1133] Extract $BUILD_TOOLS_DIR In the script to build without gradle, the build-tools full path is used at several places. Use a separate variable for readability. --- server/build_without_gradle.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 1444e72c..f4fcba10 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -16,6 +16,7 @@ SCRCPY_VERSION_NAME=1.24 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} +BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" @@ -41,9 +42,8 @@ EOF echo "Generating java from aidl..." cd "$SERVER_DIR/src/main/aidl" -"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \ - android/view/IRotationWatcher.aidl -"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \ +"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl +"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \ android/content/IOnPrimaryClipChangedListener.aidl echo "Compiling java sources..." @@ -59,8 +59,7 @@ cd "$CLASSES_DIR" if [[ $PLATFORM -lt 31 ]] then # use dx - "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ - --output "$BUILD_DIR/classes.dex" \ + "$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \ android/view/*.class \ android/content/*.class \ com/genymobile/scrcpy/*.class \ @@ -72,7 +71,7 @@ then rm -rf classes.dex classes else # use d8 - "$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \ + "$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \ --output "$BUILD_DIR/classes.zip" \ android/view/*.class \ android/content/*.class \ From 00e9e69c2a198974d992d8bcbb97866f10da4e9f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 25 Sep 2022 15:39:55 +0200 Subject: [PATCH 0571/1133] Use /dev/null instead of closing fds Some adb commands do not like when stdin, stdout or stderr are closed (they hang forever). Open /dev/null for each. --- app/src/sys/unix/process.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/sys/unix/process.c b/app/src/sys/unix/process.c index cef227ed..8c4a53c3 100644 --- a/app/src/sys/unix/process.c +++ b/app/src/sys/unix/process.c @@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, close(in[0]); } close(in[1]); + } else { + int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666); + if (devnull != -1) { + dup2(devnull, STDIN_FILENO); + } else { + LOGE("Could not open /dev/null for stdin"); + } } - // Do not close stdin in the child process, this makes adb fail on Linux if (pout) { if (out[1] != STDOUT_FILENO) { @@ -102,8 +108,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, } close(out[0]); } else if (!inherit_stdout) { - // Close stdout in the child process - close(STDOUT_FILENO); + int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); + if (devnull != -1) { + dup2(devnull, STDOUT_FILENO); + } else { + LOGE("Could not open /dev/null for stdout"); + } } if (perr) { @@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, } close(err[0]); } else if (!inherit_stderr) { - // Close stderr in the child process - close(STDERR_FILENO); + int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); + if (devnull != -1) { + dup2(devnull, STDERR_FILENO); + } else { + LOGE("Could not open /dev/null for stderr"); + } } close(internal[0]); From 949b64dff2efe23039fa4801fbc95ef46e208477 Mon Sep 17 00:00:00 2001 From: SeungHoon Han Date: Tue, 2 Aug 2022 19:39:50 +0900 Subject: [PATCH 0572/1133] Add fallback to get DisplayInfo PR #3416 Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/Command.java | 10 + .../main/java/com/genymobile/scrcpy/IO.java | 11 ++ .../scrcpy/wrappers/DisplayManager.java | 60 +++++- .../genymobile/scrcpy/CommandParserTest.java | 186 ++++++++++++++++++ 4 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Command.java b/server/src/main/java/com/genymobile/scrcpy/Command.java index 0ef976a6..362504ff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Command.java +++ b/server/src/main/java/com/genymobile/scrcpy/Command.java @@ -30,4 +30,14 @@ public final class Command { } return result; } + + public static String execReadOutput(String... cmd) throws IOException, InterruptedException { + Process process = Runtime.getRuntime().exec(cmd); + String output = IO.toString(process.getInputStream()); + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode); + } + return output; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java index 57c017db..6eaf0d6a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/IO.java +++ b/server/src/main/java/com/genymobile/scrcpy/IO.java @@ -6,7 +6,9 @@ import android.system.OsConstants; import java.io.FileDescriptor; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Scanner; public final class IO { private IO() { @@ -37,4 +39,13 @@ public final class IO { public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException { writeFully(fd, ByteBuffer.wrap(buffer, offset, len)); } + + public static String toString(InputStream inputStream) { + StringBuilder builder = new StringBuilder(); + Scanner scanner = new Scanner(inputStream); + while (scanner.hasNextLine()) { + builder.append(scanner.nextLine()).append('\n'); + } + return builder.toString(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 3f4f897d..bf172126 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -1,8 +1,16 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.Command; import com.genymobile.scrcpy.DisplayInfo; +import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; +import android.view.Display; + +import java.lang.reflect.Field; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public final class DisplayManager { private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal @@ -10,11 +18,61 @@ public final class DisplayManager { this.manager = manager; } + // public to call it from unit tests + public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) { + Pattern regex = Pattern.compile( + "^ mOverrideDisplayInfo=DisplayInfo\\{\".*?\", displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, " + + "rotation ([0-9]+).*?, layerStack ([0-9]+)", + Pattern.MULTILINE); + Matcher m = regex.matcher(dumpsysDisplayOutput); + if (!m.find()) { + return null; + } + int flags = parseDisplayFlags(m.group(1)); + int width = Integer.parseInt(m.group(2)); + int height = Integer.parseInt(m.group(3)); + int rotation = Integer.parseInt(m.group(4)); + int layerStack = Integer.parseInt(m.group(5)); + + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags); + } + + private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) { + try { + String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display"); + return parseDisplayInfo(dumpsysDisplayOutput, displayId); + } catch (Exception e) { + Ln.e("Could not get display info from \"dumpsys display\" output", e); + return null; + } + } + + private static int parseDisplayFlags(String text) { + Pattern regex = Pattern.compile("FLAG_[A-Z_]+"); + if (text == null) { + return 0; + } + + int flags = 0; + Matcher m = regex.matcher(text); + while (m.find()) { + String flagString = m.group(); + try { + Field filed = Display.class.getDeclaredField(flagString); + flags |= filed.getInt(null); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Silently ignore, some flags reported by "dumpsys display" are @TestApi + } + } + return flags; + } + public DisplayInfo getDisplayInfo(int displayId) { try { Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId); if (displayInfo == null) { - return null; + // fallback when displayInfo is null + return getDisplayInfoFromDumpsysDisplay(displayId); } Class cls = displayInfo.getClass(); // width and height already take the rotation into account diff --git a/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java new file mode 100644 index 00000000..e6f57bcb --- /dev/null +++ b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java @@ -0,0 +1,186 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.DisplayManager; + +import android.view.Display; +import org.junit.Assert; +import org.junit.Test; + +public class CommandParserTest { + @Test + public void testParseDisplayInfoFromDumpsysDisplay() { + /* @formatter:off */ + String partialOutput = "Logical Displays: size=1\n" + + " Display 0:\n" + + "mDisplayId=0\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, " + + "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, " + + "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, " + + "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], " + + "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state OFF, " + + "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, " + + "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, " + + "FLAG_TRUSTED, real 1440 x 3120, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, " + + "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, " + + "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities " + + "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, " + + "minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 " + + "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo " + + "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, " + + "relativeAddress=null}, removeMode 0}\n" + + " mRequestedMinimalPostProcessing=false\n"; + DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0); + Assert.assertNotNull(displayInfo); + Assert.assertEquals(0, displayInfo.getDisplayId()); + Assert.assertEquals(0, displayInfo.getRotation()); + Assert.assertEquals(0, displayInfo.getLayerStack()); + // FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported + Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags()); + Assert.assertEquals(1440, displayInfo.getSize().getWidth()); + Assert.assertEquals(3120, displayInfo.getSize().getHeight()); + } + + @Test + public void testParseDisplayInfoFromDumpsysDisplayWithRotation() { + /* @formatter:off */ + String partialOutput = "Logical Displays: size=1\n" + + " Display 0:\n" + + "mDisplayId=0\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, " + + "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, " + + "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, " + + "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], " + + "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state ON, " + + "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, " + + "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, " + + "FLAG_TRUSTED, real 3120 x 1440, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, " + + "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, " + + "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities " + + "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, " + + "minimalPostProcessingSupported false, rotation 3, state ON, type INTERNAL, uniqueId \"local:0\", app 3120 x 1440, density 600 " + + "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo " + + "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, " + + "relativeAddress=null}, removeMode 0}\n" + + " mRequestedMinimalPostProcessing=false"; + DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0); + Assert.assertNotNull(displayInfo); + Assert.assertEquals(0, displayInfo.getDisplayId()); + Assert.assertEquals(3, displayInfo.getRotation()); + Assert.assertEquals(0, displayInfo.getLayerStack()); + // FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported + Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags()); + Assert.assertEquals(3120, displayInfo.getSize().getWidth()); + Assert.assertEquals(1440, displayInfo.getSize().getHeight()); + } + + @Test + public void testParseDisplayInfoFromDumpsysDisplayAPI31() { + /* @formatter:off */ + String partialOutput = "Logical Displays: size=1\n" + + " Display 0:\n" + + " mDisplayId=0\n" + + " mPhase=1\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 " + + "Infinity]}\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, " + + "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff " + + "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, " + + "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, " + + "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state " + + "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, " + + "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, " + + "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, " + + "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff " + + "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, " + + "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, " + + "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state " + + "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, " + + "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, " + + "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n" + + " mRequestedMinimalPostProcessing=false\n" + + " mFrameRateOverrides=[]\n" + + " mPendingFrameRateOverrideUids={}\n"; + DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0); + Assert.assertNotNull(displayInfo); + Assert.assertEquals(0, displayInfo.getDisplayId()); + Assert.assertEquals(0, displayInfo.getRotation()); + Assert.assertEquals(0, displayInfo.getLayerStack()); + // FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported + Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags()); + Assert.assertEquals(1080, displayInfo.getSize().getWidth()); + Assert.assertEquals(2280, displayInfo.getSize().getHeight()); + } + + @Test + public void testParseDisplayInfoFromDumpsysDisplayAPI31NoFlags() { + /* @formatter:off */ + String partialOutput = "Logical Displays: size=1\n" + + " Display 0:\n" + + " mDisplayId=0\n" + + " mPhase=1\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 " + + "Infinity]}\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, " + + "real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff " + + "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, " + + "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, " + + "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state " + + "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, " + + "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, " + + "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, " + + "real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff " + + "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, " + + "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, " + + "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state " + + "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, " + + "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, " + + "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, " + + "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n" + + " mRequestedMinimalPostProcessing=false\n" + + " mFrameRateOverrides=[]\n" + + " mPendingFrameRateOverrideUids={}\n"; + DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0); + Assert.assertNotNull(displayInfo); + Assert.assertEquals(0, displayInfo.getDisplayId()); + Assert.assertEquals(0, displayInfo.getRotation()); + Assert.assertEquals(0, displayInfo.getLayerStack()); + Assert.assertEquals(0, displayInfo.getFlags()); + Assert.assertEquals(1080, displayInfo.getSize().getWidth()); + Assert.assertEquals(2280, displayInfo.getSize().getHeight()); + } +} From 7505f7117e19de18e405a3662a38523bf7d20209 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 27 Sep 2022 14:12:27 +0200 Subject: [PATCH 0573/1133] Fix typo in logs --- app/src/control_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 3513abc7..fce846ed 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = { "move", "cancel", "outside", - "ponter-down", + "pointer-down", "pointer-up", "hover-move", "scroll", From 40644994e8fb400dfd298810cd05227a7d5915eb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 2 Oct 2022 17:39:23 +0200 Subject: [PATCH 0574/1133] Make ServiceManager and Settings methods static There were exactly one instance of ServiceManager and Settings, stored in Device. Since a Device instance is not created by the CleanUp executable, it was not straightforward to call wrapper methods on cleanup. Remove this artificial restriction and expose them publicly via static methods (this is equivalent to expose a singleton, but less verbose). --- .../java/com/genymobile/scrcpy/CleanUp.java | 8 +--- .../java/com/genymobile/scrcpy/Device.java | 31 +++++-------- .../java/com/genymobile/scrcpy/Server.java | 5 +-- .../java/com/genymobile/scrcpy/Settings.java | 20 ++++----- .../scrcpy/wrappers/ServiceManager.java | 45 ++++++++++--------- 5 files changed, 49 insertions(+), 60 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 319a957d..831dc994 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -1,7 +1,5 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.ServiceManager; - import android.os.Parcel; import android.os.Parcelable; import android.util.Base64; @@ -164,12 +162,10 @@ public final class CleanUp { Config config = Config.fromBase64(args[0]); if (config.disableShowTouches || config.restoreStayOn != -1) { - ServiceManager serviceManager = new ServiceManager(); - Settings settings = new Settings(serviceManager); if (config.disableShowTouches) { Ln.i("Disabling \"show touches\""); try { - settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); } catch (SettingsException e) { Ln.e("Could not restore \"show_touches\"", e); } @@ -177,7 +173,7 @@ public final class CleanUp { if (config.restoreStayOn != -1) { Ln.i("Restoring \"stay awake\""); try { - settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); + Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); } catch (SettingsException e) { Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 763a7fad..97b25b22 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -31,9 +31,6 @@ public final class Device { public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; - private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); - private static final Settings SETTINGS = new Settings(SERVICE_MANAGER); - public interface RotationListener { void onRotationChanged(int rotation); } @@ -66,9 +63,9 @@ public final class Device { public Device(Options options) { displayId = options.getDisplayId(); - DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId); + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { - int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds(); + int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); throw new InvalidDisplayIdException(displayId, displayIds); } @@ -82,7 +79,7 @@ public final class Device { screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); layerStack = displayInfo.getLayerStack(); - SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { + ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) { synchronized (Device.this) { @@ -98,7 +95,7 @@ public final class Device { if (options.getControl() && options.getClipboardAutosync()) { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically - ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardManager != null) { clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { @Override @@ -192,7 +189,7 @@ public final class Device { return false; } - return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode); + return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode); } public boolean injectEvent(InputEvent event, int injectMode) { @@ -220,7 +217,7 @@ public final class Device { } public static boolean isScreenOn() { - return SERVICE_MANAGER.getPowerManager().isScreenOn(); + return ServiceManager.getPowerManager().isScreenOn(); } public synchronized void setRotationListener(RotationListener rotationListener) { @@ -232,19 +229,19 @@ public final class Device { } public static void expandNotificationPanel() { - SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel(); + ServiceManager.getStatusBarManager().expandNotificationsPanel(); } public static void expandSettingsPanel() { - SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel(); + ServiceManager.getStatusBarManager().expandSettingsPanel(); } public static void collapsePanels() { - SERVICE_MANAGER.getStatusBarManager().collapsePanels(); + ServiceManager.getStatusBarManager().collapsePanels(); } public static String getClipboardText() { - ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardManager == null) { return null; } @@ -256,7 +253,7 @@ public final class Device { } public boolean setClipboardText(String text) { - ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager(); + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardManager == null) { return false; } @@ -299,7 +296,7 @@ public final class Device { * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). */ public static void rotateDevice() { - WindowManager wm = SERVICE_MANAGER.getWindowManager(); + WindowManager wm = ServiceManager.getWindowManager(); boolean accelerometerRotation = !wm.isRotationFrozen(); @@ -315,8 +312,4 @@ public final class Device { wm.thawRotation(); } } - - public static Settings getSettings() { - return SETTINGS; - } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 1df91552..ec03515e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -20,10 +20,9 @@ public final class Server { int restoreStayOn = -1; boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled if (options.getShowTouches() || options.getStayAwake()) { - Settings settings = Device.getSettings(); if (options.getShowTouches()) { try { - String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); + String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); // If "show touches" was disabled, it must be disabled back on clean up mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); } catch (SettingsException e) { @@ -34,7 +33,7 @@ public final class Server { if (options.getStayAwake()) { int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; try { - String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); + String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); try { restoreStayOn = Integer.parseInt(oldValue); if (restoreStayOn == stayOn) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java index cb15ebb4..1d38814d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Settings.java +++ b/server/src/main/java/com/genymobile/scrcpy/Settings.java @@ -7,16 +7,14 @@ import android.os.Build; import java.io.IOException; -public class Settings { +public final class Settings { public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE; public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL; - private final ServiceManager serviceManager; - - public Settings(ServiceManager serviceManager) { - this.serviceManager = serviceManager; + private Settings() { + /* not instantiable */ } private static void execSettingsPut(String table, String key, String value) throws SettingsException { @@ -35,10 +33,10 @@ public class Settings { } } - public String getValue(String table, String key) throws SettingsException { + public static String getValue(String table, String key) throws SettingsException { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { // on Android >= 12, it always fails: - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { return provider.getValue(table, key); } catch (SettingsException e) { Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e); @@ -48,10 +46,10 @@ public class Settings { return execSettingsGet(table, key); } - public void putValue(String table, String key, String value) throws SettingsException { + public static void putValue(String table, String key, String value) throws SettingsException { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { // on Android >= 12, it always fails: - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { provider.putValue(table, key, value); } catch (SettingsException e) { Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e); @@ -61,10 +59,10 @@ public class Settings { execSettingsPut(table, key, value); } - public String getAndPutValue(String table, String key, String value) throws SettingsException { + public static String getAndPutValue(String table, String key, String value) throws SettingsException { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { // on Android >= 12, it always fails: - try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) { + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { String oldValue = provider.getValue(table, key); if (!value.equals(oldValue)) { provider.putValue(table, key, value); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 68f6817d..cb6863b6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -13,27 +13,30 @@ public final class ServiceManager { public static final String PACKAGE_NAME = "com.android.shell"; public static final int USER_ID = 0; - private final Method getServiceMethod; - - private WindowManager windowManager; - private DisplayManager displayManager; - private InputManager inputManager; - private PowerManager powerManager; - private StatusBarManager statusBarManager; - private ClipboardManager clipboardManager; - private ActivityManager activityManager; - - public ServiceManager() { + private static final Method GET_SERVICE_METHOD; + static { try { - getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); + GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); } catch (Exception e) { throw new AssertionError(e); } } - private IInterface getService(String service, String type) { + private static WindowManager windowManager; + private static DisplayManager displayManager; + private static InputManager inputManager; + private static PowerManager powerManager; + private static StatusBarManager statusBarManager; + private static ClipboardManager clipboardManager; + private static ActivityManager activityManager; + + private ServiceManager() { + /* not instantiable */ + } + + private static IInterface getService(String service, String type) { try { - IBinder binder = (IBinder) getServiceMethod.invoke(null, service); + IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service); Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class); return (IInterface) asInterfaceMethod.invoke(null, binder); } catch (Exception e) { @@ -41,14 +44,14 @@ public final class ServiceManager { } } - public WindowManager getWindowManager() { + public static WindowManager getWindowManager() { if (windowManager == null) { windowManager = new WindowManager(getService("window", "android.view.IWindowManager")); } return windowManager; } - public DisplayManager getDisplayManager() { + public static DisplayManager getDisplayManager() { if (displayManager == null) { try { Class clazz = Class.forName("android.hardware.display.DisplayManagerGlobal"); @@ -62,7 +65,7 @@ public final class ServiceManager { return displayManager; } - public InputManager getInputManager() { + public static InputManager getInputManager() { if (inputManager == null) { try { Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance"); @@ -75,21 +78,21 @@ public final class ServiceManager { return inputManager; } - public PowerManager getPowerManager() { + public static PowerManager getPowerManager() { if (powerManager == null) { powerManager = new PowerManager(getService("power", "android.os.IPowerManager")); } return powerManager; } - public StatusBarManager getStatusBarManager() { + public static StatusBarManager getStatusBarManager() { if (statusBarManager == null) { statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService")); } return statusBarManager; } - public ClipboardManager getClipboardManager() { + public static ClipboardManager getClipboardManager() { if (clipboardManager == null) { IInterface clipboard = getService("clipboard", "android.content.IClipboard"); if (clipboard == null) { @@ -103,7 +106,7 @@ public final class ServiceManager { return clipboardManager; } - public ActivityManager getActivityManager() { + public static ActivityManager getActivityManager() { if (activityManager == null) { try { // On old Android versions, the ActivityManager is not exposed via AIDL, From 1bfbadef9658c50d8d6206a2114785cdfa176bdf Mon Sep 17 00:00:00 2001 From: Anima C13 <31348553+animaone@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:23:59 -0300 Subject: [PATCH 0575/1133] Add -s auto-completion for bash Fixes #3522 PR #3523 Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 3e75cbb0..0d3a2559 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -93,6 +93,11 @@ _scrcpy() { COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur")) return ;; + -s|--serial) + # Use 'adb devices' to list serial numbers + COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur})) + return + ;; -b|--bitrate \ |--codec-options \ |--crop \ @@ -103,7 +108,6 @@ _scrcpy() { |-m|--max-size \ |-p|--port \ |--push-target \ - |-s|--serial \ |--tunnel-host \ |--tunnel-port \ |--v4l2-buffer \ From 16e2c1ce26d0bd73b914530263a46e3ea6c7ba6c Mon Sep 17 00:00:00 2001 From: Anima C13 <31348553+animaone@users.noreply.github.com> Date: Sat, 8 Oct 2022 13:27:29 -0300 Subject: [PATCH 0576/1133] Add -s auto-completion for zsh Fixes #3522 PR #3523 Signed-off-by: Romain Vimont --- app/data/zsh-completion/_scrcpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 097c80d6..56c13fd0 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -47,7 +47,7 @@ arguments=( '--record-format=[Force recording format]:format:(mp4 mkv)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' - {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]' + {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' {-S,--turn-screen-off}'[Turn the device screen off immediately]' {-t,--show-touches}'[Show physical touches]' From cb46e4a64ae33054a019c70479e2cf71e2a1970d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 19 Oct 2022 15:13:55 +0200 Subject: [PATCH 0577/1133] Add missing include for memmove() --- app/src/util/vector.h | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/util/vector.h b/app/src/util/vector.h index 2c03a430..0c6cab98 100644 --- a/app/src/util/vector.h +++ b/app/src/util/vector.h @@ -6,6 +6,7 @@ #include #include #include +#include // Adapted from vlc_vector: // From ffc7b9169303374f3bf3cae3ea3251111f9ab825 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 19 Oct 2022 15:14:56 +0200 Subject: [PATCH 0578/1133] Add missing include for strlen() --- app/src/util/thread.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/util/thread.c b/app/src/util/thread.c index 478867b6..f9687add 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -1,6 +1,7 @@ #include "thread.h" #include +#include #include #include "log.h" From b62424a98abe301a23525befe7d97cb2dd141ea5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 19 Oct 2022 15:17:43 +0200 Subject: [PATCH 0579/1133] Build log.c for test_cli On Windows, sc_log_windows_error() is called from net.c, so log.c must also be compiled. Fixes #3542 --- app/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/app/meson.build b/app/meson.build index fd5418e3..626e89f8 100644 --- a/app/meson.build +++ b/app/meson.build @@ -267,6 +267,7 @@ if get_option('buildtype') == 'debug' 'tests/test_cli.c', 'src/cli.c', 'src/options.c', + 'src/util/log.c', 'src/util/net.c', 'src/util/str.c', 'src/util/strbuf.c', From d71587e39ba0d0c1541629a2cc8dce443de6581d Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sat, 22 Oct 2022 21:32:12 +0800 Subject: [PATCH 0580/1133] Avoid string concatenation in crossfiles This feature is not supported on older meson versions: ERROR: Malformed value in cross file variable prebuilt_libusb. Refs PR #3546 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- cross_win32.txt | 2 +- cross_win64.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index d1d5d11b..1e1b5242 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' -prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32' +prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index cc9e4a0b..f952dec2 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1' prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' -prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64' +prebuilt_libusb = 'libusb-1.0.26/MinGW-x64' From 48bb6f2ea858a02e05900d9828c0089166de17df Mon Sep 17 00:00:00 2001 From: Yu-Chen Lin Date: Sun, 23 Oct 2022 14:15:25 +0800 Subject: [PATCH 0581/1133] Support wchar_t in argv for Windows PR #3547 Fixes #2932 Signed-off-by: Yu-Chen Lin Signed-off-by: Romain Vimont --- app/src/main.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index 3334cbf9..b3a468cc 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -4,6 +4,10 @@ #include #include #include +#ifdef _WIN32 +#include +#include "util/str.h" +#endif #ifdef HAVE_V4L2 # include #endif @@ -18,8 +22,8 @@ #include "version.h" int -main(int argc, char *argv[]) { -#ifdef __WINDOWS__ +main_scrcpy(int argc, char *argv[]) { +#ifdef _WIN32 // disable buffering, we want logs immediately // even line buffering (setvbuf() with mode _IOLBF) is not sufficient setbuf(stdout, NULL); @@ -80,3 +84,52 @@ main(int argc, char *argv[]) { return ret; } + +int +main(int argc, char *argv[]) { +#ifndef _WIN32 + return main_scrcpy(argc, argv); +#else + (void) argc; + (void) argv; + int wargc; + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); + if (!wargv) { + LOG_OOM(); + return SCRCPY_EXIT_FAILURE; + } + + char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8)); + if (!argv_utf8) { + LOG_OOM(); + LocalFree(wargv); + return SCRCPY_EXIT_FAILURE; + } + + argv_utf8[wargc] = NULL; + + for (int i = 0; i < wargc; ++i) { + argv_utf8[i] = sc_str_from_wchars(wargv[i]); + if (!argv_utf8[i]) { + LOG_OOM(); + for (int j = 0; j < i; ++j) { + free(argv_utf8[j]); + } + LocalFree(wargv); + free(argv_utf8); + return SCRCPY_EXIT_FAILURE; + } + } + + LocalFree(wargv); + + int ret = main_scrcpy(wargc, argv_utf8); + + for (int i = 0; i < wargc; ++i) { + free(argv_utf8[i]); + } + free(argv_utf8); + + return ret; +#endif +} From 597703b62e05faea3c556565e0add4cff6bb54e6 Mon Sep 17 00:00:00 2001 From: SeungHoon Han Date: Wed, 9 Nov 2022 13:42:56 +0900 Subject: [PATCH 0582/1133] Fix DisplayInfo parsing for Android Q The DisplayInfo dump format has slightly changed in AOSP: PR #3573 Ref #3416 Signed-off-by: Romain Vimont --- .../scrcpy/wrappers/DisplayManager.java | 2 +- .../genymobile/scrcpy/CommandParserTest.java | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index bf172126..17b9ae4d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -21,7 +21,7 @@ public final class DisplayManager { // public to call it from unit tests public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) { Pattern regex = Pattern.compile( - "^ mOverrideDisplayInfo=DisplayInfo\\{\".*?\", displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, " + "^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, " + "rotation ([0-9]+).*?, layerStack ([0-9]+)", Pattern.MULTILINE); Matcher m = regex.matcher(dumpsysDisplayOutput); diff --git a/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java index e6f57bcb..d74c5d77 100644 --- a/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.DisplayManager; import android.view.Display; + import org.junit.Assert; import org.junit.Test; @@ -183,4 +184,60 @@ public class CommandParserTest { Assert.assertEquals(1080, displayInfo.getSize().getWidth()); Assert.assertEquals(2280, displayInfo.getSize().getHeight()); } + + @Test + public void testParseDisplayInfoFromDumpsysDisplayAPI29WithNoFlags() { + /* @formatter:off */ + String partialOutput = "Logical Displays: size=2\n" + + " Display 0:\n" + + " mDisplayId=0\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mAllowedDisplayModes=[1]\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + + "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes [" + + "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + + "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + + "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + + "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + + "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes [" + + "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + + "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + + "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + + "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + + " Display 31:\n" + + " mDisplayId=31\n" + + " mLayerStack=31\n" + + " mHasContent=true\n" + + " mAllowedDisplayModes=[92]\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=PanelLayer-#main\n" + + " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId \"virtual:com.test.system,10040,PanelLayer-#main,0\", " + + "app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x 110, mode 92, defaultMode 92, modes [" + + "{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + + "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId \"virtual:com.test.system,10040,PanelLayer-#main,0\", " + + "app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x 110, mode 92, defaultMode 92, modes [" + + "{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + + "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"; + DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 31); + Assert.assertNotNull(displayInfo); + Assert.assertEquals(31, displayInfo.getDisplayId()); + Assert.assertEquals(0, displayInfo.getRotation()); + Assert.assertEquals(31, displayInfo.getLayerStack()); + Assert.assertEquals(0, displayInfo.getFlags()); + Assert.assertEquals(800, displayInfo.getSize().getWidth()); + Assert.assertEquals(110, displayInfo.getSize().getHeight()); + } } From c00a9ead5e383b00d6b36464c2b234909720f095 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Nov 2022 09:21:16 +0100 Subject: [PATCH 0583/1133] Always use --key=value in README Mandatory arguments may be passed in either of these two forms: 1. --key value 2. --key=value Optional argument may only be passed in the second form. For consistency, always document using --key=value. Refs f76fe2c0d4847d0e40d8708f97591abf3fa22ea5 --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 2ae50c4b..1fedde12 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ increase performance. To limit both the width and height to some value (e.g. 1024): ```bash -scrcpy --max-size 1024 +scrcpy --max-size=1024 scrcpy -m 1024 # short version ``` @@ -199,7 +199,7 @@ preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. The default bit-rate is 8 Mbps. To change the video bitrate (e.g. to 2 Mbps): ```bash -scrcpy --bit-rate 2M +scrcpy --bit-rate=2M scrcpy -b 2M # short version ``` @@ -208,7 +208,7 @@ scrcpy -b 2M # short version The capture frame rate can be limited: ```bash -scrcpy --max-fps 15 +scrcpy --max-fps=15 ``` This is officially supported since Android 10, but may work on earlier versions. @@ -229,7 +229,7 @@ The device screen may be cropped to mirror only part of the screen. This is useful, for example, to mirror only one eye of the Oculus Go: ```bash -scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) +scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0) ``` If `--max-size` is also specified, resizing is applied after cropping. @@ -258,14 +258,14 @@ Some devices have more than one encoder, and some of them may cause issues or crash. It is possible to select a different encoder: ```bash -scrcpy --encoder OMX.qcom.video.encoder.avc +scrcpy --encoder=OMX.qcom.video.encoder.avc ``` To list the available encoders, you can pass an invalid encoder name; the error will give the available encoders: ```bash -scrcpy --encoder _ +scrcpy --encoder=_ ``` ### Capture @@ -275,14 +275,14 @@ scrcpy --encoder _ It is possible to record the screen while mirroring: ```bash -scrcpy --record file.mp4 +scrcpy --record=file.mp4 scrcpy -r file.mkv ``` To disable mirroring while recording: ```bash -scrcpy --no-display --record file.mp4 +scrcpy --no-display --record=file.mp4 scrcpy -Nr file.mkv # interrupt recording with Ctrl+C ``` @@ -431,7 +431,7 @@ none found, try running `adb disconnect`, and then run those two commands again. It may be useful to decrease the bit-rate and the resolution: ```bash -scrcpy --bit-rate 2M --max-size 800 +scrcpy --bit-rate=2M --max-size=800 scrcpy -b2M -m800 # short version ``` @@ -443,7 +443,7 @@ scrcpy -b2M -m800 # short version If several devices are listed in `adb devices`, you can specify the _serial_: ```bash -scrcpy --serial 0123456789abcdef +scrcpy --serial=0123456789abcdef scrcpy -s 0123456789abcdef # short version ``` @@ -453,7 +453,7 @@ The serial may also be provided via the environment variable `ANDROID_SERIAL` If the device is connected over TCP/IP: ```bash -scrcpy --serial 192.168.0.1:5555 +scrcpy --serial=192.168.0.1:5555 scrcpy -s 192.168.0.1:5555 # short version ``` @@ -606,7 +606,7 @@ scrcpy --force-adb-forward Like for wireless connections, it may be useful to reduce quality: ``` -scrcpy -b2M -m800 --max-fps 15 +scrcpy -b2M -m800 --max-fps=15 ``` ### Window configuration @@ -616,7 +616,7 @@ scrcpy -b2M -m800 --max-fps 15 By default, the window title is the device model. It can be changed: ```bash -scrcpy --window-title 'My device' +scrcpy --window-title='My device' ``` #### Position and size @@ -624,7 +624,7 @@ scrcpy --window-title 'My device' The initial window position and size may be specified: ```bash -scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600 +scrcpy --window-x=100 --window-y=100 --window-width=800 --window-height=600 ``` #### Borderless @@ -659,7 +659,7 @@ Fullscreen can then be toggled dynamically with MOD+f. The window may be rotated: ```bash -scrcpy --rotation 1 +scrcpy --rotation=1 ``` Possible values: @@ -701,7 +701,7 @@ If several displays are available, it is possible to select the display to mirror: ```bash -scrcpy --display 1 +scrcpy --display=1 ``` The list of display ids can be retrieved by: From 6469b55861a820e7b7d897376acd3f1f4988e689 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Nov 2022 09:27:10 +0100 Subject: [PATCH 0584/1133] Fix CommandParserTest code style Make checkstyle happy. --- .../genymobile/scrcpy/CommandParserTest.java | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java index d74c5d77..de996a07 100644 --- a/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/CommandParserTest.java @@ -3,7 +3,6 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.DisplayManager; import android.view.Display; - import org.junit.Assert; import org.junit.Test; @@ -188,49 +187,49 @@ public class CommandParserTest { @Test public void testParseDisplayInfoFromDumpsysDisplayAPI29WithNoFlags() { /* @formatter:off */ - String partialOutput = "Logical Displays: size=2\n" + - " Display 0:\n" + - " mDisplayId=0\n" + - " mLayerStack=0\n" + - " mHasContent=true\n" + - " mAllowedDisplayModes=[1]\n" + - " mRequestedColorMode=0\n" + - " mDisplayOffset=(0, 0)\n" + - " mDisplayScalingDisabled=false\n" + - " mPrimaryDisplayDevice=Built-in Screen\n" + - " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + - "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes [" + - "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + - "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + - "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + - "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + - "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + - " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + - "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes [" + - "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + - "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + - "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + - "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + - "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + - " Display 31:\n" + - " mDisplayId=31\n" + - " mLayerStack=31\n" + - " mHasContent=true\n" + - " mAllowedDisplayModes=[92]\n" + - " mRequestedColorMode=0\n" + - " mDisplayOffset=(0, 0)\n" + - " mDisplayScalingDisabled=false\n" + - " mPrimaryDisplayDevice=PanelLayer-#main\n" + - " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId \"virtual:com.test.system,10040,PanelLayer-#main,0\", " + - "app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x 110, mode 92, defaultMode 92, modes [" + - "{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + - "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + - "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n" + - " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId \"virtual:com.test.system,10040,PanelLayer-#main,0\", " + - "app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x 110, mode 92, defaultMode 92, modes [" + - "{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + - "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + - "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"; + String partialOutput = "Logical Displays: size=2\n" + + " Display 0:\n" + + " mDisplayId=0\n" + + " mLayerStack=0\n" + + " mHasContent=true\n" + + " mAllowedDisplayModes=[1]\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=Built-in Screen\n" + + " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + + "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes [" + + "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + + "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + + "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + + "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, " + + "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes [" + + "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, " + + "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, " + + "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, " + + "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n" + + " Display 31:\n" + + " mDisplayId=31\n" + + " mLayerStack=31\n" + + " mHasContent=true\n" + + " mAllowedDisplayModes=[92]\n" + + " mRequestedColorMode=0\n" + + " mDisplayOffset=(0, 0)\n" + + " mDisplayScalingDisabled=false\n" + + " mPrimaryDisplayDevice=PanelLayer-#main\n" + + " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId " + + "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x " + + "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + + "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n" + + " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId " + + "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x " + + "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], " + + "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, " + + "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"; DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 31); Assert.assertNotNull(displayInfo); Assert.assertEquals(31, displayInfo.getDisplayId()); From bd1deffa70832a2ab30b8d060992ca70e3136ba8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Nov 2022 09:04:42 +0100 Subject: [PATCH 0585/1133] Use current adb port (if any) for --tcpip If the current adb port is not 5555 (typically 0 because it is not in TCP/IP mode), --tcpip automatically executes (among other commands): adb tcpip 5555 In case adb was already listening on another port, this command forced to listen on 5555, and the connection should still succeed. But this reconfiguration might be inconvenient for the user. If adb is already in TCP/IP mode, use the current enabled port without reconfiguration. Fixes #3591 --- README.md | 4 +-- app/scrcpy.1 | 2 +- app/src/server.c | 85 +++++++++++++++++++++++++--------------------- app/src/util/str.h | 4 +++ 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 20ad0f9c..87091696 100644 --- a/README.md +++ b/README.md @@ -395,8 +395,8 @@ address), connect the device over USB, then run: scrcpy --tcpip # without arguments ``` -It will automatically find the device IP address, enable TCP/IP mode, then -connect to the device before starting. +It will automatically find the device IP address and adb port, enable TCP/IP +mode if necessary, then connect to the device before starting. ##### Manual diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7cb893b7..852d9d03 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -275,7 +275,7 @@ Configure and reconnect the device over TCP/IP. If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). -If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting. +If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting. .TP .B \-S, \-\-turn\-screen\-off diff --git a/app/src/server.c b/app/src/server.c index 663ef18b..e5970487 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -19,6 +19,8 @@ #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" +#define SC_ADB_PORT_DEFAULT 5555 + static char * get_server_path(void) { #ifdef __WINDOWS__ @@ -513,27 +515,36 @@ sc_server_on_terminated(void *userdata) { LOGD("Server terminated"); } -static bool -is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { +static uint16_t +get_adb_tcp_port(struct sc_server *server, const char *serial) { struct sc_intr *intr = &server->intr; char *current_port = sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); if (!current_port) { - return false; + return 0; } - // Is the device is listening on TCP on port 5555? - bool enabled = !strcmp("5555", current_port); + long value; + bool ok = sc_str_parse_integer(current_port, &value); free(current_port); - return enabled; + if (!ok) { + return 0; + } + + if (value < 0 || value > 0xFFFF) { + return 0; + } + + return value; } static bool wait_tcpip_mode_enabled(struct sc_server *server, const char *serial, - unsigned attempts, sc_tick delay) { - if (is_tcpip_mode_enabled(server, serial)) { - LOGI("TCP/IP mode enabled"); + uint16_t expected_port, unsigned attempts, + sc_tick delay) { + uint16_t adb_port = get_adb_tcp_port(server, serial); + if (adb_port == expected_port) { return true; } @@ -547,28 +558,23 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial, return false; } - if (is_tcpip_mode_enabled(server, serial)) { - LOGI("TCP/IP mode enabled"); + adb_port = get_adb_tcp_port(server, serial); + if (adb_port == expected_port) { return true; } } while (--attempts); return false; } -char * -append_port_5555(const char *ip) { - size_t len = strlen(ip); - - // sizeof counts the final '\0' - char *ip_port = malloc(len + sizeof(":5555")); - if (!ip_port) { +static char * +append_port(const char *ip, uint16_t port) { + char *ip_port; + int ret = asprintf(&ip_port, "%s:%" PRIu16, ip, port); + if (ret == -1) { LOG_OOM(); return NULL; } - memcpy(ip_port, ip, len); - memcpy(ip_port + len, ":5555", sizeof(":5555")); - return ip_port; } @@ -586,34 +592,36 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) { return NULL; } - char *ip_port = append_port_5555(ip); - free(ip); - if (!ip_port) { - return NULL; - } - - bool tcp_mode = is_tcpip_mode_enabled(server, serial); + uint16_t adb_port = get_adb_tcp_port(server, serial); + if (adb_port) { + LOGI("TCP/IP mode already enabled on port %" PRIu16, adb_port); + } else { + LOGI("Enabling TCP/IP mode on port " SC_STR(SC_ADB_PORT_DEFAULT) "..."); - if (!tcp_mode) { - bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT); + bool ok = sc_adb_tcpip(intr, serial, SC_ADB_PORT_DEFAULT, + SC_ADB_NO_STDOUT); if (!ok) { LOGE("Could not restart adbd in TCP/IP mode"); - goto error; + free(ip); + return NULL; } unsigned attempts = 40; sc_tick delay = SC_TICK_FROM_MS(250); - ok = wait_tcpip_mode_enabled(server, serial, attempts, delay); + ok = wait_tcpip_mode_enabled(server, serial, SC_ADB_PORT_DEFAULT, + attempts, delay); if (!ok) { - goto error; + free(ip); + return NULL; } + + adb_port = SC_ADB_PORT_DEFAULT; + LOGI("TCP/IP mode enabled on port " SC_STR(SC_ADB_PORT_DEFAULT)); } + char *ip_port = append_port(ip, adb_port); + free(ip); return ip_port; - -error: - free(ip_port); - return NULL; } static bool @@ -640,7 +648,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server, const char *addr) { // Append ":5555" if no port is present bool contains_port = strchr(addr, ':'); - char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr); + char *ip_port = contains_port ? strdup(addr) + : append_port(addr, SC_ADB_PORT_DEFAULT); if (!ip_port) { LOG_OOM(); return false; diff --git a/app/src/util/str.h b/app/src/util/str.h index 1736bd95..4f7eeeda 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -6,6 +6,10 @@ #include #include +/* Stringify a numeric value */ +#define SC_STR(s) SC_XSTR(s) +#define SC_XSTR(s) #s + /** * Like strncpy(), except: * - it copies at most n-1 chars From b51841e85d22284eb5dda42096dc46197c710daf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 21 Dec 2022 13:28:08 +0100 Subject: [PATCH 0586/1133] Upgrade junit to 4.13.2 --- server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index 00590381..c239e6e5 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -19,7 +19,7 @@ android { } dependencies { - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.2' } apply from: "$project.rootDir/config/android-checkstyle.gradle" From 82cb8ab870140403f244fba80ddf2bc5b26b2d78 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 20 Dec 2022 10:38:39 +0100 Subject: [PATCH 0587/1133] Adapt ClipboardManager for Android 13 A new "attributionTag" parameter has been added to the methods getPrimaryClip(), setPrimaryClip() and addPrimaryClipChangedListener() of IClipboard.aidl. Refs Fixes #3497 --- .../scrcpy/wrappers/ClipboardManager.java | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index e25b6e99..f43a76bc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -15,6 +15,9 @@ public class ClipboardManager { private Method getPrimaryClipMethod; private Method setPrimaryClipMethod; private Method addPrimaryClipChangedListener; + private boolean alternativeGetMethod; + private boolean alternativeSetMethod; + private boolean alternativeAddListenerMethod; public ClipboardManager(IInterface manager) { this.manager = manager; @@ -25,7 +28,12 @@ public class ClipboardManager { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class); } else { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class); + try { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class); + } catch (NoSuchMethodException e) { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class); + alternativeGetMethod = true; + } } } return getPrimaryClipMethod; @@ -36,23 +44,34 @@ public class ClipboardManager { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class); } else { - setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class); + try { + setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class); + } catch (NoSuchMethodException e) { + setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class); + alternativeSetMethod = true; + } } } return setPrimaryClipMethod; } - private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException { + private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager) + throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME); } + if (alternativeMethod) { + return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); + } return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); } - private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) + private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME); + } else if (alternativeMethod) { + method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); } else { method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); } @@ -61,7 +80,7 @@ public class ClipboardManager { public CharSequence getText() { try { Method method = getGetPrimaryClipMethod(); - ClipData clipData = getPrimaryClip(method, manager); + ClipData clipData = getPrimaryClip(method, alternativeGetMethod, manager); if (clipData == null || clipData.getItemCount() == 0) { return null; } @@ -76,7 +95,7 @@ public class ClipboardManager { try { Method method = getSetPrimaryClipMethod(); ClipData clipData = ClipData.newPlainText(null, text); - setPrimaryClip(method, manager, clipData); + setPrimaryClip(method, alternativeSetMethod, manager, clipData); return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); @@ -84,10 +103,12 @@ public class ClipboardManager { } } - private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener) - throws InvocationTargetException, IllegalAccessException { + private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager, + IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, listener, ServiceManager.PACKAGE_NAME); + } else if (alternativeMethod) { + method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); } else { method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); } @@ -99,8 +120,14 @@ public class ClipboardManager { addPrimaryClipChangedListener = manager.getClass() .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class); } else { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); + try { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); + } catch (NoSuchMethodException e) { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, int.class); + alternativeAddListenerMethod = true; + } } } return addPrimaryClipChangedListener; @@ -109,7 +136,7 @@ public class ClipboardManager { public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { try { Method method = getAddPrimaryClipChangedListener(); - addPrimaryClipChangedListener(method, manager, listener); + addPrimaryClipChangedListener(method, alternativeAddListenerMethod, manager, listener); return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); From 64821466a1ddcdf7f0a50fa39067f066284ef9ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 21 Dec 2022 13:29:27 +0100 Subject: [PATCH 0588/1133] Use "meson setup" This fixes the following warning: > WARNING: Running the setup command as `meson [options]` instead of > `meson setup [options]` is ambiguous and deprecated. --- BUILD.md | 4 ++-- DEVELOP.md | 4 ++-- release.mk | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/BUILD.md b/BUILD.md index 1d5d970b..e3a2a56b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -260,7 +260,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk Then, build: ```bash -meson x --buildtype=release --strip -Db_lto=true +meson setup x --buildtype=release --strip -Db_lto=true ninja -Cx # DO NOT RUN AS ROOT ``` @@ -281,7 +281,7 @@ Download the prebuilt server somewhere, and specify its path during the Meson configuration: ```bash -meson x --buildtype=release --strip -Db_lto=true \ +meson setup x --buildtype=release --strip -Db_lto=true \ -Dprebuilt_server=/path/to/scrcpy-server ninja -Cx # DO NOT RUN AS ROOT ``` diff --git a/DEVELOP.md b/DEVELOP.md index d200c3fd..bd409fff 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -277,7 +277,7 @@ The server is pushed to the device by the client on startup. To debug it, enable the server debugger during configuration: ```bash -meson x -Dserver_debugger=true +meson setup x -Dserver_debugger=true # or, if x is already configured meson configure x -Dserver_debugger=true ``` @@ -286,7 +286,7 @@ If your device runs Android 8 or below, set the `server_debugger_method` to `old` in addition: ```bash -meson x -Dserver_debugger=true -Dserver_debugger_method=old +meson setup x -Dserver_debugger=true -Dserver_debugger_method=old # or, if x is already configured meson configure x -Dserver_debugger=true -Dserver_debugger_method=old ``` diff --git a/release.mk b/release.mk index 94a9680e..339c42cc 100644 --- a/release.mk +++ b/release.mk @@ -53,13 +53,13 @@ clean: test: [ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \ - meson "$(TEST_BUILD_DIR)" -Db_sanitize=address ) + meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address ) ninja -C "$(TEST_BUILD_DIR)" $(GRADLE) -p server check build-server: [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ - meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) + meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) ninja -C "$(SERVER_BUILD_DIR)" prepare-deps-win32: @@ -76,7 +76,7 @@ prepare-deps-win64: build-win32: prepare-deps-win32 [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ - meson "$(WIN32_BUILD_DIR)" \ + meson setup "$(WIN32_BUILD_DIR)" \ --cross-file cross_win32.txt \ --buildtype release --strip -Db_lto=true \ -Dcompile_server=false \ @@ -85,7 +85,7 @@ build-win32: prepare-deps-win32 build-win64: prepare-deps-win64 [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ - meson "$(WIN64_BUILD_DIR)" \ + meson setup "$(WIN64_BUILD_DIR)" \ --cross-file cross_win64.txt \ --buildtype release --strip -Db_lto=true \ -Dcompile_server=false \ From 8b38b11875eee81186adebe709ef0fcfd68d88ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 21 Dec 2022 13:31:18 +0100 Subject: [PATCH 0589/1133] Add parent directory in release zipfile This avoids to mistakenly extract all the files in the current directory. --- release.mk | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/release.mk b/release.mk index 339c42cc..20561d61 100644 --- a/release.mk +++ b/release.mk @@ -24,13 +24,13 @@ SERVER_BUILD_DIR := build-server WIN32_BUILD_DIR := build-win32 WIN64_BUILD_DIR := build-win64 -DIST := dist -WIN32_TARGET_DIR := scrcpy-win32 -WIN64_TARGET_DIR := scrcpy-win64 - VERSION := $(shell git describe --tags --always) -WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip -WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip + +DIST := dist +WIN32_TARGET_DIR := scrcpy-win32-$(VERSION) +WIN64_TARGET_DIR := scrcpy-win64-$(VERSION) +WIN32_TARGET := $(WIN32_TARGET_DIR).zip +WIN64_TARGET := $(WIN64_TARGET_DIR).zip RELEASE_DIR := release-$(VERSION) @@ -131,9 +131,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 - cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ - zip -r "../$(WIN32_TARGET)" . + cd "$(DIST)"; \ + zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)" zip-win64: dist-win64 - cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ - zip -r "../$(WIN64_TARGET)" . + cd "$(DIST)"; \ + zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)" From 18082f60697ae8edad2db950637638c8bb6bf0d2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 21 Dec 2022 22:06:43 +0100 Subject: [PATCH 0590/1133] Remove continuous resizing workaround for Windows It turns out that the workaround only worked for MacOS. Refs #3458 Refs SDL/#1059 --- app/src/screen.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index ae28e6e6..b2c17575 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -306,13 +306,14 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { } -#if defined(__APPLE__) || defined(__WINDOWS__) +#if defined(__APPLE__) # define CONTINUOUS_RESIZING_WORKAROUND #endif #ifdef CONTINUOUS_RESIZING_WORKAROUND // On Windows and MacOS, resizing blocks the event loop, so resizing events are -// not triggered. As a workaround, handle them in an event handler. +// not triggered. On MacOS, as a workaround, handle them in an event handler +// (it does not work for Windows unfortunately). // // // From c7b1d0ea9af8bb9603ec8f713f1a5fbf3f628b6a Mon Sep 17 00:00:00 2001 From: Pawel Jasinski Date: Tue, 8 Nov 2022 17:15:08 +0100 Subject: [PATCH 0591/1133] Force mouse source when --forward-all-clicks Right click and middle click require the source device to be a mouse, not a touchscreen. Therefore, the source device was changed only when a button other than the primary button was pressed (see adc547fa6e8e6167cd9633a97d98de6665b8c23a). However, this led to inconsistencies between the ACTION_DOWN when a secondary button is pressed (with a mouse as source device) and the matching ACTION_UP when the secondary button is released (with a touchscreen as source device, because then there is no button pressed). To avoid the problem in all cases, force a mouse as source device when --forward-all-clicks is set. Concretely, for mouse events in --forward-all-clicks mode: - device source is set to InputDevice.SOURCE_MOUSE; - motion event toolType is set to MotionEvent.TOOL_TYPE_MOUSE; Otherwise (when --forward-all-clicks is unset, or for real touch events), finger events are injected: - device source is set to InputDevice.SOURCE_TOUCHSCREEN; - motion event toolType is set to MotionEvent.TOOL_TYPE_FINGER. Fixes #3568 PR #3579 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/control_msg.c | 21 ++++++++++++++-- app/src/control_msg.h | 6 ++++- app/src/input_events.h | 2 ++ app/src/input_manager.c | 8 ++++++- app/src/mouse_inject.c | 4 ++-- .../com/genymobile/scrcpy/Controller.java | 24 ++++++++++++------- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index fce846ed..60bbd826 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -61,6 +61,22 @@ static const char *const copy_key_labels[] = { "cut", }; +static inline const char * +get_well_known_pointer_id_name(uint64_t pointer_id) { + switch (pointer_id) { + case POINTER_ID_MOUSE: + return "mouse"; + case POINTER_ID_GENERIC_FINGER: + return "finger"; + case POINTER_ID_VIRTUAL_MOUSE: + return "vmouse"; + case POINTER_ID_VIRTUAL_FINGER: + return "vfinger"; + default: + return NULL; + } +} + static void write_position(uint8_t *buf, const struct sc_position *position) { sc_write32be(&buf[0], position->point.x); @@ -159,11 +175,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) { int action = msg->inject_touch_event.action & AMOTION_EVENT_ACTION_MASK; uint64_t id = msg->inject_touch_event.pointer_id; - if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { + const char *pointer_name = get_well_known_pointer_id_name(id); + if (pointer_name) { // string pointer id LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 " pressure=%f buttons=%06lx", - id == POINTER_ID_MOUSE ? "mouse" : "vfinger", + pointer_name, MOTIONEVENT_ACTION_LABEL(action), msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.y, diff --git a/app/src/control_msg.h b/app/src/control_msg.h index f51bdecd..eb7e25b3 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -18,7 +18,11 @@ #define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14) #define POINTER_ID_MOUSE UINT64_C(-1) -#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) +#define POINTER_ID_GENERIC_FINGER UINT64_C(-2) + +// Used for injecting an additional virtual pointer for pinch-to-zoom +#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3) +#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4) enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, diff --git a/app/src/input_events.h b/app/src/input_events.h index 15d22910..5831ba0f 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -353,6 +353,7 @@ struct sc_mouse_click_event { struct sc_position position; enum sc_action action; enum sc_mouse_button button; + uint64_t pointer_id; uint8_t buttons_state; // bitwise-OR of sc_mouse_button values }; @@ -365,6 +366,7 @@ struct sc_mouse_scroll_event { struct sc_mouse_motion_event { struct sc_position position; + uint64_t pointer_id; int32_t xrel; int32_t yrel; uint8_t buttons_state; // bitwise-OR of sc_mouse_button values diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 42b49a13..ee95d00a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -335,7 +335,9 @@ simulate_virtual_finger(struct sc_input_manager *im, msg.inject_touch_event.action = action; msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.point = point; - msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER; + msg.inject_touch_event.pointer_id = + im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE + : POINTER_ID_VIRTUAL_FINGER; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.buttons = 0; @@ -564,6 +566,8 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im, event->x, event->y), }, + .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE + : POINTER_ID_GENERIC_FINGER, .xrel = event->xrel, .yrel = event->yrel, .buttons_state = @@ -687,6 +691,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, }, .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), + .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE + : POINTER_ID_GENERIC_FINGER, .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state, im->forward_all_clicks), diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index 2e89de9a..bca94637 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -69,7 +69,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = AMOTION_EVENT_ACTION_MOVE, - .pointer_id = POINTER_ID_MOUSE, + .pointer_id = event->pointer_id, .position = event->position, .pressure = 1.f, .buttons = convert_mouse_buttons(event->buttons_state), @@ -90,7 +90,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { .action = convert_mouse_action(event->action), - .pointer_id = POINTER_ID_MOUSE, + .pointer_id = event->pointer_id, .position = event->position, .pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f, .buttons = convert_mouse_buttons(event->buttons_state), diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 95b64711..a8219edd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -16,6 +16,10 @@ public class Controller { private static final int DEFAULT_DEVICE_ID = 0; + // control_msg.h values of the pointerId field in inject_touch_event message + private static final int POINTER_ID_MOUSE = -1; + private static final int POINTER_ID_VIRTUAL_MOUSE = -3; + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); private final Device device; @@ -194,7 +198,19 @@ public class Controller { pointer.setPressure(pressure); pointer.setUp(action == MotionEvent.ACTION_UP); + int source; int pointerCount = pointersState.update(pointerProperties, pointerCoords); + if (pointerId == POINTER_ID_MOUSE || pointerId == POINTER_ID_VIRTUAL_MOUSE) { + // real mouse event (forced by the client when --forward-on-click) + pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_MOUSE; + source = InputDevice.SOURCE_MOUSE; + } else { + // POINTER_ID_GENERIC_FINGER, POINTER_ID_VIRTUAL_FINGER or real touch from device + pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_FINGER; + source = InputDevice.SOURCE_TOUCHSCREEN; + // Buttons must not be set for touch events + buttons = 0; + } if (pointerCount == 1) { if (action == MotionEvent.ACTION_DOWN) { @@ -209,14 +225,6 @@ public class Controller { } } - // Right-click and middle-click only work if the source is a mouse - boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; - int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; - if (source != InputDevice.SOURCE_MOUSE) { - // Buttons must not be set for touch events - buttons = 0; - } - MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); From b5773a6fe8f6b6cc84594418fc64bcd40eba9b2f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 12:24:58 +0100 Subject: [PATCH 0592/1133] Upgrade platform-tools (33.0.3) for Windows Include the latest version of adb in Windows releases. --- app/prebuilt-deps/prepare-adb.sh | 6 +++--- release.mk | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh index 6a1f4896..0e4e498c 100755 --- a/app/prebuilt-deps/prepare-adb.sh +++ b/app/prebuilt-deps/prepare-adb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=platform-tools-33.0.1 +DEP_DIR=platform-tools-33.0.3 -FILENAME=platform-tools_r33.0.1-windows.zip -SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42 +FILENAME=platform-tools_r33.0.3-windows.zip +SHA256SUM=1e59afd40a74c5c0eab0a9fad3f0faf8a674267106e0b19921be9f67081808c2 if [[ -d "$DEP_DIR" ]] then diff --git a/release.mk b/release.mk index 20561d61..c55706ea 100644 --- a/release.mk +++ b/release.mk @@ -105,9 +105,9 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -124,9 +124,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 725a922271170706ef29c8883f179f71a89e21ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 12:28:21 +0100 Subject: [PATCH 0593/1133] Upgrade SDL (2.26.1) for Windows Include the latest version of SDL in Windows releases. --- app/prebuilt-deps/prepare-sdl.sh | 6 +++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index 0b41fe5c..644ed72d 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.0.22 +DEP_DIR=SDL2-2.26.1 -FILENAME=SDL2-devel-2.0.22-mingw.tar.gz -SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1 +FILENAME=SDL2-devel-2.26.1-mingw.tar.gz +SHA256SUM=aa43e1531a89551f9f9e14b27953a81d4ac946a9e574b5813cd0f2b36e83cc1c if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index 1e1b5242..32226949 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -20,6 +20,6 @@ ffmpeg_avcodec = 'avcodec-58' ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' -prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index f952dec2..4a020f51 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -20,6 +20,6 @@ ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1' -prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = 'libusb-1.0.26/MinGW-x64' diff --git a/release.mk b/release.mk index c55706ea..ab2cf301 100644 --- a/release.mk +++ b/release.mk @@ -108,7 +108,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.0.22/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.26.1/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 @@ -127,7 +127,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.0.22/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.26.1/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 From 8e0c89921856c495a102e30db27d52060aa176dd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 12:33:08 +0100 Subject: [PATCH 0594/1133] Upgrade FFmpeg (5.1.2) for Windows 64-bit Use the latest version of FFmpeg in Windows 64-bit releases. --- app/prebuilt-deps/prepare-ffmpeg-win64.sh | 4 ++-- cross_win64.txt | 2 +- release.mk | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg-win64.sh b/app/prebuilt-deps/prepare-ffmpeg-win64.sh index 8e6c2440..f5d56e6f 100755 --- a/app/prebuilt-deps/prepare-ffmpeg-win64.sh +++ b/app/prebuilt-deps/prepare-ffmpeg-win64.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=5.0.1 +VERSION=5.1.2 DEP_DIR=ffmpeg-win64-$VERSION FILENAME=ffmpeg-$VERSION-full_build-shared.7z -SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c +SHA256SUM=d9eb97b72d7cfdae4d0f7eaea59ccffb8c364d67d88018ea715d5e2e193f00e9 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win64.txt b/cross_win64.txt index 4a020f51..4dde4ab1 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -19,7 +19,7 @@ endian = 'little' ffmpeg_avcodec = 'avcodec-59' ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' -prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1' +prebuilt_ffmpeg = 'ffmpeg-win64-5.1.2' prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' prebuilt_libusb_root = 'libusb-1.0.26' prebuilt_libusb = 'libusb-1.0.26/MinGW-x64' diff --git a/release.mk b/release.mk index ab2cf301..06443e1a 100644 --- a/release.mk +++ b/release.mk @@ -119,11 +119,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.0.1/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From fe21158c2023017df39ee7ecc6462579a1f3fe45 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 12:33:29 +0100 Subject: [PATCH 0595/1133] Bump version to 1.25 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 6c731003..e20453c1 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "1.24" + VALUE "ProductVersion", "1.25" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index bfca6134..0d25085a 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.24', + version: '1.25', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index c239e6e5..44bd78e8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 33 - versionCode 12400 - versionName "1.24" + versionCode 12500 + versionName "1.25" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index f4fcba10..d2757d37 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.24 +SCRCPY_VERSION_NAME=1.25 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} From 4c43784fd1e4e0fad8e3816b240bda5694ea0717 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 12:44:01 +0100 Subject: [PATCH 0596/1133] Update links to v1.25 --- BUILD.md | 6 +++--- README.md | 8 ++++---- install_release.sh | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index e3a2a56b..0c708bde 100644 --- a/BUILD.md +++ b/BUILD.md @@ -272,10 +272,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.24`][direct-scrcpy-server] - SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056` + - [`scrcpy-server-v1.25`][direct-scrcpy-server] + SHA-256: `ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/README.md b/README.md index 87424360..b2a767fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.24) +# scrcpy (v1.25) scrcpy @@ -106,10 +106,10 @@ process][BUILD_simple]). For Windows, a prebuilt archive with all the dependencies (including `adb`) is available: - - [`scrcpy-win64-v1.24.zip`][direct-win64] - SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367` + - [`scrcpy-win64-v1.25.zip`][direct-win64] + SHA-256: `db65125e9c65acd00359efb7cea9c05f63cc7ccd5833000cd243cc92f5053028` -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-win64-v1.24.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-win64-v1.25.zip It is also available in [Chocolatey]: diff --git a/install_release.sh b/install_release.sh index 88262f8e..0ee80a72 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.24/scrcpy-server-v1.24 -PREBUILT_SERVER_SHA256=ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25 +PREBUILT_SERVER_SHA256=ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 54c7baceac11626618392f4312d592fd97238dff Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Dec 2022 13:07:07 +0100 Subject: [PATCH 0597/1133] Use "meson setup" from install_release.sh Refs 64821466a1ddcdf7f0a50fa39067f066284ef9ca --- install_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_release.sh b/install_release.sh index 0ee80a72..319aaa4f 100755 --- a/install_release.sh +++ b/install_release.sh @@ -12,7 +12,7 @@ echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check echo "[scrcpy] Building client..." rm -rf "$BUILDDIR" -meson "$BUILDDIR" --buildtype=release --strip -Db_lto=true \ +meson setup "$BUILDDIR" --buildtype=release --strip -Db_lto=true \ -Dprebuilt_server=scrcpy-server cd "$BUILDDIR" ninja From d8c2fe6ef2279769089fb617e93735217a8668b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Dec 2022 12:42:59 +0100 Subject: [PATCH 0598/1133] Revert "Remove continuous resizing workaround for Windows" This reverts commit 18082f60697ae8edad2db950637638c8bb6bf0d2. I can't reproduce, but it seems the workaround improves the behavior on some Windows versions. Fixes #3640 Refs #3458 --- app/src/screen.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index b2c17575..ae28e6e6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -306,14 +306,13 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { } -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__WINDOWS__) # define CONTINUOUS_RESIZING_WORKAROUND #endif #ifdef CONTINUOUS_RESIZING_WORKAROUND // On Windows and MacOS, resizing blocks the event loop, so resizing events are -// not triggered. On MacOS, as a workaround, handle them in an event handler -// (it does not work for Windows unfortunately). +// not triggered. As a workaround, handle them in an event handler. // // // From bf8696d02e2fdc81854909b43513981b20228f13 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 2 Jan 2023 15:55:46 +0100 Subject: [PATCH 0599/1133] Avoid unnecessary copy on config packets demuxing Use av_packet_ref() to reference the packet without copy. This also simplifies the logic, by making the "offset" variable and the memcpy() call local to the if-block. --- app/src/demuxer.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 2c0c64a8..c88af220 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -95,29 +95,27 @@ sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // A config packet must not be decoded immediately (it contains no // frame); instead, it must be concatenated with the future data packet. if (demuxer->pending || is_config) { - size_t offset; if (demuxer->pending) { - offset = demuxer->pending->size; + size_t offset = demuxer->pending->size; if (av_grow_packet(demuxer->pending, packet->size)) { LOG_OOM(); return false; } + + memcpy(demuxer->pending->data + offset, packet->data, packet->size); } else { - offset = 0; demuxer->pending = av_packet_alloc(); if (!demuxer->pending) { LOG_OOM(); return false; } - if (av_new_packet(demuxer->pending, packet->size)) { + if (av_packet_ref(demuxer->pending, packet)) { LOG_OOM(); av_packet_free(&demuxer->pending); return false; } } - memcpy(demuxer->pending->data + offset, packet->data, packet->size); - if (!is_config) { // prepare the concat packet to send to the decoder demuxer->pending->pts = packet->pts; From b3f626feee7efd34e1d7417a4b6fcfcdfd735d89 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 3 Jan 2023 08:45:29 +0100 Subject: [PATCH 0600/1133] Add FAQ section about HID/OTG on Windows Refs #3654 --- FAQ.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index e6c3c94d..76e418dc 100644 --- a/FAQ.md +++ b/FAQ.md @@ -7,7 +7,7 @@ Here are the common reported problems and their status. If you encounter any error, the first step is to upgrade to the latest version. -## `adb` issues +## `adb` and USB issues `scrcpy` execute `adb` commands to initialize the connection with the device. If `adb` fails, then scrcpy will not work. @@ -133,6 +133,21 @@ Try with another USB cable or plug it into another USB port. See [#281] and [#283]: https://github.com/Genymobile/scrcpy/issues/283 +## HID/OTG issues on Windows + +On Windows, if `scrcpy --otg` (or `--hid-keyboard`/`--hid-mouse`) results in: + +> ERROR: Could not find any USB device + +(or if only unrelated USB devices are detected), there might be drivers issues. + +Please read [#3654], in particular [this comment][#3654-comment1] and [the next +one][#3654-comment2]. + +[#3654]: https://github.com/Genymobile/scrcpy/issues/3654 +[#3654-comment1]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369278232 +[#3654-comment2]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369295011 + ## Control issues From 87da1372380ebddb60e4d89cff9a251c866e21c5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 18 Jan 2023 14:37:16 +0100 Subject: [PATCH 0601/1133] Remove "on Linux" in FAQ HID now works on all platforms. --- FAQ.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FAQ.md b/FAQ.md index 76e418dc..a6b106cc 100644 --- a/FAQ.md +++ b/FAQ.md @@ -168,8 +168,7 @@ 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). +Since scrcpy v1.20, it is possible to simulate a [physical keyboard][hid] (HID). [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters From e6cd42355b773bd0e9613ca9fb5f8364352ac38b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Jan 2023 10:40:48 +0100 Subject: [PATCH 0602/1133] Use separate gen dir to build without gradle The generated source files were written to the classes dir. Use a separate gen dir instead. --- server/build_without_gradle.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index d2757d37..47e65b6b 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -20,6 +20,7 @@ BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" CLASSES_DIR="$BUILD_DIR/classes" +GEN_DIR="$BUILD_DIR/gen" SERVER_DIR=$(dirname "$0") SERVER_BINARY=scrcpy-server ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" @@ -28,10 +29,11 @@ echo "Platform: android-$PLATFORM" echo "Build-tools: $BUILD_TOOLS" echo "Build dir: $BUILD_DIR" -rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex -mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy" +rm -rf "$CLASSES_DIR" "$GEN_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex +mkdir -p "$CLASSES_DIR" +mkdir -p "$GEN_DIR/com/genymobile/scrcpy" -<< EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java" +<< EOF cat > "$GEN_DIR/com/genymobile/scrcpy/BuildConfig.java" package com.genymobile.scrcpy; public final class BuildConfig { @@ -42,13 +44,13 @@ EOF echo "Generating java from aidl..." cd "$SERVER_DIR/src/main/aidl" -"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl -"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \ +"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl +"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \ android/content/IOnPrimaryClipChangedListener.aidl echo "Compiling java sources..." cd ../java -javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ +javac -bootclasspath "$ANDROID_JAR" -cp "$GEN_DIR" -d "$CLASSES_DIR" \ -source 1.8 -target 1.8 \ com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/wrappers/*.java @@ -68,7 +70,7 @@ then echo "Archiving..." cd "$BUILD_DIR" jar cvf "$SERVER_BINARY" classes.dex - rm -rf classes.dex classes + rm -rf classes.dex else # use d8 "$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \ @@ -80,7 +82,8 @@ else cd "$BUILD_DIR" mv classes.zip "$SERVER_BINARY" - rm -rf classes fi +rm -rf "$GEN_DIR" "$CLASSES_DIR" + echo "Server generated in $BUILD_DIR/$SERVER_BINARY" From 059ec45f8244ac3b01cf024c5182c77cb40c76d9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 21 Jan 2023 19:35:04 +0100 Subject: [PATCH 0603/1133] Add jrand48()/nrand48() compat functions These functions are not available on all platforms. --- app/meson.build | 2 ++ app/src/compat.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ app/src/compat.h | 8 ++++++++ 3 files changed, 54 insertions(+) diff --git a/app/meson.build b/app/meson.build index 626e89f8..cc465564 100644 --- a/app/meson.build +++ b/app/meson.build @@ -170,6 +170,8 @@ check_functions = [ 'strdup', 'asprintf', 'vasprintf', + 'nrand48', + 'jrand48', ] foreach f : check_functions diff --git a/app/src/compat.c b/app/src/compat.c index 11ddd3cb..bb0152aa 100644 --- a/app/src/compat.c +++ b/app/src/compat.c @@ -51,3 +51,47 @@ int vasprintf(char **strp, const char *fmt, va_list ap) { return len; } #endif + +#if !defined(HAVE_NRAND48) || !defined(HAVE_JRAND48) +#define SC_RAND48_MASK UINT64_C(0xFFFFFFFFFFFF) // 48 bits +#define SC_RAND48_A UINT64_C(0x5DEECE66D) +#define SC_RAND48_C 0xB +static inline uint64_t rand_iter48(uint64_t x) { + assert((x & ~SC_RAND48_MASK) == 0); + return (x * SC_RAND48_A + SC_RAND48_C) & SC_RAND48_MASK; +} + +static uint64_t rand_iter48_xsubi(unsigned short xsubi[3]) { + uint64_t x = ((uint64_t) xsubi[0] << 32) + | ((uint64_t) xsubi[1] << 16) + | xsubi[2]; + + x = rand_iter48(x); + + xsubi[0] = (x >> 32) & 0XFFFF; + xsubi[1] = (x >> 16) & 0XFFFF; + xsubi[2] = x & 0XFFFF; + + return x; +} + +#ifndef HAVE_NRAND48 +long nrand48(unsigned short xsubi[3]) { + // range [0, 2^31) + return rand_iter48_xsubi(xsubi) >> 17; +} +#endif + +#ifndef HAVE_JRAND48 +long jrand48(unsigned short xsubi[3]) { + // range [-2^31, 2^31) + union { + uint32_t u; + int32_t i; + } v; + v.u = rand_iter48_xsubi(xsubi) >> 16; + return v.i; +} +#endif + +#endif diff --git a/app/src/compat.h b/app/src/compat.h index 8265dbc8..857623e6 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -59,4 +59,12 @@ int asprintf(char **strp, const char *fmt, ...); int vasprintf(char **strp, const char *fmt, va_list ap); #endif +#ifndef HAVE_NRAND48 +long nrand48(unsigned short xsubi[3]); +#endif + +#ifndef HAVE_JRAND48 +long jrand48(unsigned short xsubi[3]); +#endif + #endif From 74e3f8b2531ee873c3555060d5b02791a325553a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 19:26:19 +0100 Subject: [PATCH 0604/1133] Add random util Add a user-friendly tool to generate random numbers. --- app/meson.build | 1 + app/src/util/rand.c | 24 ++++++++++++++++++++++++ app/src/util/rand.h | 16 ++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 app/src/util/rand.c create mode 100644 app/src/util/rand.h diff --git a/app/meson.build b/app/meson.build index cc465564..3ddd5a5d 100644 --- a/app/meson.build +++ b/app/meson.build @@ -37,6 +37,7 @@ src = [ 'src/util/net_intr.c', 'src/util/process.c', 'src/util/process_intr.c', + 'src/util/rand.c', 'src/util/strbuf.c', 'src/util/str.c', 'src/util/term.c', diff --git a/app/src/util/rand.c b/app/src/util/rand.c new file mode 100644 index 00000000..590e4ca4 --- /dev/null +++ b/app/src/util/rand.c @@ -0,0 +1,24 @@ +#include "rand.h" + +#include + +#include "tick.h" + +void sc_rand_init(struct sc_rand *rand) { + sc_tick seed = sc_tick_now(); // microsecond precision + rand->xsubi[0] = (seed >> 32) & 0xFFFF; + rand->xsubi[1] = (seed >> 16) & 0xFFFF; + rand->xsubi[2] = seed & 0xFFFF; +} + +uint32_t sc_rand_u32(struct sc_rand *rand) { + // jrand returns a value in range [-2^31, 2^31] + // conversion from signed to unsigned is well-defined to wrap-around + return jrand48(rand->xsubi); +} + +uint64_t sc_rand_u64(struct sc_rand *rand) { + uint32_t msb = sc_rand_u32(rand); + uint32_t lsb = sc_rand_u32(rand); + return ((uint64_t) msb << 32) | lsb; +} diff --git a/app/src/util/rand.h b/app/src/util/rand.h new file mode 100644 index 00000000..262b0b9b --- /dev/null +++ b/app/src/util/rand.h @@ -0,0 +1,16 @@ +#ifndef SC_RAND_H +#define SC_RAND_H + +#include "common.h" + +#include + +struct sc_rand { + unsigned short xsubi[3]; +}; + +void sc_rand_init(struct sc_rand *rand); +uint32_t sc_rand_u32(struct sc_rand *rand); +uint64_t sc_rand_u64(struct sc_rand *rand); + +#endif From 4315be164823d2c8fc44b475b52af79bfee98ff1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 21:48:54 +0100 Subject: [PATCH 0605/1133] Use random name for device socket For the initial connection between the device and the computer, an adb tunnel is established (with "adb reverse" or "adb forward"). The device-side of the tunnel is a local socket having the hard-coded name "scrcpy". This may cause issues when several scrcpy instances are started in a few seconds for the same device, since they will try to bind the same name. To avoid conflicts, make the client generate a random UID, and append this UID to the local socket name ("scrcpy_01234567"). --- app/src/adb/adb_tunnel.c | 24 ++++++++-------- app/src/adb/adb_tunnel.h | 6 ++-- app/src/scrcpy.c | 12 ++++++++ app/src/server.c | 28 +++++++++++++++---- app/src/server.h | 2 ++ .../genymobile/scrcpy/DesktopConnection.java | 21 ++++++++++---- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++ .../java/com/genymobile/scrcpy/Server.java | 10 ++++++- 8 files changed, 87 insertions(+), 25 deletions(-) diff --git a/app/src/adb/adb_tunnel.c b/app/src/adb/adb_tunnel.c index c613bc2b..fa936e4b 100644 --- a/app/src/adb/adb_tunnel.c +++ b/app/src/adb/adb_tunnel.c @@ -7,8 +7,6 @@ #include "util/net_intr.h" #include "util/process_intr.h" -#define SC_SOCKET_NAME "scrcpy" - static bool listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); @@ -17,10 +15,11 @@ listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { static bool enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, const char *serial, + const char *device_socket_name, struct sc_port_range port_range) { uint16_t port = port_range.first; for (;;) { - if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port, + if (!sc_adb_reverse(intr, serial, device_socket_name, port, SC_ADB_NO_STDOUT)) { // the command itself failed, it will fail on any port return false; @@ -52,7 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, } // failure, disable tunnel and try another port - if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + if (!sc_adb_reverse_remove(intr, serial, device_socket_name, SC_ADB_NO_STDOUT)) { LOGW("Could not remove reverse tunnel on port %" PRIu16, port); } @@ -78,12 +77,13 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, static bool enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, const char *serial, + const char *device_socket_name, struct sc_port_range port_range) { tunnel->forward = true; uint16_t port = port_range.first; for (;;) { - if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME, + if (sc_adb_forward(intr, serial, port, device_socket_name, SC_ADB_NO_STDOUT)) { // success tunnel->local_port = port; @@ -123,13 +123,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) { bool sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, - const char *serial, struct sc_port_range port_range, - bool force_adb_forward) { + const char *serial, const char *device_socket_name, + struct sc_port_range port_range, bool force_adb_forward) { assert(!tunnel->enabled); if (!force_adb_forward) { // Attempt to use "adb reverse" - if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) { + if (enable_tunnel_reverse_any_port(tunnel, intr, serial, + device_socket_name, port_range)) { return true; } @@ -139,12 +140,13 @@ sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, LOGW("'adb reverse' failed, fallback to 'adb forward'"); } - return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range); + return enable_tunnel_forward_any_port(tunnel, intr, serial, + device_socket_name, port_range); } bool sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, - const char *serial) { + const char *serial, const char *device_socket_name) { assert(tunnel->enabled); bool ret; @@ -152,7 +154,7 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, ret = sc_adb_forward_remove(intr, serial, tunnel->local_port, SC_ADB_NO_STDOUT); } else { - ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, + ret = sc_adb_reverse_remove(intr, serial, device_socket_name, SC_ADB_NO_STDOUT); assert(tunnel->server_socket != SC_SOCKET_NONE); diff --git a/app/src/adb/adb_tunnel.h b/app/src/adb/adb_tunnel.h index 12e3cf17..7ed5bf54 100644 --- a/app/src/adb/adb_tunnel.h +++ b/app/src/adb/adb_tunnel.h @@ -34,14 +34,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel); */ bool sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, - const char *serial, struct sc_port_range port_range, - bool force_adb_forward); + const char *serial, const char *device_socket_name, + struct sc_port_range port_range, bool force_adb_forward); /** * Close the tunnel */ bool sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, - const char *serial); + const char *serial, const char *device_socket_name); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3588e9ae..14688471 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -32,6 +32,7 @@ #include "util/acksync.h" #include "util/log.h" #include "util/net.h" +#include "util/rand.h" #ifdef HAVE_V4L2 # include "v4l2_sink.h" #endif @@ -265,6 +266,14 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) { // event } +static uint32_t +scrcpy_generate_uid() { + struct sc_rand rand; + sc_rand_init(&rand); + // Only use 31 bits to avoid issues with signed values on the Java-side + return sc_rand_u32(&rand) & 0x7FFFFFFF; +} + enum scrcpy_exit_code scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; @@ -298,7 +307,10 @@ scrcpy(struct scrcpy_options *options) { struct sc_acksync *acksync = NULL; + uint32_t uid = scrcpy_generate_uid(); + struct sc_server_params params = { + .uid = uid, .req_serial = options->serial, .select_usb = options->select_usb, .select_tcpip = options->select_tcpip, diff --git a/app/src/server.c b/app/src/server.c index e5970487..9384ce64 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -20,6 +20,7 @@ #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define SC_ADB_PORT_DEFAULT 5555 +#define SC_SOCKET_NAME_PREFIX "scrcpy_" static char * get_server_path(void) { @@ -197,6 +198,7 @@ execute_server(struct sc_server *server, cmd[count++] = p; \ } + ADD_PARAM("uid=%08x", params->uid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); @@ -364,6 +366,7 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, } server->serial = NULL; + server->device_socket_name = NULL; server->stopped = false; server->video_socket = SC_SOCKET_NONE; @@ -463,7 +466,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { } // we don't need the adb tunnel anymore - sc_adb_tunnel_close(tunnel, &server->intr, serial); + sc_adb_tunnel_close(tunnel, &server->intr, serial, + server->device_socket_name); // The sockets will be closed on stop if device_read_info() fails bool ok = device_read_info(&server->intr, video_socket, info); @@ -494,7 +498,8 @@ fail: if (tunnel->enabled) { // Always leave this function with tunnel disabled - sc_adb_tunnel_close(tunnel, &server->intr, serial); + sc_adb_tunnel_close(tunnel, &server->intr, serial, + server->device_socket_name); } return false; @@ -764,13 +769,23 @@ run_server(void *data) { assert(serial); LOGD("Device serial: %s", serial); + int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x", + params->uid); + if (r == -1) { + LOG_OOM(); + goto error_connection_failed; + } + assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8); + assert(server->device_socket_name); + ok = push_server(&server->intr, serial); if (!ok) { goto error_connection_failed; } ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial, - params->port_range, params->force_adb_forward); + server->device_socket_name, params->port_range, + params->force_adb_forward); if (!ok) { goto error_connection_failed; } @@ -778,7 +793,8 @@ run_server(void *data) { // server will connect to our server socket sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { - sc_adb_tunnel_close(&server->tunnel, &server->intr, serial); + sc_adb_tunnel_close(&server->tunnel, &server->intr, serial, + server->device_socket_name); goto error_connection_failed; } @@ -790,7 +806,8 @@ run_server(void *data) { if (!ok) { sc_process_terminate(pid); sc_process_wait(pid, true); // ignore exit code - sc_adb_tunnel_close(&server->tunnel, &server->intr, serial); + sc_adb_tunnel_close(&server->tunnel, &server->intr, serial, + server->device_socket_name); goto error_connection_failed; } @@ -884,6 +901,7 @@ sc_server_destroy(struct sc_server *server) { } free(server->serial); + free(server->device_socket_name); sc_server_params_destroy(&server->params); sc_intr_destroy(&server->intr); sc_cond_destroy(&server->cond_stopped); diff --git a/app/src/server.h b/app/src/server.h index 49ba83c1..e0f2c225 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -22,6 +22,7 @@ struct sc_server_info { }; struct sc_server_params { + uint32_t uid; const char *req_serial; enum sc_log_level log_level; const char *crop; @@ -54,6 +55,7 @@ struct sc_server { // The internal allocated strings are copies owned by the server struct sc_server_params params; char *serial; + char *device_socket_name; sc_thread thread; struct sc_server_info info; // initialized once connected diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 78728d81..54a40d22 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -15,7 +15,7 @@ public final class DesktopConnection implements Closeable { private static final int DEVICE_NAME_FIELD_LENGTH = 64; - private static final String SOCKET_NAME = "scrcpy"; + private static final String SOCKET_NAME_PREFIX = "scrcpy"; private final LocalSocket videoSocket; private final FileDescriptor videoFd; @@ -46,11 +46,22 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { + private static String getSocketName(int uid) { + if (uid == -1) { + // If no UID is set, use "scrcpy" to simplify using scrcpy-server alone + return SOCKET_NAME_PREFIX; + } + + return SOCKET_NAME_PREFIX + String.format("_%08x", uid); + } + + public static DesktopConnection open(int uid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { + String socketName = getSocketName(uid); + LocalSocket videoSocket; LocalSocket controlSocket = null; if (tunnelForward) { - LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME); + LocalServerSocket localServerSocket = new LocalServerSocket(socketName); try { videoSocket = localServerSocket.accept(); if (sendDummyByte) { @@ -69,10 +80,10 @@ public final class DesktopConnection implements Closeable { localServerSocket.close(); } } else { - videoSocket = connect(SOCKET_NAME); + videoSocket = connect(socketName); if (control) { try { - controlSocket = connect(SOCKET_NAME); + controlSocket = connect(socketName); } catch (IOException | RuntimeException e) { videoSocket.close(); throw e; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d1607c20..171d6661 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -6,6 +6,7 @@ import java.util.List; public class Options { private Ln.Level logLevel = Ln.Level.DEBUG; + private int uid = -1; // 31-bit non-negative value, or -1 private int maxSize; private int bitRate = 8000000; private int maxFps; @@ -37,6 +38,14 @@ public class Options { this.logLevel = logLevel; } + public int getUid() { + return uid; + } + + public void setUid(int uid) { + this.uid = uid; + } + public int getMaxSize() { return maxSize; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ec03515e..a8b948c6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -66,11 +66,12 @@ public final class Server { Thread initThread = startInitThread(options); + int uid = options.getUid(); boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); boolean sendDummyByte = options.getSendDummyByte(); - try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) { + try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, control, sendDummyByte)) { if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); @@ -178,6 +179,13 @@ public final class Server { String key = arg.substring(0, equalIndex); String value = arg.substring(equalIndex + 1); switch (key) { + case "uid": + int uid = Integer.parseInt(value, 0x10); + if (uid < -1) { + throw new IllegalArgumentException("uid may not be negative (except -1 for 'none'): " + uid); + } + options.setUid(uid); + break; case "log_level": Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); options.setLogLevel(level); From b22810b17c1319f02919af493aa174db5d2e174e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 21:59:26 +0100 Subject: [PATCH 0606/1133] Use try-with-resources Replace an explicit try-finally by a try-with-resources block. --- .../main/java/com/genymobile/scrcpy/DesktopConnection.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 54a40d22..7f287a6a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -61,8 +61,7 @@ public final class DesktopConnection implements Closeable { LocalSocket videoSocket; LocalSocket controlSocket = null; if (tunnelForward) { - LocalServerSocket localServerSocket = new LocalServerSocket(socketName); - try { + try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { videoSocket = localServerSocket.accept(); if (sendDummyByte) { // send one byte so the client may read() to detect a connection error @@ -76,8 +75,6 @@ public final class DesktopConnection implements Closeable { throw e; } } - } finally { - localServerSocket.close(); } } else { videoSocket = connect(socketName); From 8cbbcc939f95f4f5660503233b73f520b1fffdce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:07:57 +0100 Subject: [PATCH 0607/1133] Add missing final modifiers --- server/src/main/java/com/genymobile/scrcpy/CodecOption.java | 4 ++-- server/src/main/java/com/genymobile/scrcpy/Position.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java index 12f2a889..22c45a90 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecOption.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecOption.java @@ -4,8 +4,8 @@ import java.util.ArrayList; import java.util.List; public class CodecOption { - private String key; - private Object value; + private final String key; + private final Object value; public CodecOption(String key, Object value) { this.key = key; diff --git a/server/src/main/java/com/genymobile/scrcpy/Position.java b/server/src/main/java/com/genymobile/scrcpy/Position.java index e9b6d8a2..2d298645 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Position.java +++ b/server/src/main/java/com/genymobile/scrcpy/Position.java @@ -3,8 +3,8 @@ package com.genymobile.scrcpy; import java.util.Objects; public class Position { - private Point point; - private Size screenSize; + private final Point point; + private final Size screenSize; public Position(Point point, Size screenSize) { this.point = point; From 234ad7ee78e6b466757da4b57a5899d0f081affe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:08:21 +0100 Subject: [PATCH 0608/1133] Support Java lambdas in build_without_gradle.sh Building Java source code using lambdas requires core-lambda-stubs.jar. Refs #3657 --- server/build_without_gradle.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 47e65b6b..6677844c 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -24,6 +24,7 @@ GEN_DIR="$BUILD_DIR/gen" SERVER_DIR=$(dirname "$0") SERVER_BINARY=scrcpy-server ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" +LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar" echo "Platform: android-$PLATFORM" echo "Build-tools: $BUILD_TOOLS" @@ -50,7 +51,9 @@ cd "$SERVER_DIR/src/main/aidl" echo "Compiling java sources..." cd ../java -javac -bootclasspath "$ANDROID_JAR" -cp "$GEN_DIR" -d "$CLASSES_DIR" \ +javac -bootclasspath "$ANDROID_JAR" \ + -cp "$LAMBDA_JAR:$GEN_DIR" \ + -d "$CLASSES_DIR" \ -source 1.8 -target 1.8 \ com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/wrappers/*.java From bdba55411829ab84112f6b62f2a72907639afc98 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:16:36 +0100 Subject: [PATCH 0609/1133] Use Java lambdas where possible --- .../com/genymobile/scrcpy/Controller.java | 9 ++-- .../java/com/genymobile/scrcpy/Server.java | 53 ++++++------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index a8219edd..7663b1cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -258,12 +258,9 @@ public class Controller { * Schedule a call to set power mode to off after a small delay. */ private static void schedulePowerModeOff() { - EXECUTOR.schedule(new Runnable() { - @Override - public void run() { - Ln.i("Forcing screen off"); - Device.setScreenPowerMode(Device.POWER_MODE_OFF); - } + EXECUTOR.schedule(() -> { + Ln.i("Forcing screen off"); + Device.setScreenPowerMode(Device.POWER_MODE_OFF); }, 200, TimeUnit.MILLISECONDS); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a8b948c6..4a371e5b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -88,12 +88,7 @@ public final class Server { controllerThread = startController(controller); deviceMessageSenderThread = startDeviceMessageSender(controller.getSender()); - device.setClipboardListener(new Device.ClipboardListener() { - @Override - public void onClipboardTextChanged(String text) { - controller.getSender().pushClipboardText(text); - } - }); + device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); } try { @@ -115,26 +110,18 @@ public final class Server { } private static Thread startInitThread(final Options options) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - initAndCleanUp(options); - } - }); + Thread thread = new Thread(() -> initAndCleanUp(options)); thread.start(); return thread; } private static Thread startController(final Controller controller) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - controller.control(); - } catch (IOException e) { - // this is expected on close - Ln.d("Controller stopped"); - } + Thread thread = new Thread(() -> { + try { + controller.control(); + } catch (IOException e) { + // this is expected on close + Ln.d("Controller stopped"); } }); thread.start(); @@ -142,15 +129,12 @@ public final class Server { } private static Thread startDeviceMessageSender(final DeviceMessageSender sender) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - sender.loop(); - } catch (IOException | InterruptedException e) { - // this is expected on close - Ln.d("Device message sender stopped"); - } + Thread thread = new Thread(() -> { + try { + sender.loop(); + } catch (IOException | InterruptedException e) { + // this is expected on close + Ln.d("Device message sender stopped"); } }); thread.start(); @@ -327,12 +311,9 @@ public final class Server { } public static void main(String... args) throws Exception { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - Ln.e("Exception on thread " + t, e); - suggestFix(e); - } + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + Ln.e("Exception on thread " + t, e); + suggestFix(e); }); Options options = createOptions(args); From 74d32e612db1dfb26900d0a11a98f3a20a27ecfd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:18:54 +0100 Subject: [PATCH 0610/1133] Terminate loop explicitly on interrupted Make explicit that the loop terminates when the current thread is interrupted. --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- .../main/java/com/genymobile/scrcpy/DeviceMessageSender.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 7663b1cf..3dc609f1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -75,7 +75,7 @@ public class Controller { SystemClock.sleep(500); } - while (true) { + while (!Thread.currentThread().isInterrupted()) { handleEvent(); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 4ebccacc..2630652a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -25,7 +25,7 @@ public final class DeviceMessageSender { } public void loop() throws IOException, InterruptedException { - while (true) { + while (!Thread.currentThread().isInterrupted()) { String text; long sequence; synchronized (this) { From 75d7c01a0c6be9495edf05eb84d081c892bab414 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:26:01 +0100 Subject: [PATCH 0611/1133] Keep the same display binder across sessions Do not destroy/recreate the display when starting a new encoding session (on device rotation for example). --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index e95896d3..78a08da8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -76,12 +76,12 @@ public class ScreenEncoder implements Device.RotationListener { private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { MediaFormat format = createFormat(bitRate, maxFps, codecOptions); + IBinder display = createDisplay(); device.setRotationListener(this); boolean alive; try { do { MediaCodec codec = createCodec(encoderName); - IBinder display = createDisplay(); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); // include the locked video orientation @@ -120,7 +120,6 @@ public class ScreenEncoder implements Device.RotationListener { device.setMaxSize(newMaxSize); alive = true; } finally { - destroyDisplay(display); codec.release(); if (surface != null) { surface.release(); @@ -129,6 +128,7 @@ public class ScreenEncoder implements Device.RotationListener { } while (alive); } finally { device.setRotationListener(null); + destroyDisplay(display); } } From 91c69ad95c639f8c6b84ed71165323227d92e676 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:27:34 +0100 Subject: [PATCH 0612/1133] Remove useless destroyDisplay() method The method made exactly one simple call. Just make the call directly. --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 78a08da8..a63886e9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -128,7 +128,7 @@ public class ScreenEncoder implements Device.RotationListener { } while (alive); } finally { device.setRotationListener(null); - destroyDisplay(display); + SurfaceControl.destroyDisplay(display); } } @@ -297,8 +297,4 @@ public class ScreenEncoder implements Device.RotationListener { SurfaceControl.closeTransaction(); } } - - private static void destroyDisplay(IBinder display) { - SurfaceControl.destroyDisplay(display); - } } From 52f85fd6f150907818ccd1213719c1d21aa4684d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:31:09 +0100 Subject: [PATCH 0613/1133] Keep the same MediaCodec instance across sessions Calling codec.reset() is sufficient. --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index a63886e9..270751f3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -75,13 +75,13 @@ public class ScreenEncoder implements Device.RotationListener { } private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { + MediaCodec codec = createCodec(encoderName); MediaFormat format = createFormat(bitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); boolean alive; try { do { - MediaCodec codec = createCodec(encoderName); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); // include the locked video orientation @@ -120,13 +120,14 @@ public class ScreenEncoder implements Device.RotationListener { device.setMaxSize(newMaxSize); alive = true; } finally { - codec.release(); + codec.reset(); if (surface != null) { surface.release(); } } } while (alive); } finally { + codec.release(); device.setRotationListener(null); SurfaceControl.destroyDisplay(display); } From 6cccf3ab2a67e0353d746dc00e8adf0893c56e55 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:38:37 +0100 Subject: [PATCH 0614/1133] Remove useless configure() method Inline its single call. --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 270751f3..5688c385 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -94,7 +94,7 @@ public class ScreenEncoder implements Device.RotationListener { Surface surface = null; try { - configure(codec, format); + codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = codec.createInputSurface(); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); codec.start(); @@ -279,10 +279,6 @@ public class ScreenEncoder implements Device.RotationListener { return SurfaceControl.createDisplay("scrcpy", secure); } - private static void configure(MediaCodec codec, MediaFormat format) { - codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); - } - private static void setSize(MediaFormat format, int width, int height) { format.setInteger(MediaFormat.KEY_WIDTH, width); format.setInteger(MediaFormat.KEY_HEIGHT, height); From b53d2c66e0549d434b187f39f3a7d71e27e64a79 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:39:28 +0100 Subject: [PATCH 0615/1133] Remove useless setSize() method Inline its content. It makes the logic of internalStreamScreen() more readable (it reduces the number of indirections). --- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 5688c385..023b676f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -84,13 +84,16 @@ public class ScreenEncoder implements Device.RotationListener { do { ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); + // include the locked video orientation Rect videoRect = screenInfo.getVideoSize().toRect(); + format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width()); + format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height()); + // does not include the locked video orientation Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); int layerStack = device.getLayerStack(); - setSize(format, videoRect.width(), videoRect.height()); Surface surface = null; try { @@ -279,11 +282,6 @@ public class ScreenEncoder implements Device.RotationListener { return SurfaceControl.createDisplay("scrcpy", secure); } - private static void setSize(MediaFormat format, int width, int height) { - format.setInteger(MediaFormat.KEY_WIDTH, width); - format.setInteger(MediaFormat.KEY_HEIGHT, height); - } - private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { SurfaceControl.openTransaction(); try { From a9b2697f3e3d43f329d43ee091516fbd4270b136 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 22:42:37 +0100 Subject: [PATCH 0616/1133] Move local variables declarations This makes it clear that these local variables are only passed to setDisplaySurface(). --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 023b676f..37467937 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -90,16 +90,17 @@ public class ScreenEncoder implements Device.RotationListener { format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width()); format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height()); - // does not include the locked video orientation - Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); - int videoRotation = screenInfo.getVideoRotation(); - int layerStack = device.getLayerStack(); - Surface surface = null; try { codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = codec.createInputSurface(); + + // does not include the locked video orientation + Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); + int videoRotation = screenInfo.getVideoRotation(); + int layerStack = device.getLayerStack(); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + codec.start(); alive = encode(codec, fd); From a52053421abee7c96b1071a06cb68bbeac2fd464 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 27 Jan 2023 23:03:41 +0100 Subject: [PATCH 0617/1133] Extract retry handling Move the code to downscale and retry on error out of the catch-block. Refs 26b4104844fb9516be13ff1f2be34e2945090e79 --- .../com/genymobile/scrcpy/ScreenEncoder.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 37467937..a695f0db 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -108,20 +108,10 @@ public class ScreenEncoder implements Device.RotationListener { codec.stop(); } catch (IllegalStateException | IllegalArgumentException e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); - if (!downsizeOnError || firstFrameSent) { - // Fail immediately + if (!prepareRetry(device, screenInfo)) { throw e; } - - int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); - if (newMaxSize == 0) { - // Definitively fail - throw e; - } - - // Retry with a smaller device size - Ln.i("Retrying with -m" + newMaxSize + "..."); - device.setMaxSize(newMaxSize); + Ln.i("Retrying..."); alive = true; } finally { codec.reset(); @@ -137,6 +127,25 @@ public class ScreenEncoder implements Device.RotationListener { } } + private boolean prepareRetry(Device device, ScreenInfo screenInfo) { + if (!downsizeOnError || firstFrameSent) { + // Must fail immediately + return false; + } + + int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); + Ln.i("newMaxSize = " + newMaxSize); + if (newMaxSize == 0) { + // Must definitively fail + return false; + } + + // Retry with a smaller device size + Ln.i("Retrying with -m" + newMaxSize + "..."); + device.setMaxSize(newMaxSize); + return true; + } + private static int chooseMaxSizeFallback(Size failedSize) { int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight()); for (int value : MAX_SIZE_FALLBACK) { From 07806ba9159b1f4dbd2ede77b110e254af1fd7f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 29 Jan 2023 14:48:07 +0100 Subject: [PATCH 0618/1133] Retry on spurious error MediaCodec may fail spuriously, typically when stopping an encoding and starting a new one immediately (for example on device rotation). In that case, retry a few times, in many cases it should work. Refs #3693 --- .../com/genymobile/scrcpy/ScreenEncoder.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index a695f0db..f0384e2c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -9,6 +9,7 @@ import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.Build; import android.os.IBinder; +import android.os.SystemClock; import android.view.Surface; import java.io.FileDescriptor; @@ -27,6 +28,7 @@ public class ScreenEncoder implements Device.RotationListener { // Keep the values in descending order private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; + private static final int MAX_CONSECUTIVE_ERRORS = 3; private static final long PACKET_FLAG_CONFIG = 1L << 63; private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; @@ -43,6 +45,7 @@ public class ScreenEncoder implements Device.RotationListener { private long ptsOrigin; private boolean firstFrameSent; + private int consecutiveErrors; public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { @@ -128,11 +131,25 @@ public class ScreenEncoder implements Device.RotationListener { } private boolean prepareRetry(Device device, ScreenInfo screenInfo) { - if (!downsizeOnError || firstFrameSent) { + if (firstFrameSent) { + ++consecutiveErrors; + if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + // Definitively fail + return false; + } + + // Wait a bit to increase the probability that retrying will fix the problem + SystemClock.sleep(50); + return true; + } + + if (!downsizeOnError) { // Must fail immediately return false; } + // Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising) + int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); Ln.i("newMaxSize = " + newMaxSize); if (newMaxSize == 0) { @@ -181,6 +198,7 @@ public class ScreenEncoder implements Device.RotationListener { if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { // If this is not a config packet, then it contains a frame firstFrameSent = true; + consecutiveErrors = 0; } } } finally { From 0afef0c6349e3c79ebcdc24d955f61fdc27c42f4 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 29 Jan 2023 22:14:05 +0100 Subject: [PATCH 0619/1133] Forward action button to device On click event, only the whole buttons state was passed to the device. In addition, on ACTION_DOWN and ACTION_UP, pass the button associated to the action. Refs #3635 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/control_msg.c | 12 ++++++++---- app/src/control_msg.h | 1 + app/src/input_manager.c | 1 + app/src/mouse_inject.c | 1 + app/tests/test_control_msg_serialize.c | 6 ++++-- .../java/com/genymobile/scrcpy/ControlMessage.java | 9 ++++++++- .../com/genymobile/scrcpy/ControlMessageReader.java | 5 +++-- .../genymobile/scrcpy/ControlMessageReaderTest.java | 4 +++- 8 files changed, 29 insertions(+), 10 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 60bbd826..d4d6c62a 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -117,8 +117,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { uint16_t pressure = sc_float_to_u16fp(msg->inject_touch_event.pressure); sc_write16be(&buf[22], pressure); - sc_write32be(&buf[24], msg->inject_touch_event.buttons); - return 28; + sc_write32be(&buf[24], msg->inject_touch_event.action_button); + sc_write32be(&buf[28], msg->inject_touch_event.buttons); + return 32; case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: write_position(&buf[1], &msg->inject_scroll_event.position); int16_t hscroll = @@ -179,22 +180,25 @@ sc_control_msg_log(const struct sc_control_msg *msg) { if (pointer_name) { // string pointer id LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 - " pressure=%f buttons=%06lx", + " pressure=%f action_button=%06lx buttons=%06lx", pointer_name, MOTIONEVENT_ACTION_LABEL(action), msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.y, msg->inject_touch_event.pressure, + (long) msg->inject_touch_event.action_button, (long) msg->inject_touch_event.buttons); } else { // numeric pointer id LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" - PRIi32 " pressure=%f buttons=%06lx", + PRIi32 " pressure=%f action_button=%06lx" + " buttons=%06lx", id, MOTIONEVENT_ACTION_LABEL(action), msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.y, msg->inject_touch_event.pressure, + (long) msg->inject_touch_event.action_button, (long) msg->inject_touch_event.buttons); } break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index eb7e25b3..b90a00b3 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -65,6 +65,7 @@ struct sc_control_msg { } inject_text; struct { enum android_motionevent_action action; + enum android_motionevent_buttons action_button; enum android_motionevent_buttons buttons; uint64_t pointer_id; struct sc_position position; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index ee95d00a..c8098ee7 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -339,6 +339,7 @@ simulate_virtual_finger(struct sc_input_manager *im, im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE : POINTER_ID_VIRTUAL_FINGER; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; + msg.inject_touch_event.action_button = 0; msg.inject_touch_event.buttons = 0; if (!sc_controller_push_msg(im->controller, &msg)) { diff --git a/app/src/mouse_inject.c b/app/src/mouse_inject.c index bca94637..71b7a64d 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_inject.c @@ -93,6 +93,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, .pointer_id = event->pointer_id, .position = event->position, .pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f, + .action_button = convert_mouse_buttons(event->button), .buttons = convert_mouse_buttons(event->buttons_state), }, }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 87117e3a..b2eef49c 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -90,13 +90,14 @@ static void test_serialize_inject_touch_event(void) { }, }, .pressure = 1.0f, + .action_button = AMOTION_EVENT_BUTTON_PRIMARY, .buttons = AMOTION_EVENT_BUTTON_PRIMARY, }, }; unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); - assert(size == 28); + assert(size == 32); const unsigned char expected[] = { SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, @@ -105,7 +106,8 @@ static void test_serialize_inject_touch_event(void) { 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200 0x04, 0x38, 0x07, 0x80, // 1080 1920 0xff, 0xff, // pressure - 0x00, 0x00, 0x00, 0x01 // AMOTION_EVENT_BUTTON_PRIMARY + 0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY (action button) + 0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY (buttons) }; assert(!memcmp(buf, expected, sizeof(expected))); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 0b05b22a..e1800374 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -29,6 +29,7 @@ public final class ControlMessage { private int metaState; // KeyEvent.META_* private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* private int keycode; // KeyEvent.KEYCODE_* + private int actionButton; // MotionEvent.BUTTON_* private int buttons; // MotionEvent.BUTTON_* private long pointerId; private float pressure; @@ -60,13 +61,15 @@ public final class ControlMessage { return msg; } - public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int buttons) { + public static ControlMessage createInjectTouchEvent(int action, long pointerId, Position position, float pressure, int actionButton, + int buttons) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_INJECT_TOUCH_EVENT; msg.action = action; msg.pointerId = pointerId; msg.pressure = pressure; msg.position = position; + msg.actionButton = actionButton; msg.buttons = buttons; return msg; } @@ -140,6 +143,10 @@ public final class ControlMessage { return keycode; } + public int getActionButton() { + return actionButton; + } + public int getButtons() { return buttons; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index a52fd134..d95c36d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -9,7 +9,7 @@ import java.nio.charset.StandardCharsets; public class ControlMessageReader { static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; - static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; + static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 31; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; @@ -140,8 +140,9 @@ public class ControlMessageReader { long pointerId = buffer.getLong(); Position position = readPosition(buffer); float pressure = Binary.u16FixedPointToFloat(buffer.getShort()); + int actionButton = buffer.getInt(); int buttons = buffer.getInt(); - return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons); + return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, actionButton, buttons); } private ControlMessage parseInjectScrollEvent() { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 1fc009ce..8405905a 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -94,7 +94,8 @@ public class ControlMessageReaderTest { dos.writeShort(1080); dos.writeShort(1920); dos.writeShort(0xffff); // pressure - dos.writeInt(MotionEvent.BUTTON_PRIMARY); + dos.writeInt(MotionEvent.BUTTON_PRIMARY); // action button + dos.writeInt(MotionEvent.BUTTON_PRIMARY); // buttons byte[] packet = bos.toByteArray(); @@ -112,6 +113,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1f, event.getPressure(), 0f); // must be exact + Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getActionButton()); Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getButtons()); } From 8c5c55f9e193be8a195b885799ba84377c413cc5 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 29 Jan 2023 22:23:11 +0100 Subject: [PATCH 0620/1133] Fix mouse pointer state update If the pointer is a mouse, the pointer is UP only when no buttons are pressed (not when a button is released, because there might be other buttons still pressed). Refs #3635 Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 3dc609f1..bff01d9f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -196,22 +196,23 @@ public class Controller { Pointer pointer = pointersState.get(pointerIndex); pointer.setPoint(point); pointer.setPressure(pressure); - pointer.setUp(action == MotionEvent.ACTION_UP); int source; - int pointerCount = pointersState.update(pointerProperties, pointerCoords); if (pointerId == POINTER_ID_MOUSE || pointerId == POINTER_ID_VIRTUAL_MOUSE) { // real mouse event (forced by the client when --forward-on-click) pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_MOUSE; source = InputDevice.SOURCE_MOUSE; + pointer.setUp(buttons == 0); } else { // POINTER_ID_GENERIC_FINGER, POINTER_ID_VIRTUAL_FINGER or real touch from device pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_FINGER; source = InputDevice.SOURCE_TOUCHSCREEN; // Buttons must not be set for touch events buttons = 0; + pointer.setUp(action == MotionEvent.ACTION_UP); } + int pointerCount = pointersState.update(pointerProperties, pointerCoords); if (pointerCount == 1) { if (action == MotionEvent.ACTION_DOWN) { lastTouchDown = now; From 9b286ec8a7d014c2a91bbc6d24343f9496724521 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 29 Jan 2023 23:08:22 +0100 Subject: [PATCH 0621/1133] Inject additional ACTION_BUTTON_* events for mouse On mouse click events: - the first button pressed must first generate ACTION_DOWN; - all button pressed (including the first one) must generate ACTION_BUTTON_PRESS; - all button released (including the last one) must generate ACTION_BUTTON_RELEASE; - the last button released must in addition generate ACTION_UP. Otherwise, Chrome does not work properly. Fixes #3635 Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/Controller.java | 62 ++++++++++++++++++- .../scrcpy/wrappers/InputManager.java | 20 ++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index bff01d9f..b0b0c787 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -1,5 +1,7 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.InputManager; + import android.os.Build; import android.os.SystemClock; import android.view.InputDevice; @@ -99,7 +101,7 @@ public class Controller { break; case ControlMessage.TYPE_INJECT_TOUCH_EVENT: if (device.supportsInputEvents()) { - injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons()); + injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getActionButton(), msg.getButtons()); } break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: @@ -179,7 +181,7 @@ public class Controller { return successCount; } - private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) { + private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) { long now = SystemClock.uptimeMillis(); Point point = device.getPhysicalPoint(position); @@ -226,6 +228,62 @@ public class Controller { } } + /* If the input device is a mouse (on API >= 23): + * - the first button pressed must first generate ACTION_DOWN; + * - all button pressed (including the first one) must generate ACTION_BUTTON_PRESS; + * - all button released (including the last one) must generate ACTION_BUTTON_RELEASE; + * - the last button released must in addition generate ACTION_UP. + * + * Otherwise, Chrome does not work properly: + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && source == InputDevice.SOURCE_MOUSE) { + if (action == MotionEvent.ACTION_DOWN) { + if (actionButton == buttons) { + // First button pressed: ACTION_DOWN + MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!device.injectEvent(downEvent, Device.INJECT_MODE_ASYNC)) { + return false; + } + } + + // Any button pressed: ACTION_BUTTON_PRESS + MotionEvent pressEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_PRESS, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!InputManager.setActionButton(pressEvent, actionButton)) { + return false; + } + if (!device.injectEvent(pressEvent, Device.INJECT_MODE_ASYNC)) { + return false; + } + + return true; + } + + if (action == MotionEvent.ACTION_UP) { + // Any button released: ACTION_BUTTON_RELEASE + MotionEvent releaseEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_RELEASE, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!InputManager.setActionButton(releaseEvent, actionButton)) { + return false; + } + if (!device.injectEvent(releaseEvent, Device.INJECT_MODE_ASYNC)) { + return false; + } + + if (buttons == 0) { + // Last button released: ACTION_UP + MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties, + pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); + if (!device.injectEvent(upEvent, Device.INJECT_MODE_ASYNC)) { + return false; + } + } + + return true; + } + } + MotionEvent event = MotionEvent .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 38e96d45..32bf4252 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; import android.view.InputEvent; +import android.view.MotionEvent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -17,6 +18,7 @@ public final class InputManager { private Method injectInputEventMethod; private static Method setDisplayIdMethod; + private static Method setActionButtonMethod; public InputManager(android.hardware.input.InputManager manager) { this.manager = manager; @@ -56,4 +58,22 @@ public final class InputManager { return false; } } + + private static Method getSetActionButtonMethod() throws NoSuchMethodException { + if (setActionButtonMethod == null) { + setActionButtonMethod = MotionEvent.class.getMethod("setActionButton", int.class); + } + return setActionButtonMethod; + } + + public static boolean setActionButton(MotionEvent motionEvent, int actionButton) { + try { + Method method = getSetActionButtonMethod(); + method.invoke(motionEvent, actionButton); + return true; + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Cannot set action button on MotionEvent", e); + return false; + } + } } From 6a07e3d470d9b849676a54406fceb44554b68557 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Feb 2023 12:30:00 +0100 Subject: [PATCH 0622/1133] Fix manpage formatting Only the option arguments must be underlined. --- app/scrcpy.1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 852d9d03..a828a239 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -26,7 +26,7 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 8000000. .TP -.BI "\-\-codec\-options " key[:type]=value[,...] +.BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] Set a list of comma-separated key:type=value options for the device encoder. The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. @@ -117,7 +117,7 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. .TP -.BI "\-\-lock\-video\-orientation[=value] +\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR] Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise. Default is "unlocked". @@ -199,7 +199,7 @@ It may only work over USB. See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR. .TP -.BI "\-p, \-\-port " port[:port] +.BI "\-p, \-\-port " port\fR[:\fIport\fR] Set the TCP port (range) used by the client to listen. Default is 27183:27199. @@ -260,7 +260,7 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre The device serial number. Mandatory only if several devices are connected to adb. .TP -.BI "\-\-shortcut\-mod " key[+...]][,...] +.BI "\-\-shortcut\-mod " key\fR[+...]][,...] Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','. @@ -270,7 +270,7 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr Default is "lalt,lsuper" (left-Alt or left-Super). .TP -.BI "\-\-tcpip[=ip[:port]] +.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]] Configure and reconnect the device over TCP/IP. If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). From 4177de5880b4f93c03ce30b754b22c57d245ec3a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Feb 2023 22:46:22 +0100 Subject: [PATCH 0623/1133] Do not expose controller threads The way the controller executes its events asynchronously is an implementation detail. --- .../com/genymobile/scrcpy/Controller.java | 25 +++++++++- .../scrcpy/DeviceMessageSender.java | 22 ++++++++- .../java/com/genymobile/scrcpy/Server.java | 46 +++---------------- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index b0b0c787..6147e36a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -24,6 +24,8 @@ public class Controller { private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); + private Thread thread; + private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; @@ -62,7 +64,7 @@ public class Controller { } } - public void control() throws IOException { + private void control() throws IOException { // on start, power on the device if (powerOn && !Device.isScreenOn()) { device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); @@ -82,6 +84,27 @@ public class Controller { } } + public void start() { + thread = new Thread(() -> { + try { + control(); + } catch (IOException e) { + // this is expected on close + Ln.d("Controller stopped"); + } + }); + thread.start(); + sender.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + thread = null; + } + sender.stop(); + } + public DeviceMessageSender getSender() { return sender; } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 2630652a..7ec4ab41 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -6,6 +6,8 @@ public final class DeviceMessageSender { private final DesktopConnection connection; + private Thread thread; + private String clipboardText; private long ack; @@ -24,7 +26,7 @@ public final class DeviceMessageSender { notify(); } - public void loop() throws IOException, InterruptedException { + private void loop() throws IOException, InterruptedException { while (!Thread.currentThread().isInterrupted()) { String text; long sequence; @@ -49,4 +51,22 @@ public final class DeviceMessageSender { } } } + public void start() { + thread = new Thread(() -> { + try { + loop(); + } catch (IOException | InterruptedException e) { + // this is expected on close + Ln.d("Device message sender stopped"); + } + }); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + thread = null; + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4a371e5b..dc14d7c9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -79,16 +79,13 @@ public final class Server { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); - Thread controllerThread = null; - Thread deviceMessageSenderThread = null; + Controller controller = null; if (control) { - final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); + controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); + controller.start(); - // asynchronous - controllerThread = startController(controller); - deviceMessageSenderThread = startDeviceMessageSender(controller.getSender()); - - device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); + final Controller controllerRef = controller; + device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text)); } try { @@ -99,11 +96,8 @@ public final class Server { Ln.d("Screen streaming stopped"); } finally { initThread.interrupt(); - if (controllerThread != null) { - controllerThread.interrupt(); - } - if (deviceMessageSenderThread != null) { - deviceMessageSenderThread.interrupt(); + if (controller != null) { + controller.stop(); } } } @@ -115,32 +109,6 @@ public final class Server { return thread; } - private static Thread startController(final Controller controller) { - Thread thread = new Thread(() -> { - try { - controller.control(); - } catch (IOException e) { - // this is expected on close - Ln.d("Controller stopped"); - } - }); - thread.start(); - return thread; - } - - private static Thread startDeviceMessageSender(final DeviceMessageSender sender) { - Thread thread = new Thread(() -> { - try { - sender.loop(); - } catch (IOException | InterruptedException e) { - // this is expected on close - Ln.d("Device message sender stopped"); - } - }); - thread.start(); - return thread; - } - private static Options createOptions(String... args) { if (args.length < 1) { throw new IllegalArgumentException("Missing client version"); From bdbf1f4eb7dac0f00c5419a99f23cd9f05e39871 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Jan 2023 22:54:34 +0100 Subject: [PATCH 0624/1133] Move Workarounds call Workarounds are not specific to the screen encoder. Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 11 ----------- .../src/main/java/com/genymobile/scrcpy/Server.java | 7 +++++++ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f0384e2c..c7f886b4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -67,17 +67,6 @@ public class ScreenEncoder implements Device.RotationListener { } public void streamScreen(Device device, FileDescriptor fd) throws IOException { - Workarounds.prepareMainLooper(); - if (Build.BRAND.equalsIgnoreCase("meizu")) { - // - // - Workarounds.fillAppInfo(); - } - - internalStreamScreen(device, fd); - } - - private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { MediaCodec codec = createCodec(encoderName); MediaFormat format = createFormat(bitRate, maxFps, codecOptions); IBinder display = createDisplay(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index dc14d7c9..06281223 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -71,6 +71,13 @@ public final class Server { boolean control = options.getControl(); boolean sendDummyByte = options.getSendDummyByte(); + Workarounds.prepareMainLooper(); + if (Build.BRAND.equalsIgnoreCase("meizu")) { + // + // + Workarounds.fillAppInfo(); + } + try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, control, sendDummyByte)) { if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); From 36d656e91f11718dfe43aceaa27f6a7439e93b7e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Feb 2023 19:08:43 +0100 Subject: [PATCH 0625/1133] Improve workarounds call comments --- server/src/main/java/com/genymobile/scrcpy/Server.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 06281223..a4f17262 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -73,8 +73,14 @@ public final class Server { Workarounds.prepareMainLooper(); if (Build.BRAND.equalsIgnoreCase("meizu")) { - // - // + // Workarounds must be applied for Meizu phones: + // - + // - + // - + // + // But only apply when strictly necessary, since workarounds can cause other issues: + // - + // - Workarounds.fillAppInfo(); } From 1c82c3923d63985655686dd5884a7a9e9407619e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Feb 2023 21:56:43 +0100 Subject: [PATCH 0626/1133] Compute relative PTS on the client-side The PTS received from MediaCodec are expressed relative to an arbitrary clock origin. We consider the PTS of the first frame to be 0, and the PTS of every other frame is relative to this first PTS (note that the PTS is only used for recording, it is ignored for mirroring). For simplicity, this relative PTS was computed on the server-side. To prepare support for multiple stream (video and audio), send the packet with its original PTS, and handle the PTS offset on the client-side (by the recorder). Since we can't know in advance which stream will produce the first packet with the lowest PTS (a packet received later on one stream may have a PTS lower than a packet received earlier on another stream), computing the PTS on the server-side would require unnecessary waiting. --- app/src/recorder.c | 15 +++++++++++++++ app/src/recorder.h | 2 ++ .../java/com/genymobile/scrcpy/ScreenEncoder.java | 6 +----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index b14b6050..455e1db1 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -11,6 +11,8 @@ /** Downcast packet_sink to recorder */ #define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink) +#define SC_PTS_ORIGIN_NONE UINT64_C(-1) + static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVOutputFormat * @@ -169,6 +171,18 @@ run_recorder(void *data) { sc_mutex_unlock(&recorder->mutex); + if (recorder->pts_origin == SC_PTS_ORIGIN_NONE + && rec->packet->pts != AV_NOPTS_VALUE) { + // First PTS received + recorder->pts_origin = rec->packet->pts; + } + + if (rec->packet->pts != AV_NOPTS_VALUE) { + // Set PTS relatve to the origin + rec->packet->pts -= recorder->pts_origin; + rec->packet->dts = rec->packet->pts; + } + // recorder->previous is only written from this thread, no need to lock struct sc_record_packet *previous = recorder->previous; recorder->previous = rec; @@ -243,6 +257,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { recorder->failed = false; recorder->header_written = false; recorder->previous = NULL; + recorder->pts_origin = SC_PTS_ORIGIN_NONE; const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); diff --git a/app/src/recorder.h b/app/src/recorder.h index 373278e6..a03c91d7 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -28,6 +28,8 @@ struct sc_recorder { struct sc_size declared_frame_size; bool header_written; + uint64_t pts_origin; + sc_thread thread; sc_mutex mutex; sc_cond queue_cond; diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index c7f886b4..07ea2d80 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -42,7 +42,6 @@ public class ScreenEncoder implements Device.RotationListener { private final int maxFps; private final boolean sendFrameMeta; private final boolean downsizeOnError; - private long ptsOrigin; private boolean firstFrameSent; private int consecutiveErrors; @@ -207,10 +206,7 @@ public class ScreenEncoder implements Device.RotationListener { if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { pts = PACKET_FLAG_CONFIG; // non-media data packet } else { - if (ptsOrigin == 0) { - ptsOrigin = bufferInfo.presentationTimeUs; - } - pts = bufferInfo.presentationTimeUs - ptsOrigin; + pts = bufferInfo.presentationTimeUs; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { pts |= PACKET_FLAG_KEY_FRAME; } From 3aac74e9e945c7fc71c5e54b3c53afe666707af7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Feb 2023 19:16:23 +0100 Subject: [PATCH 0627/1133] Move variable assignment Computing eof flag is not necessary if rotation changed. --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 07ea2d80..95f89ed5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -169,12 +169,13 @@ public class ScreenEncoder implements Device.RotationListener { while (!consumeRotationChange() && !eof) { int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); - eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; try { if (consumeRotationChange()) { // must restart encoding with new size break; } + + eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; if (outputBufferId >= 0) { ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); From 87972e2022686b1176bfaf0c678e703856c2b027 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Feb 2023 21:08:43 +0100 Subject: [PATCH 0628/1133] Extract video streaming to a separate class ScreenEncoder handled both capture/encoding and sending over the network. Move the streaming part to a separate VideoStreamer. --- .../com/genymobile/scrcpy/ScreenEncoder.java | 49 +++++------------- .../java/com/genymobile/scrcpy/Server.java | 7 +-- .../com/genymobile/scrcpy/VideoStreamer.java | 51 +++++++++++++++++++ 3 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 95f89ed5..1d66c0d1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -12,7 +12,6 @@ import android.os.IBinder; import android.os.SystemClock; import android.view.Surface; -import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -22,6 +21,10 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ScreenEncoder implements Device.RotationListener { + public interface Callbacks { + void onPacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException; + } + private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; @@ -30,25 +33,18 @@ public class ScreenEncoder implements Device.RotationListener { private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final int MAX_CONSECUTIVE_ERRORS = 3; - private static final long PACKET_FLAG_CONFIG = 1L << 63; - private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; - private final AtomicBoolean rotationChanged = new AtomicBoolean(); - private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); private final String encoderName; private final List codecOptions; private final int bitRate; private final int maxFps; - private final boolean sendFrameMeta; private final boolean downsizeOnError; private boolean firstFrameSent; private int consecutiveErrors; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName, - boolean downsizeOnError) { - this.sendFrameMeta = sendFrameMeta; + public ScreenEncoder(int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; @@ -65,7 +61,7 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, FileDescriptor fd) throws IOException { + public void streamScreen(Device device, Callbacks callbacks) throws IOException { MediaCodec codec = createCodec(encoderName); MediaFormat format = createFormat(bitRate, maxFps, codecOptions); IBinder display = createDisplay(); @@ -94,7 +90,7 @@ public class ScreenEncoder implements Device.RotationListener { codec.start(); - alive = encode(codec, fd); + alive = encode(codec, callbacks); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); } catch (IllegalStateException | IllegalArgumentException e) { @@ -163,7 +159,7 @@ public class ScreenEncoder implements Device.RotationListener { return 0; } - private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { + private boolean encode(MediaCodec codec, Callbacks callbacks) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); @@ -179,16 +175,14 @@ public class ScreenEncoder implements Device.RotationListener { if (outputBufferId >= 0) { ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); - if (sendFrameMeta) { - writeFrameMeta(fd, bufferInfo, codecBuffer.remaining()); - } - - IO.writeFully(fd, codecBuffer); - if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { + boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; + if (!isConfig) { // If this is not a config packet, then it contains a frame firstFrameSent = true; consecutiveErrors = 0; } + + callbacks.onPacket(codecBuffer, bufferInfo); } } finally { if (outputBufferId >= 0) { @@ -200,25 +194,6 @@ public class ScreenEncoder implements Device.RotationListener { return !eof; } - private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException { - headerBuffer.clear(); - - long pts; - if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - pts = PACKET_FLAG_CONFIG; // non-media data packet - } else { - pts = bufferInfo.presentationTimeUs; - if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { - pts |= PACKET_FLAG_KEY_FRAME; - } - } - - headerBuffer.putLong(pts); - headerBuffer.putInt(packetSize); - headerBuffer.flip(); - IO.writeFully(fd, headerBuffer); - } - private static MediaCodecInfo[] listEncoders() { List result = new ArrayList<>(); MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a4f17262..46612069 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -89,8 +89,8 @@ public final class Server { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); } - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName(), options.getDownsizeOnError()); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), + options.getDownsizeOnError()); Controller controller = null; if (control) { @@ -103,7 +103,8 @@ public final class Server { try { // synchronous - screenEncoder.streamScreen(device, connection.getVideoFd()); + VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), options.getSendFrameMeta()); + screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { // this is expected on close Ln.d("Screen streaming stopped"); diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java new file mode 100644 index 00000000..77e51499 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java @@ -0,0 +1,51 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodec; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class VideoStreamer implements ScreenEncoder.Callbacks { + + private static final long PACKET_FLAG_CONFIG = 1L << 63; + private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; + + private final FileDescriptor fd; + private final boolean sendFrameMeta; + + private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); + + public VideoStreamer(FileDescriptor fd, boolean sendFrameMeta) { + this.fd = fd; + this.sendFrameMeta = sendFrameMeta; + } + + @Override + public void onPacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { + if (sendFrameMeta) { + writeFrameMeta(fd, bufferInfo, codecBuffer.remaining()); + } + + IO.writeFully(fd, codecBuffer); + } + + private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException { + headerBuffer.clear(); + + long ptsAndFlags; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + ptsAndFlags = PACKET_FLAG_CONFIG; // non-media data packet + } else { + ptsAndFlags = bufferInfo.presentationTimeUs; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { + ptsAndFlags |= PACKET_FLAG_KEY_FRAME; + } + } + + headerBuffer.putLong(ptsAndFlags); + headerBuffer.putInt(packetSize); + headerBuffer.flip(); + IO.writeFully(fd, headerBuffer); + } +} From f70f6cdd3e5ffee2a687f47e8227544788932701 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Feb 2023 21:32:17 +0100 Subject: [PATCH 0629/1133] Simplify server info initialization Use sc_read16be() to read 16-bit integer fields. --- app/src/server.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 9384ce64..05e040cd 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -8,6 +8,7 @@ #include #include "adb/adb.h" +#include "util/binary.h" #include "util/file.h" #include "util/log.h" #include "util/net_intr.h" @@ -398,10 +399,9 @@ device_read_info(struct sc_intr *intr, sc_socket device_socket, buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; memcpy(info->device_name, (char *) buf, sizeof(info->device_name)); - info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8) - | buf[SC_DEVICE_NAME_FIELD_LENGTH + 1]; - info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8) - | buf[SC_DEVICE_NAME_FIELD_LENGTH + 3]; + unsigned char *fields = &buf[SC_DEVICE_NAME_FIELD_LENGTH]; + info->frame_size.width = sc_read16be(fields); + info->frame_size.height = sc_read16be(&fields[2]); return true; } From 3e517cd40eb50f8afb5137f39b0676fead58902a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Feb 2023 12:35:37 +0100 Subject: [PATCH 0630/1133] Add option to select video codec Introduce the selection mechanism. Alternative codecs will be added in further commits. PR #3713 --- README.md | 18 +++++++--- app/scrcpy.1 | 4 +++ app/src/cli.c | 22 ++++++++++++ app/src/demuxer.c | 29 ++++++++++++++-- app/src/options.c | 1 + app/src/options.h | 5 +++ app/src/scrcpy.c | 1 + app/src/server.c | 13 +++++++ app/src/server.h | 1 + .../java/com/genymobile/scrcpy/Options.java | 20 +++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 22 ++++++------ .../java/com/genymobile/scrcpy/Server.java | 20 +++++++++-- .../com/genymobile/scrcpy/VideoCodec.java | 34 +++++++++++++++++++ .../com/genymobile/scrcpy/VideoStreamer.java | 7 ++++ 14 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/VideoCodec.java diff --git a/README.md b/README.md index b2a767fd..facdfc81 100644 --- a/README.md +++ b/README.md @@ -252,10 +252,19 @@ This affects recording orientation. The [window may also be rotated](#rotation) independently. -#### Encoder +#### Codec -Some devices have more than one encoder, and some of them may cause issues or -crash. It is possible to select a different encoder: +The video codec can be selected: + +```bash +scrcpy --codec=h264 # default +``` + + +##### Encoder + +Some devices have more than one encoder for a specific codec, and some of them +may cause issues or crash. It is possible to select a different encoder: ```bash scrcpy --encoder=OMX.qcom.video.encoder.avc @@ -265,7 +274,8 @@ To list the available encoders, you can pass an invalid encoder name; the error will give the available encoders: ```bash -scrcpy --encoder=_ +scrcpy --encoder=_ # for the default codec +scrcpy --codec=h264 --encoder=_ # for a specific codec ``` ### Capture diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a828a239..c7ddeefb 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,6 +25,10 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 8000000. +.TP +.BI "\-\-codec " name +Select a video codec (h264). + .TP .BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] Set a list of comma-separated key:type=value options for the device encoder. diff --git a/app/src/cli.c b/app/src/cli.c index 538dd3e7..ee65e4c0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -57,6 +57,7 @@ #define OPT_NO_CLEANUP 1037 #define OPT_PRINT_FPS 1038 #define OPT_NO_POWER_ON 1039 +#define OPT_CODEC 1040 struct sc_option { char shortopt; @@ -105,6 +106,12 @@ static const struct sc_option options[] = { "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is " STR(DEFAULT_BIT_RATE) ".", }, + { + .longopt_id = OPT_CODEC, + .longopt = "codec", + .argdesc = "name", + .text = "Select a video codec (h264).", + }, { .longopt_id = OPT_CODEC_OPTIONS, .longopt = "codec-options", @@ -1377,6 +1384,16 @@ guess_record_format(const char *filename) { return 0; } +static bool +parse_codec(const char *optarg, enum sc_codec *codec) { + if (!strcmp(optarg, "h264")) { + *codec = SC_CODEC_H264; + return true; + } + LOGE("Unsupported codec: %s (expected h264)", optarg); + return false; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1610,6 +1627,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_PRINT_FPS: opts->start_fps_counter = true; break; + case OPT_CODEC: + if (!parse_codec(optarg, &opts->codec)) { + return false; + } + break; case OPT_OTG: #ifdef HAVE_USB opts->otg = true; diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c88af220..ad0b40a2 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -17,6 +17,25 @@ #define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1) +static enum AVCodecID +sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { + uint8_t data[4]; + ssize_t r = net_recv_all(demuxer->socket, data, 4); + if (r < 4) { + return false; + } + +#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII + uint32_t codec_id = sc_read32be(data); + switch (codec_id) { + case SC_CODEC_ID_H264: + return AV_CODEC_ID_H264; + default: + LOGE("Unknown codec id 0x%08" PRIx32, codec_id); + return AV_CODEC_ID_NONE; + } +} + static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // The video stream contains raw packets, without time information. When we @@ -171,7 +190,13 @@ static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; - const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); + enum AVCodecID codec_id = sc_demuxer_recv_codec_id(demuxer); + if (codec_id == AV_CODEC_ID_NONE) { + // Error already logged + goto end; + } + + const AVCodec *codec = avcodec_find_decoder(codec_id); if (!codec) { LOGE("H.264 decoder not found"); goto end; @@ -188,7 +213,7 @@ run_demuxer(void *data) { goto finally_free_codec_ctx; } - demuxer->parser = av_parser_init(AV_CODEC_ID_H264); + demuxer->parser = av_parser_init(codec_id); if (!demuxer->parser) { LOGE("Could not initialize parser"); goto finally_close_sinks; diff --git a/app/src/options.c b/app/src/options.c index 8b2624d9..525795ae 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -13,6 +13,7 @@ const struct scrcpy_options scrcpy_options_default = { .v4l2_device = NULL, #endif .log_level = SC_LOG_LEVEL_INFO, + .codec = SC_CODEC_H264, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .port_range = { diff --git a/app/src/options.h b/app/src/options.h index 7e542c06..18006e42 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -23,6 +23,10 @@ enum sc_record_format { SC_RECORD_FORMAT_MKV, }; +enum sc_codec { + SC_CODEC_H264, +}; + enum sc_lock_video_orientation { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, // lock the current orientation when scrcpy starts @@ -93,6 +97,7 @@ struct scrcpy_options { const char *v4l2_device; #endif enum sc_log_level log_level; + enum sc_codec codec; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 14688471..e85536e6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -315,6 +315,7 @@ scrcpy(struct scrcpy_options *options) { .select_usb = options->select_usb, .select_tcpip = options->select_tcpip, .log_level = options->log_level, + .codec = options->codec, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index 05e040cd..aaa0ffb1 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -156,6 +156,16 @@ sc_server_sleep(struct sc_server *server, sc_tick deadline) { return !stopped; } +static const char * +sc_server_get_codec_name(enum sc_codec codec) { + switch (codec) { + case SC_CODEC_H264: + return "h264"; + default: + return NULL; + } +} + static sc_pid execute_server(struct sc_server *server, const struct sc_server_params *params) { @@ -203,6 +213,9 @@ execute_server(struct sc_server *server, ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + if (params->codec != SC_CODEC_H264) { + ADD_PARAM("codec=%s", sc_server_get_codec_name(params->codec)); + } if (params->max_size) { ADD_PARAM("max_size=%" PRIu16, params->max_size); } diff --git a/app/src/server.h b/app/src/server.h index e0f2c225..950ad532 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -25,6 +25,7 @@ struct sc_server_params { uint32_t uid; const char *req_serial; enum sc_log_level log_level; + enum sc_codec codec; const char *crop; const char *codec_options; const char *encoder_name; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 171d6661..d43a60a2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -5,9 +5,12 @@ import android.graphics.Rect; import java.util.List; public class Options { + private static final String VIDEO_CODEC_H264 = "h264"; + private Ln.Level logLevel = Ln.Level.DEBUG; private int uid = -1; // 31-bit non-negative value, or -1 private int maxSize; + private VideoCodec codec = VideoCodec.H264; private int bitRate = 8000000; private int maxFps; private int lockVideoOrientation = -1; @@ -29,6 +32,7 @@ public class Options { private boolean sendDeviceMeta = true; // send device name and size private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean sendDummyByte = true; // write a byte on start to detect connection issues + private boolean sendCodecId = true; // write the codec ID (4 bytes) before the stream public Ln.Level getLogLevel() { return logLevel; @@ -54,6 +58,14 @@ public class Options { this.maxSize = maxSize; } + public VideoCodec getCodec() { + return codec; + } + + public void setCodec(VideoCodec codec) { + this.codec = codec; + } + public int getBitRate() { return bitRate; } @@ -205,4 +217,12 @@ public class Options { public void setSendDummyByte(boolean sendDummyByte) { this.sendDummyByte = sendDummyByte; } + + public boolean getSendCodecId() { + return sendCodecId; + } + + public void setSendCodecId(boolean sendCodecId) { + this.sendCodecId = sendCodecId; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 1d66c0d1..fed6f6c3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -35,6 +35,7 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); + private final String videoMimeType; private final String encoderName; private final List codecOptions; private final int bitRate; @@ -44,7 +45,8 @@ public class ScreenEncoder implements Device.RotationListener { private boolean firstFrameSent; private int consecutiveErrors; - public ScreenEncoder(int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { + public ScreenEncoder(String videoMimeType, int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { + this.videoMimeType = videoMimeType; this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; @@ -62,8 +64,8 @@ public class ScreenEncoder implements Device.RotationListener { } public void streamScreen(Device device, Callbacks callbacks) throws IOException { - MediaCodec codec = createCodec(encoderName); - MediaFormat format = createFormat(bitRate, maxFps, codecOptions); + MediaCodec codec = createCodec(videoMimeType, encoderName); + MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); boolean alive; @@ -194,28 +196,28 @@ public class ScreenEncoder implements Device.RotationListener { return !eof; } - private static MediaCodecInfo[] listEncoders() { + private static MediaCodecInfo[] listEncoders(String videoMimeType) { List result = new ArrayList<>(); MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); for (MediaCodecInfo codecInfo : list.getCodecInfos()) { - if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(MediaFormat.MIMETYPE_VIDEO_AVC)) { + if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(videoMimeType)) { result.add(codecInfo); } } return result.toArray(new MediaCodecInfo[result.size()]); } - private static MediaCodec createCodec(String encoderName) throws IOException { + private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - MediaCodecInfo[] encoders = listEncoders(); + MediaCodecInfo[] encoders = listEncoders(videoMimeType); throw new InvalidEncoderException(encoderName, encoders); } } - MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); + MediaCodec codec = MediaCodec.createEncoderByType(videoMimeType); Ln.d("Using encoder: '" + codec.getName() + "'"); return codec; } @@ -237,9 +239,9 @@ public class ScreenEncoder implements Device.RotationListener { Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); } - private static MediaFormat createFormat(int bitRate, int maxFps, List codecOptions) { + private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List codecOptions) { MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); + format.setString(MediaFormat.KEY_MIME, videoMimeType); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // must be present to configure the encoder, but does not impact the actual frame rate, which is variable format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 46612069..81c3e813 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -85,12 +85,13 @@ public final class Server { } try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, control, sendDummyByte)) { + VideoCodec codec = options.getCodec(); if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); } - ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), - options.getDownsizeOnError()); + ScreenEncoder screenEncoder = new ScreenEncoder(codec.getMimeType(), options.getBitRate(), options.getMaxFps(), codecOptions, + options.getEncoderName(), options.getDownsizeOnError()); Controller controller = null; if (control) { @@ -104,6 +105,9 @@ public final class Server { try { // synchronous VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), options.getSendFrameMeta()); + if (options.getSendCodecId()) { + videoStreamer.writeHeader(codec.getId()); + } screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { // this is expected on close @@ -156,6 +160,13 @@ public final class Server { Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); options.setLogLevel(level); break; + case "codec": + VideoCodec codec = VideoCodec.findByName(value); + if (codec == null) { + throw new IllegalArgumentException("Video codec " + value + " not supported"); + } + options.setCodec(codec); + break; case "max_size": int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 options.setMaxSize(maxSize); @@ -237,12 +248,17 @@ public final class Server { boolean sendDummyByte = Boolean.parseBoolean(value); options.setSendDummyByte(sendDummyByte); break; + case "send_codec_id": + boolean sendCodecId = Boolean.parseBoolean(value); + options.setSendCodecId(sendCodecId); + break; case "raw_video_stream": boolean rawVideoStream = Boolean.parseBoolean(value); if (rawVideoStream) { options.setSendDeviceMeta(false); options.setSendFrameMeta(false); options.setSendDummyByte(false); + options.setSendCodecId(false); } break; default: diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java new file mode 100644 index 00000000..e5edfcf2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -0,0 +1,34 @@ +package com.genymobile.scrcpy; + +import android.media.MediaFormat; + +public enum VideoCodec { + H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC); + + private final int id; // 4-byte ASCII representation of the name + private final String name; + private final String mimeType; + + VideoCodec(int id, String name, String mimeType) { + this.id = id; + this.name = name; + this.mimeType = mimeType; + } + + public int getId() { + return id; + } + + public String getMimeType() { + return mimeType; + } + + public static VideoCodec findByName(String name) { + for (VideoCodec codec : values()) { + if (codec.name.equals(name)) { + return codec; + } + } + return null; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java index 77e51499..943c641d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java @@ -21,6 +21,13 @@ public final class VideoStreamer implements ScreenEncoder.Callbacks { this.sendFrameMeta = sendFrameMeta; } + public void writeHeader(int codecId) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(codecId); + buffer.flip(); + IO.writeFully(fd, buffer); + } + @Override public void onPacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { if (sendFrameMeta) { From 4342c5637d74de2a6af5a6c83b81060bdf198af4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Feb 2023 12:40:55 +0100 Subject: [PATCH 0631/1133] Add support for H265 Add option --codec=h265. PR #3713 Fixes #3092 --- README.md | 6 ++++-- app/scrcpy.1 | 2 +- app/src/cli.c | 8 ++++++-- app/src/demuxer.c | 3 +++ app/src/options.h | 1 + app/src/server.c | 2 ++ .../src/main/java/com/genymobile/scrcpy/VideoCodec.java | 3 ++- 7 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index facdfc81..6218bee4 100644 --- a/README.md +++ b/README.md @@ -254,10 +254,12 @@ The [window may also be rotated](#rotation) independently. #### Codec -The video codec can be selected: +The video codec can be selected. The possible values are `h264` (default) and +`h265`: ```bash scrcpy --codec=h264 # default +scrcpy --codec=h265 ``` @@ -275,7 +277,7 @@ error will give the available encoders: ```bash scrcpy --encoder=_ # for the default codec -scrcpy --codec=h264 --encoder=_ # for a specific codec +scrcpy --codec=h265 --encoder=_ # for a specific codec ``` ### Capture diff --git a/app/scrcpy.1 b/app/scrcpy.1 index c7ddeefb..be607173 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -27,7 +27,7 @@ Default is 8000000. .TP .BI "\-\-codec " name -Select a video codec (h264). +Select a video codec (h264 or h265). .TP .BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] diff --git a/app/src/cli.c b/app/src/cli.c index ee65e4c0..76d59f38 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -110,7 +110,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_CODEC, .longopt = "codec", .argdesc = "name", - .text = "Select a video codec (h264).", + .text = "Select a video codec (h264 or h265).", }, { .longopt_id = OPT_CODEC_OPTIONS, @@ -1390,7 +1390,11 @@ parse_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_H264; return true; } - LOGE("Unsupported codec: %s (expected h264)", optarg); + if (!strcmp(optarg, "h265")) { + *codec = SC_CODEC_H265; + return true; + } + LOGE("Unsupported codec: %s (expected h264 or h265)", optarg); return false; } diff --git a/app/src/demuxer.c b/app/src/demuxer.c index ad0b40a2..119d4065 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -26,10 +26,13 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { } #define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII +#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII uint32_t codec_id = sc_read32be(data); switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; + case SC_CODEC_ID_H265: + return AV_CODEC_ID_HEVC; default: LOGE("Unknown codec id 0x%08" PRIx32, codec_id); return AV_CODEC_ID_NONE; diff --git a/app/src/options.h b/app/src/options.h index 18006e42..b3c39594 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -25,6 +25,7 @@ enum sc_record_format { enum sc_codec { SC_CODEC_H264, + SC_CODEC_H265, }; enum sc_lock_video_orientation { diff --git a/app/src/server.c b/app/src/server.c index aaa0ffb1..a63504bb 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -161,6 +161,8 @@ sc_server_get_codec_name(enum sc_codec codec) { switch (codec) { case SC_CODEC_H264: return "h264"; + case SC_CODEC_H265: + return "h265"; default: return NULL; } diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java index e5edfcf2..048765f7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -3,7 +3,8 @@ package com.genymobile.scrcpy; import android.media.MediaFormat; public enum VideoCodec { - H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC); + H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), + H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC); private final int id; // 4-byte ASCII representation of the name private final String name; From d2dce5103832d6a9fc29f56a9795c22e44ceafa1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Feb 2023 12:42:22 +0100 Subject: [PATCH 0632/1133] Add support for AV1 Add option --codec=av1. PR #3713 --- README.md | 5 +++-- app/scrcpy.1 | 2 +- app/src/cli.c | 15 +++++++++++++-- app/src/demuxer.c | 3 +++ app/src/options.h | 1 + app/src/server.c | 2 ++ .../java/com/genymobile/scrcpy/VideoCodec.java | 3 ++- 7 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6218bee4..a19aec0f 100644 --- a/README.md +++ b/README.md @@ -254,12 +254,13 @@ The [window may also be rotated](#rotation) independently. #### Codec -The video codec can be selected. The possible values are `h264` (default) and -`h265`: +The video codec can be selected. The possible values are `h264` (default), +`h265` and `av1`: ```bash scrcpy --codec=h264 # default scrcpy --codec=h265 +scrcpy --codec=av1 ``` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index be607173..18c70d65 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -27,7 +27,7 @@ Default is 8000000. .TP .BI "\-\-codec " name -Select a video codec (h264 or h265). +Select a video codec (h264, h265 or av1). .TP .BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] diff --git a/app/src/cli.c b/app/src/cli.c index 76d59f38..27b0ddef 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -110,7 +110,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_CODEC, .longopt = "codec", .argdesc = "name", - .text = "Select a video codec (h264 or h265).", + .text = "Select a video codec (h264, h265 or av1).", }, { .longopt_id = OPT_CODEC_OPTIONS, @@ -1394,7 +1394,11 @@ parse_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_H265; return true; } - LOGE("Unsupported codec: %s (expected h264 or h265)", optarg); + if (!strcmp(optarg, "av1")) { + *codec = SC_CODEC_AV1; + return true; + } + LOGE("Unsupported codec: %s (expected h264, h265 or av1)", optarg); return false; } @@ -1744,6 +1748,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->record_format == SC_RECORD_FORMAT_MP4 + && opts->codec == SC_CODEC_AV1) { + LOGE("Could not mux AV1 stream into MP4 container " + "(record to mkv or select another video codec)"); + return false; + } + if (!opts->control) { if (opts->turn_screen_off) { LOGE("Could not request to turn screen off if control is disabled"); diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 119d4065..6c44a558 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -27,12 +27,15 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { #define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII #define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII +#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII uint32_t codec_id = sc_read32be(data); switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; case SC_CODEC_ID_H265: return AV_CODEC_ID_HEVC; + case SC_CODEC_ID_AV1: + return AV_CODEC_ID_AV1; default: LOGE("Unknown codec id 0x%08" PRIx32, codec_id); return AV_CODEC_ID_NONE; diff --git a/app/src/options.h b/app/src/options.h index b3c39594..b9d237e0 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -26,6 +26,7 @@ enum sc_record_format { enum sc_codec { SC_CODEC_H264, SC_CODEC_H265, + SC_CODEC_AV1, }; enum sc_lock_video_orientation { diff --git a/app/src/server.c b/app/src/server.c index a63504bb..74d318c8 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -163,6 +163,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "h264"; case SC_CODEC_H265: return "h265"; + case SC_CODEC_AV1: + return "av1"; default: return NULL; } diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java index 048765f7..809d7dbc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -4,7 +4,8 @@ import android.media.MediaFormat; public enum VideoCodec { H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), - H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC); + H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), + AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1); private final int id; // 4-byte ASCII representation of the name private final String name; From f2dee20a207b9906574d9b3d62a0ab2585dade20 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 5 Feb 2023 09:50:51 +0100 Subject: [PATCH 0633/1133] Set power mode on all physical displays Android 10 and above support multiple physical displays. Apply power mode to all of them. Fixes #3716 --- .../java/com/genymobile/scrcpy/Device.java | 20 ++++++++++ .../scrcpy/wrappers/SurfaceControl.java | 37 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 97b25b22..30e64fd7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -277,6 +277,26 @@ public final class Device { * @param mode one of the {@code POWER_MODE_*} constants */ public static boolean setScreenPowerMode(int mode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Change the power mode for all physical displays + long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds(); + if (physicalDisplayIds == null) { + Ln.e("Could not get physical display ids"); + return false; + } + + boolean allOk = true; + for (long physicalDisplayId : physicalDisplayIds) { + IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); + boolean ok = SurfaceControl.setDisplayPowerMode(binder, mode); + if (!ok) { + allOk = false; + } + } + return allOk; + } + + // Older Android versions, only 1 display IBinder d = SurfaceControl.getBuiltInDisplay(); if (d == null) { Ln.e("Could not get built-in display"); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 8fbb860b..595ee6d4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -30,6 +30,8 @@ public final class SurfaceControl { private static Method getBuiltInDisplayMethod; private static Method setDisplayPowerModeMethod; + private static Method getPhysicalDisplayTokenMethod; + private static Method getPhysicalDisplayIdsMethod; private SurfaceControl() { // only static methods @@ -98,7 +100,6 @@ public final class SurfaceControl { } public static IBinder getBuiltInDisplay() { - try { Method method = getGetBuiltInDisplayMethod(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { @@ -114,6 +115,40 @@ public final class SurfaceControl { } } + private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { + if (getPhysicalDisplayTokenMethod == null) { + getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); + } + return getPhysicalDisplayTokenMethod; + } + + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + try { + Method method = getGetPhysicalDisplayTokenMethod(); + return (IBinder) method.invoke(null, physicalDisplayId); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { + if (getPhysicalDisplayIdsMethod == null) { + getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); + } + return getPhysicalDisplayIdsMethod; + } + + public static long[] getPhysicalDisplayIds() { + try { + Method method = getGetPhysicalDisplayIdsMethod(); + return (long[]) method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException { if (setDisplayPowerModeMethod == null) { setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class); From b4caa483dd6682279170b5ec61963e6239f15072 Mon Sep 17 00:00:00 2001 From: Kartik Kushwaha <75395993+Corrupter-rot@users.noreply.github.com> Date: Sat, 4 Feb 2023 23:09:01 +0530 Subject: [PATCH 0634/1133] Add Fedora instructions in README Add the command to install the scrcpy package for Fedora directly. PR #3715 Signed-off-by: Romain Vimont --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b2a767fd..7f4fe704 100644 --- a/README.md +++ b/README.md @@ -80,16 +80,22 @@ On Arch Linux: pacman -S scrcpy ``` +On Fedora, a [COPR] package is available: [`scrcpy`][copr-link]: + +``` +dnf copr enable zeno/scrcpy +dnf install scrcpy +``` + +[COPR]: https://fedoraproject.org/wiki/Category:Copr +[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ + A [Snap] package is available: [`scrcpy`][snap-link]. [snap-link]: https://snapstats.org/snaps/scrcpy [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) -For Fedora, a [COPR] package is available: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. From 6524e90c68d5e85ab2acc6177abed3603b4e9f78 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Feb 2023 23:11:03 +0100 Subject: [PATCH 0635/1133] Remove unused constant This line was committed by error. Refs 3e517cd40eb50f8afb5137f39b0676fead58902a --- server/src/main/java/com/genymobile/scrcpy/Options.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d43a60a2..474e5cf2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -5,7 +5,6 @@ import android.graphics.Rect; import java.util.List; public class Options { - private static final String VIDEO_CODEC_H264 = "h264"; private Ln.Level logLevel = Ln.Level.DEBUG; private int uid = -1; // 31-bit non-negative value, or -1 From bd56c0abf7287b985702f1d0c24dab56d817969e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:45:31 +0100 Subject: [PATCH 0636/1133] Remove unused codec context The demuxer does not need any codec context. --- app/src/demuxer.c | 10 +--------- app/src/demuxer.h | 1 - 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 6c44a558..945d3c78 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -208,15 +208,9 @@ run_demuxer(void *data) { goto end; } - demuxer->codec_ctx = avcodec_alloc_context3(codec); - if (!demuxer->codec_ctx) { - LOG_OOM(); - goto end; - } - if (!sc_demuxer_open_sinks(demuxer, codec)) { LOGE("Could not open demuxer sinks"); - goto finally_free_codec_ctx; + goto end; } demuxer->parser = av_parser_init(codec_id); @@ -261,8 +255,6 @@ finally_close_parser: av_parser_close(demuxer->parser); finally_close_sinks: sc_demuxer_close_sinks(demuxer); -finally_free_codec_ctx: - avcodec_free_context(&demuxer->codec_ctx); end: demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata); diff --git a/app/src/demuxer.h b/app/src/demuxer.h index 11e20ad6..4e660fbf 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -21,7 +21,6 @@ struct sc_demuxer { struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS]; unsigned sink_count; - AVCodecContext *codec_ctx; AVCodecParserContext *parser; // successive packets may need to be concatenated, until a non-config // packet is available From 40866ddc10f35368d25ec05eb2a6cd3d0ebc803e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:55:39 +0100 Subject: [PATCH 0637/1133] Fix demuxer error message The message applies to all packets, not only config packets. --- app/src/demuxer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 945d3c78..a913139a 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -105,7 +105,7 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { for (unsigned i = 0; i < demuxer->sink_count; ++i) { struct sc_packet_sink *sink = demuxer->sinks[i]; if (!sink->ops->push(sink, packet)) { - LOGE("Could not send config packet to sink %d", i); + LOGE("Could not send packet to sink %d", i); return false; } } From 230b8274b90cd035b94f849165e8a807346c93d3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:47:13 +0100 Subject: [PATCH 0638/1133] Fix error return value The function returns an enum AVCodecID, not a bool. --- app/src/demuxer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index a913139a..68c999c0 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -22,7 +22,7 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { uint8_t data[4]; ssize_t r = net_recv_all(demuxer->socket, data, 4); if (r < 4) { - return false; + return AV_CODEC_ID_NONE; } #define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII From 953edfd1df95aadb794167274e2be2f1037848fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:48:20 +0100 Subject: [PATCH 0639/1133] Split codec_id reading Receive codec id and convert it to AVCodecID separately. This will allow the caller to distinguish between EOS and unknown codec id. --- app/src/demuxer.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 68c999c0..e6a35f4f 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -18,17 +18,10 @@ #define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1) static enum AVCodecID -sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { - uint8_t data[4]; - ssize_t r = net_recv_all(demuxer->socket, data, 4); - if (r < 4) { - return AV_CODEC_ID_NONE; - } - +sc_demuxer_to_avcodec_id(uint32_t codec_id) { #define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII #define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII #define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII - uint32_t codec_id = sc_read32be(data); switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; @@ -42,6 +35,18 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) { } } +static bool +sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) { + uint8_t data[4]; + ssize_t r = net_recv_all(demuxer->socket, data, 4); + if (r < 4) { + return false; + } + + *codec_id = sc_read32be(data); + return true; +} + static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // The video stream contains raw packets, without time information. When we @@ -196,7 +201,13 @@ static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; - enum AVCodecID codec_id = sc_demuxer_recv_codec_id(demuxer); + uint32_t raw_codec_id; + bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); + if (!ok) { + goto end; + } + + enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id); if (codec_id == AV_CODEC_ID_NONE) { // Error already logged goto end; From 4f9e9c6619cdf847e3ee78f480fc4ca642ae7c2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:46:12 +0100 Subject: [PATCH 0640/1133] Prefix UI events constants by SC_ --- app/src/events.h | 10 +++++----- app/src/scrcpy.c | 12 ++++++------ app/src/screen.c | 6 +++--- app/src/usb/scrcpy_otg.c | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/events.h b/app/src/events.h index 3c14f96e..477328de 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,5 +1,5 @@ -#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_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) +#define SC_EVENT_NEW_FRAME SDL_USEREVENT +#define SC_EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) +#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2) +#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) +#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e85536e6..b0324f7a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -155,7 +155,7 @@ event_loop(struct scrcpy *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { - case EVENT_STREAM_STOPPED: + case SC_EVENT_STREAM_STOPPED: LOGW("Device disconnected"); return SCRCPY_EXIT_DISCONNECTED; case SDL_QUIT: @@ -179,10 +179,10 @@ await_for_server(bool *connected) { LOGD("User requested to quit"); *connected = false; return true; - case EVENT_SERVER_CONNECTION_FAILED: + case SC_EVENT_SERVER_CONNECTION_FAILED: LOGE("Server connection failed"); return false; - case EVENT_SERVER_CONNECTED: + case SC_EVENT_SERVER_CONNECTED: LOGD("Server connected"); *connected = true; return true; @@ -237,7 +237,7 @@ sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) { (void) demuxer; (void) userdata; - PUSH_EVENT(EVENT_STREAM_STOPPED); + PUSH_EVENT(SC_EVENT_STREAM_STOPPED); } static void @@ -245,7 +245,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) { (void) server; (void) userdata; - PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED); + PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED); } static void @@ -253,7 +253,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) { (void) server; (void) userdata; - PUSH_EVENT(EVENT_SERVER_CONNECTED); + PUSH_EVENT(SC_EVENT_SERVER_CONNECTED); } static void diff --git a/app/src/screen.c b/app/src/screen.c index ae28e6e6..425ba2c3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -371,7 +371,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, bool need_new_event; if (previous_skipped) { sc_fps_counter_add_skipped_frame(&screen->fps_counter); - // The EVENT_NEW_FRAME triggered for the previous frame will consume + // The SC_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; } else { @@ -380,7 +380,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, if (need_new_event) { static SDL_Event new_frame_event = { - .type = EVENT_NEW_FRAME, + .type = SC_EVENT_NEW_FRAME, }; // Post the event on the UI thread @@ -820,7 +820,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { bool relative_mode = sc_screen_is_relative_mode(screen); switch (event->type) { - case EVENT_NEW_FRAME: { + case SC_EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); if (!ok) { LOGW("Frame update failed\n"); diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index ebcfa36f..f469de1a 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -22,7 +22,7 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) { (void) userdata; SDL_Event event; - event.type = EVENT_USB_DEVICE_DISCONNECTED; + event.type = SC_EVENT_USB_DEVICE_DISCONNECTED; int ret = SDL_PushEvent(&event); if (ret < 0) { LOGE("Could not post USB disconnection event: %s", SDL_GetError()); @@ -34,7 +34,7 @@ event_loop(struct scrcpy_otg *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { - case EVENT_USB_DEVICE_DISCONNECTED: + case SC_EVENT_USB_DEVICE_DISCONNECTED: LOGW("Device disconnected"); return SCRCPY_EXIT_DISCONNECTED; case SDL_QUIT: From 730eb1086ad32fbd368a6ded61ef8b79741e5f66 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:54:46 +0100 Subject: [PATCH 0641/1133] Properly report demuxer errors All demuxer errors were reported as "device disconnected", even if the failure was not related to device socket read. --- app/src/demuxer.c | 9 +++++++-- app/src/demuxer.h | 2 +- app/src/events.h | 3 ++- app/src/scrcpy.c | 15 +++++++++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index e6a35f4f..97a17594 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -201,9 +201,13 @@ static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; + // Flag to report end-of-stream (i.e. device disconnected) + bool eos = false; + uint32_t raw_codec_id; bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); if (!ok) { + eos = true; goto end; } @@ -244,6 +248,7 @@ run_demuxer(void *data) { bool ok = sc_demuxer_recv_packet(demuxer, packet); if (!ok) { // end of stream + eos = true; break; } @@ -267,7 +272,7 @@ finally_close_parser: finally_close_sinks: sc_demuxer_close_sinks(demuxer); end: - demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata); + demuxer->cbs->on_ended(demuxer, eos, demuxer->cbs_userdata); return 0; } @@ -279,7 +284,7 @@ sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, demuxer->pending = NULL; demuxer->sink_count = 0; - assert(cbs && cbs->on_eos); + assert(cbs && cbs->on_ended); demuxer->cbs = cbs; demuxer->cbs_userdata = cbs_userdata; diff --git a/app/src/demuxer.h b/app/src/demuxer.h index 4e660fbf..dfb06675 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -31,7 +31,7 @@ struct sc_demuxer { }; struct sc_demuxer_callbacks { - void (*on_eos)(struct sc_demuxer *demuxer, void *userdata); + void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata); }; void diff --git a/app/src/events.h b/app/src/events.h index 477328de..7fa10761 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -1,5 +1,6 @@ #define SC_EVENT_NEW_FRAME SDL_USEREVENT -#define SC_EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) +#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1) #define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2) #define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) #define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) +#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b0324f7a..bf4bb199 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -155,9 +155,12 @@ event_loop(struct scrcpy *s) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { - case SC_EVENT_STREAM_STOPPED: + case SC_EVENT_DEVICE_DISCONNECTED: LOGW("Device disconnected"); return SCRCPY_EXIT_DISCONNECTED; + case SC_EVENT_DEMUXER_ERROR: + LOGE("Demuxer error"); + return SCRCPY_EXIT_FAILURE; case SDL_QUIT: LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; @@ -233,11 +236,15 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { } static void -sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) { +sc_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, void *userdata) { (void) demuxer; (void) userdata; - PUSH_EVENT(SC_EVENT_STREAM_STOPPED); + if (eos) { + PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); + } else { + PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); + } } static void @@ -421,7 +428,7 @@ scrcpy(struct scrcpy_options *options) { av_log_set_callback(av_log_callback); static const struct sc_demuxer_callbacks demuxer_cbs = { - .on_eos = sc_demuxer_on_eos, + .on_ended = sc_demuxer_on_ended, }; sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); From 400a1c69b198103feff9682895529529890c1acc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Feb 2023 22:58:46 +0100 Subject: [PATCH 0642/1133] Join all threads before end of main Some calls from separate threads may throw exceptions once the main() method has returned. --- .../src/main/java/com/genymobile/scrcpy/Controller.java | 8 +++++++- .../java/com/genymobile/scrcpy/DeviceMessageSender.java | 7 ++++++- server/src/main/java/com/genymobile/scrcpy/Server.java | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6147e36a..02684a1d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -100,11 +100,17 @@ public class Controller { public void stop() { if (thread != null) { thread.interrupt(); - thread = null; } sender.stop(); } + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + sender.join(); + } + public DeviceMessageSender getSender() { return sender; } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 7ec4ab41..b0e2a388 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -66,7 +66,12 @@ public final class DeviceMessageSender { public void stop() { if (thread != null) { thread.interrupt(); - thread = null; + } + } + + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 81c3e813..ac5f0293 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -117,6 +117,15 @@ public final class Server { if (controller != null) { controller.stop(); } + + try { + initThread.join(); + if (controller != null) { + controller.join(); + } + } catch (InterruptedException e) { + // ignore + } } } } From 45b2e6db5c8df04b85cf646d1744646d1e679c6d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 19:06:17 +0100 Subject: [PATCH 0643/1133] Log component stopped in finally clause The message must be logged even when no exception occurs. --- server/src/main/java/com/genymobile/scrcpy/Controller.java | 1 + .../main/java/com/genymobile/scrcpy/DeviceMessageSender.java | 1 + server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 02684a1d..02d77cb1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -90,6 +90,7 @@ public class Controller { control(); } catch (IOException e) { // this is expected on close + } finally { Ln.d("Controller stopped"); } }); diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index b0e2a388..0ef2a9ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -57,6 +57,7 @@ public final class DeviceMessageSender { loop(); } catch (IOException | InterruptedException e) { // this is expected on close + } finally { Ln.d("Device message sender stopped"); } }); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ac5f0293..cfb45e33 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,8 +111,8 @@ public final class Server { screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { // this is expected on close - Ln.d("Screen streaming stopped"); } finally { + Ln.d("Screen streaming stopped"); initThread.interrupt(); if (controller != null) { controller.stop(); From f03f32267e8c40301a435b5ec9db9655c852b8f9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 23:18:09 +0100 Subject: [PATCH 0644/1133] Remove unused parser Since 1c02b58412db2646ba6e57a7e305d26482b9bc0d, the parser is not used anymore. --- app/src/demuxer.c | 14 +------------- app/src/demuxer.h | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 97a17594..5cc07f6c 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -228,20 +228,10 @@ run_demuxer(void *data) { goto end; } - demuxer->parser = av_parser_init(codec_id); - if (!demuxer->parser) { - LOGE("Could not initialize parser"); - goto finally_close_sinks; - } - - // We must only pass complete frames to av_parser_parse2()! - // It's more complicated, but this allows to reduce the latency by 1 frame! - demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; - AVPacket *packet = av_packet_alloc(); if (!packet) { LOG_OOM(); - goto finally_close_parser; + goto finally_close_sinks; } for (;;) { @@ -267,8 +257,6 @@ run_demuxer(void *data) { } av_packet_free(&packet); -finally_close_parser: - av_parser_close(demuxer->parser); finally_close_sinks: sc_demuxer_close_sinks(demuxer); end: diff --git a/app/src/demuxer.h b/app/src/demuxer.h index dfb06675..86e0536d 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -21,7 +21,6 @@ struct sc_demuxer { struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS]; unsigned sink_count; - AVCodecParserContext *parser; // successive packets may need to be concatenated, until a non-config // packet is available AVPacket *pending; From 49eb326ce9beb0a993dce0f2a7beb7dcfc3b1c51 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 22:52:40 +0100 Subject: [PATCH 0645/1133] Extract packet merging Config packets must be prepended to the next media packet. Extract the logic to a new sc_packet_merger helper to simplify the demuxer code. --- app/meson.build | 1 + app/src/demuxer.c | 57 +++++++++-------------------------------- app/src/demuxer.h | 4 --- app/src/packet_merger.c | 48 ++++++++++++++++++++++++++++++++++ app/src/packet_merger.h | 43 +++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 49 deletions(-) create mode 100644 app/src/packet_merger.c create mode 100644 app/src/packet_merger.h diff --git a/app/meson.build b/app/meson.build index 3ddd5a5d..5d779756 100644 --- a/app/meson.build +++ b/app/meson.build @@ -21,6 +21,7 @@ src = [ 'src/mouse_inject.c', 'src/opengl.c', 'src/options.c', + 'src/packet_merger.c', 'src/receiver.c', 'src/recorder.c', 'src/scrcpy.c', diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 5cc07f6c..282c07de 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -6,6 +6,7 @@ #include "decoder.h" #include "events.h" +#include "packet_merger.h" #include "recorder.h" #include "util/binary.h" #include "util/log.h" @@ -120,48 +121,7 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { static bool sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { - bool is_config = packet->pts == AV_NOPTS_VALUE; - - // A config packet must not be decoded immediately (it contains no - // frame); instead, it must be concatenated with the future data packet. - if (demuxer->pending || is_config) { - if (demuxer->pending) { - size_t offset = demuxer->pending->size; - if (av_grow_packet(demuxer->pending, packet->size)) { - LOG_OOM(); - return false; - } - - memcpy(demuxer->pending->data + offset, packet->data, packet->size); - } else { - demuxer->pending = av_packet_alloc(); - if (!demuxer->pending) { - LOG_OOM(); - return false; - } - if (av_packet_ref(demuxer->pending, packet)) { - LOG_OOM(); - av_packet_free(&demuxer->pending); - return false; - } - } - - if (!is_config) { - // prepare the concat packet to send to the decoder - demuxer->pending->pts = packet->pts; - demuxer->pending->dts = packet->dts; - demuxer->pending->flags = packet->flags; - packet = demuxer->pending; - } - } - bool ok = push_packet_to_sinks(demuxer, packet); - - if (!is_config && demuxer->pending) { - // the pending packet must be discarded (consumed or error) - av_packet_free(&demuxer->pending); - } - if (!ok) { LOGE("Could not process packet"); return false; @@ -228,6 +188,9 @@ run_demuxer(void *data) { goto end; } + struct sc_packet_merger merger; + sc_packet_merger_init(&merger); + AVPacket *packet = av_packet_alloc(); if (!packet) { LOG_OOM(); @@ -242,6 +205,13 @@ run_demuxer(void *data) { break; } + // Prepend any config packet to the next media packet + ok = sc_packet_merger_merge(&merger, packet); + if (!ok) { + av_packet_unref(packet); + break; + } + ok = sc_demuxer_push_packet(demuxer, packet); av_packet_unref(packet); if (!ok) { @@ -252,9 +222,7 @@ run_demuxer(void *data) { LOGD("End of frames"); - if (demuxer->pending) { - av_packet_free(&demuxer->pending); - } + sc_packet_merger_destroy(&merger); av_packet_free(&packet); finally_close_sinks: @@ -269,7 +237,6 @@ void sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) { demuxer->socket = socket; - demuxer->pending = NULL; demuxer->sink_count = 0; assert(cbs && cbs->on_ended); diff --git a/app/src/demuxer.h b/app/src/demuxer.h index 86e0536d..e403fe35 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -21,10 +21,6 @@ struct sc_demuxer { struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS]; unsigned sink_count; - // successive packets may need to be concatenated, until a non-config - // packet is available - AVPacket *pending; - const struct sc_demuxer_callbacks *cbs; void *cbs_userdata; }; diff --git a/app/src/packet_merger.c b/app/src/packet_merger.c new file mode 100644 index 00000000..81b02d2c --- /dev/null +++ b/app/src/packet_merger.c @@ -0,0 +1,48 @@ +#include "packet_merger.h" + +#include "util/log.h" + +void +sc_packet_merger_init(struct sc_packet_merger *merger) { + merger->config = NULL; +} + +void +sc_packet_merger_destroy(struct sc_packet_merger *merger) { + free(merger->config); +} + +bool +sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet) { + bool is_config = packet->pts == AV_NOPTS_VALUE; + + if (is_config) { + free(merger->config); + + merger->config = malloc(packet->size); + if (!merger->config) { + LOG_OOM(); + return false; + } + + memcpy(merger->config, packet->data, packet->size); + merger->config_size = packet->size; + } else if (merger->config) { + size_t config_size = merger->config_size; + size_t media_size = packet->size; + + if (av_grow_packet(packet, config_size)) { + LOG_OOM(); + return false; + } + + memmove(packet->data + config_size, packet->data, media_size); + memcpy(packet->data, merger->config, config_size); + + free(merger->config); + merger->config = NULL; + // merger->size is meaningless when merger->config is NULL + } + + return true; +} diff --git a/app/src/packet_merger.h b/app/src/packet_merger.h new file mode 100644 index 00000000..e1824c2c --- /dev/null +++ b/app/src/packet_merger.h @@ -0,0 +1,43 @@ +#ifndef SC_PACKET_MERGER_H +#define SC_PACKET_MERGER_H + +#include "common.h" + +#include +#include +#include + +/** + * Config packets (containing the SPS/PPS) are sent in-band. A new config + * packet is sent whenever a new encoding session is started (on start and on + * device orientation change). + * + * Every time a config packet is received, it must be sent alone (for recorder + * extradata), then concatenated to the next media packet (for correct decoding + * and recording). + * + * This helper reads every input packet and modifies each media packet which + * immediately follows a config packet to prepend the config packet payload. + */ + +struct sc_packet_merger { + uint8_t *config; + size_t config_size; +}; + +void +sc_packet_merger_init(struct sc_packet_merger *merger); + +void +sc_packet_merger_destroy(struct sc_packet_merger *merger); + +/** + * If the packet is a config packet, then keep its data for later. + * Otherwise (if the packet is a media packet), then if a config packet is + * pending, prepend the config packet to this packet (so the packet is + * modified!). + */ +bool +sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet); + +#endif From 439a1fd4ed3d7ada4c5098a6d52893dd4e87082a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Feb 2023 09:52:31 +0100 Subject: [PATCH 0646/1133] Rename 'uid' to 'scid' A random identifier is generated to differentiate multiple running scrcpy instances. Rename it from 'uid' to 'scid' (scrcpy id) not to confuse it with Linux UID. Fixes #3729 Refs 4315be164823d2c8fc44b475b52af79bfee98ff1 --- app/src/scrcpy.c | 7 ++++--- app/src/server.c | 4 ++-- app/src/server.h | 2 +- .../com/genymobile/scrcpy/DesktopConnection.java | 12 ++++++------ .../main/java/com/genymobile/scrcpy/Options.java | 10 +++++----- .../main/java/com/genymobile/scrcpy/Server.java | 14 +++++++------- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index bf4bb199..8932dd1d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -273,8 +273,9 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) { // event } +// Generate a scrcpy id to differentiate multiple running scrcpy instances static uint32_t -scrcpy_generate_uid() { +scrcpy_generate_scid() { struct sc_rand rand; sc_rand_init(&rand); // Only use 31 bits to avoid issues with signed values on the Java-side @@ -314,10 +315,10 @@ scrcpy(struct scrcpy_options *options) { struct sc_acksync *acksync = NULL; - uint32_t uid = scrcpy_generate_uid(); + uint32_t scid = scrcpy_generate_scid(); struct sc_server_params params = { - .uid = uid, + .scid = scid, .req_serial = options->serial, .select_usb = options->select_usb, .select_tcpip = options->select_tcpip, diff --git a/app/src/server.c b/app/src/server.c index 74d318c8..96bff77e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -213,7 +213,7 @@ execute_server(struct sc_server *server, cmd[count++] = p; \ } - ADD_PARAM("uid=%08x", params->uid); + ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); @@ -787,7 +787,7 @@ run_server(void *data) { LOGD("Device serial: %s", serial); int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x", - params->uid); + params->scid); if (r == -1) { LOG_OOM(); goto error_connection_failed; diff --git a/app/src/server.h b/app/src/server.h index 950ad532..d6b1401e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -22,7 +22,7 @@ struct sc_server_info { }; struct sc_server_params { - uint32_t uid; + uint32_t scid; const char *req_serial; enum sc_log_level log_level; enum sc_codec codec; diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 7f287a6a..1f8f46e4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -46,17 +46,17 @@ public final class DesktopConnection implements Closeable { return localSocket; } - private static String getSocketName(int uid) { - if (uid == -1) { - // If no UID is set, use "scrcpy" to simplify using scrcpy-server alone + private static String getSocketName(int scid) { + if (scid == -1) { + // If no SCID is set, use "scrcpy" to simplify using scrcpy-server alone return SOCKET_NAME_PREFIX; } - return SOCKET_NAME_PREFIX + String.format("_%08x", uid); + return SOCKET_NAME_PREFIX + String.format("_%08x", scid); } - public static DesktopConnection open(int uid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { - String socketName = getSocketName(uid); + public static DesktopConnection open(int scid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { + String socketName = getSocketName(scid); LocalSocket videoSocket; LocalSocket controlSocket = null; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 474e5cf2..5c59ec8e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -7,7 +7,7 @@ import java.util.List; public class Options { private Ln.Level logLevel = Ln.Level.DEBUG; - private int uid = -1; // 31-bit non-negative value, or -1 + private int scid = -1; // 31-bit non-negative value, or -1 private int maxSize; private VideoCodec codec = VideoCodec.H264; private int bitRate = 8000000; @@ -41,12 +41,12 @@ public class Options { this.logLevel = logLevel; } - public int getUid() { - return uid; + public int getScid() { + return scid; } - public void setUid(int uid) { - this.uid = uid; + public void setScid(int scid) { + this.scid = scid; } public int getMaxSize() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index cfb45e33..d7a99576 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -66,7 +66,7 @@ public final class Server { Thread initThread = startInitThread(options); - int uid = options.getUid(); + int scid = options.getScid(); boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); boolean sendDummyByte = options.getSendDummyByte(); @@ -84,7 +84,7 @@ public final class Server { Workarounds.fillAppInfo(); } - try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, control, sendDummyByte)) { + try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, control, sendDummyByte)) { VideoCodec codec = options.getCodec(); if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); @@ -158,12 +158,12 @@ public final class Server { String key = arg.substring(0, equalIndex); String value = arg.substring(equalIndex + 1); switch (key) { - case "uid": - int uid = Integer.parseInt(value, 0x10); - if (uid < -1) { - throw new IllegalArgumentException("uid may not be negative (except -1 for 'none'): " + uid); + case "scid": + int scid = Integer.parseInt(value, 0x10); + if (scid < -1) { + throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid); } - options.setUid(uid); + options.setScid(scid); break; case "log_level": Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); From f4e7085c349fe79fa5744957e26d18a4b035e0c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Feb 2023 08:32:59 +0100 Subject: [PATCH 0647/1133] Log non-EPIPE I/O exceptions On close, the client closes the socket. This wakes up socket blocking calls on the server-side, by throwing an exception. Since this exception is expected, it was not logged. However, other IOExceptions might occur, which must not be ignored. For that purpose, log only IOException when they are not caused by an EPIPE error. --- server/src/main/java/com/genymobile/scrcpy/IO.java | 5 +++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/IO.java b/server/src/main/java/com/genymobile/scrcpy/IO.java index 6eaf0d6a..4a55c152 100644 --- a/server/src/main/java/com/genymobile/scrcpy/IO.java +++ b/server/src/main/java/com/genymobile/scrcpy/IO.java @@ -48,4 +48,9 @@ public final class IO { } return builder.toString(); } + + public static boolean isBrokenPipe(IOException e) { + Throwable cause = e.getCause(); + return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d7a99576..0aff79bc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -110,7 +110,10 @@ public final class Server { } screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { - // this is expected on close + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Video encoding error", e); + } } finally { Ln.d("Screen streaming stopped"); initThread.interrupt(); From 680ddf64bed1d5f225176d3805ab766741d6299e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 09:30:57 +0100 Subject: [PATCH 0648/1133] Fix demuxer error message Now that there are several possible codecs, do not hardcode H.264 in the error message. Refs 3e517cd40eb50f8afb5137f39b0676fead58902a --- app/src/demuxer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 282c07de..3b26415b 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -179,7 +179,7 @@ run_demuxer(void *data) { const AVCodec *codec = avcodec_find_decoder(codec_id); if (!codec) { - LOGE("H.264 decoder not found"); + LOGE("Decoder not found"); goto end; } From e91618586ca822e02de3a72fafccc962478ca79b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 09:37:13 +0100 Subject: [PATCH 0649/1133] Prefix receiver by sc_ Like all other components in scrcpy. --- app/src/controller.c | 12 ++++++------ app/src/controller.h | 2 +- app/src/receiver.c | 14 +++++++------- app/src/receiver.h | 14 +++++++------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index cdf53edb..4a1d2b1d 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -9,20 +9,20 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket, struct sc_acksync *acksync) { cbuf_init(&controller->queue); - bool ok = receiver_init(&controller->receiver, control_socket, acksync); + bool ok = sc_receiver_init(&controller->receiver, control_socket, acksync); if (!ok) { return false; } ok = sc_mutex_init(&controller->mutex); if (!ok) { - receiver_destroy(&controller->receiver); + sc_receiver_destroy(&controller->receiver); return false; } ok = sc_cond_init(&controller->msg_cond); if (!ok) { - receiver_destroy(&controller->receiver); + sc_receiver_destroy(&controller->receiver); sc_mutex_destroy(&controller->mutex); return false; } @@ -43,7 +43,7 @@ sc_controller_destroy(struct sc_controller *controller) { sc_control_msg_destroy(&msg); } - receiver_destroy(&controller->receiver); + sc_receiver_destroy(&controller->receiver); } bool @@ -117,7 +117,7 @@ sc_controller_start(struct sc_controller *controller) { return false; } - if (!receiver_start(&controller->receiver)) { + if (!sc_receiver_start(&controller->receiver)) { sc_controller_stop(controller); sc_thread_join(&controller->thread, NULL); return false; @@ -137,5 +137,5 @@ sc_controller_stop(struct sc_controller *controller) { void sc_controller_join(struct sc_controller *controller) { sc_thread_join(&controller->thread, NULL); - receiver_join(&controller->receiver); + sc_receiver_join(&controller->receiver); } diff --git a/app/src/controller.h b/app/src/controller.h index f8662353..67c3c58d 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -21,7 +21,7 @@ struct sc_controller { sc_cond msg_cond; bool stopped; struct sc_control_msg_queue queue; - struct receiver receiver; + struct sc_receiver receiver; }; bool diff --git a/app/src/receiver.c b/app/src/receiver.c index 0376948d..e715a8e6 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -7,7 +7,7 @@ #include "util/log.h" bool -receiver_init(struct receiver *receiver, sc_socket control_socket, +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, struct sc_acksync *acksync) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { @@ -21,12 +21,12 @@ receiver_init(struct receiver *receiver, sc_socket control_socket, } void -receiver_destroy(struct receiver *receiver) { +sc_receiver_destroy(struct sc_receiver *receiver) { sc_mutex_destroy(&receiver->mutex); } static void -process_msg(struct receiver *receiver, struct device_msg *msg) { +process_msg(struct sc_receiver *receiver, struct device_msg *msg) { switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { char *current = SDL_GetClipboardText(); @@ -51,7 +51,7 @@ process_msg(struct receiver *receiver, struct device_msg *msg) { } static ssize_t -process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) { +process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) { size_t head = 0; for (;;) { struct device_msg msg; @@ -76,7 +76,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) { static int run_receiver(void *data) { - struct receiver *receiver = data; + struct sc_receiver *receiver = data; static unsigned char buf[DEVICE_MSG_MAX_SIZE]; size_t head = 0; @@ -108,7 +108,7 @@ run_receiver(void *data) { } bool -receiver_start(struct receiver *receiver) { +sc_receiver_start(struct sc_receiver *receiver) { LOGD("Starting receiver thread"); bool ok = sc_thread_create(&receiver->thread, run_receiver, @@ -122,6 +122,6 @@ receiver_start(struct receiver *receiver) { } void -receiver_join(struct receiver *receiver) { +sc_receiver_join(struct sc_receiver *receiver) { sc_thread_join(&receiver->thread, NULL); } diff --git a/app/src/receiver.h b/app/src/receiver.h index f5808e4b..eb959fb8 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -11,7 +11,7 @@ // receive events from the device // managed by the controller -struct receiver { +struct sc_receiver { sc_socket control_socket; sc_thread thread; sc_mutex mutex; @@ -20,18 +20,18 @@ struct receiver { }; bool -receiver_init(struct receiver *receiver, sc_socket control_socket, - struct sc_acksync *acksync); +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, + struct sc_acksync *acksync); void -receiver_destroy(struct receiver *receiver); +sc_receiver_destroy(struct sc_receiver *receiver); bool -receiver_start(struct receiver *receiver); +sc_receiver_start(struct sc_receiver *receiver); -// no receiver_stop(), it will automatically stop on control_socket shutdown +// no sc_receiver_stop(), it will automatically stop on control_socket shutdown void -receiver_join(struct receiver *receiver); +sc_receiver_join(struct sc_receiver *receiver); #endif From 280a9afda8d44cf2450a5365fbb1072f45a4ad6f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:11:13 +0100 Subject: [PATCH 0650/1133] Fix command-line help typo --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 27b0ddef..94fccb67 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -102,7 +102,7 @@ static const struct sc_option options[] = { .shortopt = 'b', .longopt = "bit-rate", .argdesc = "value", - .text = "Encode the video at the gitven bit-rate, expressed in bits/s. " + .text = "Encode the video at the given bit-rate, expressed in bits/s. " "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is " STR(DEFAULT_BIT_RATE) ".", }, From 25e2eb7d7c21b65ad0c206f63bbadb0a19a890c6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 19:09:26 +0100 Subject: [PATCH 0651/1133] Document default video codec Mention the default option value, like for other commands. --- app/scrcpy.1 | 2 ++ app/src/cli.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 18c70d65..8f028d7c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -29,6 +29,8 @@ Default is 8000000. .BI "\-\-codec " name Select a video codec (h264, h265 or av1). +Default is h264. + .TP .BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] Set a list of comma-separated key:type=value options for the device encoder. diff --git a/app/src/cli.c b/app/src/cli.c index 94fccb67..93712113 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -110,7 +110,8 @@ static const struct sc_option options[] = { .longopt_id = OPT_CODEC, .longopt = "codec", .argdesc = "name", - .text = "Select a video codec (h264, h265 or av1).", + .text = "Select a video codec (h264, h265 or av1).\n" + "Default is h264.", }, { .longopt_id = OPT_CODEC_OPTIONS, From e02f30f8957adf09987c5bf2c7449f8241de7c8e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 02:10:14 +0100 Subject: [PATCH 0652/1133] Remove unnecessary error logs When a call to a packet or frame sink fails, do not log the error on the caller side: either the "failure" is expected (explicitly stopped) or it must be logged by the packet or frame sink implementation. --- app/src/decoder.c | 3 --- app/src/demuxer.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 9424ec1d..337aa329 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -29,7 +29,6 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) { for (unsigned i = 0; i < decoder->sink_count; ++i) { struct sc_frame_sink *sink = decoder->sinks[i]; if (!sink->ops->open(sink)) { - LOGE("Could not open frame sink %d", i); sc_decoder_close_first_sinks(decoder, i); return false; } @@ -63,7 +62,6 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { } if (!sc_decoder_open_sinks(decoder)) { - LOGE("Could not open decoder sinks"); av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); @@ -86,7 +84,6 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) { for (unsigned i = 0; i < decoder->sink_count; ++i) { struct sc_frame_sink *sink = decoder->sinks[i]; if (!sink->ops->push(sink, frame)) { - LOGE("Could not send frame to sink %d", i); return false; } } diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 3b26415b..c83d6bfa 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -111,7 +111,6 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { for (unsigned i = 0; i < demuxer->sink_count; ++i) { struct sc_packet_sink *sink = demuxer->sinks[i]; if (!sink->ops->push(sink, packet)) { - LOGE("Could not send packet to sink %d", i); return false; } } @@ -148,7 +147,6 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) { for (unsigned i = 0; i < demuxer->sink_count; ++i) { struct sc_packet_sink *sink = demuxer->sinks[i]; if (!sink->ops->open(sink, codec)) { - LOGE("Could not open packet sink %d", i); sc_demuxer_close_first_sinks(demuxer, i); return false; } @@ -184,7 +182,6 @@ run_demuxer(void *data) { } if (!sc_demuxer_open_sinks(demuxer, codec)) { - LOGE("Could not open demuxer sinks"); goto end; } From 5cf86ef7ffb374a8a1d0426b801c97f759375f5d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 15:59:05 +0100 Subject: [PATCH 0653/1133] Move finally-block to fix deadlock on stop DesktopConnection implements Closeable, so it is implicitly closed after its try-with-resources block. Closing the DesktopConnection shutdowns the sockets, so it is necessary in particular to wake up blocking read() calls from the controller. But the controller thread was joined before the DesktopConnection was closed, causing a deadlock. To fix the problem, join the controller thread only after the DesktopConnection is closed. Refs 400a1c69b198103feff9682895529529890c1acc --- .../java/com/genymobile/scrcpy/Server.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 0aff79bc..4f9fd55f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -84,6 +84,8 @@ public final class Server { Workarounds.fillAppInfo(); } + Controller controller = null; + try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, control, sendDummyByte)) { VideoCodec codec = options.getCodec(); if (options.getSendDeviceMeta()) { @@ -93,7 +95,6 @@ public final class Server { ScreenEncoder screenEncoder = new ScreenEncoder(codec.getMimeType(), options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); - Controller controller = null; if (control) { controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); controller.start(); @@ -114,21 +115,21 @@ public final class Server { if (!IO.isBrokenPipe(e)) { Ln.e("Video encoding error", e); } - } finally { - Ln.d("Screen streaming stopped"); - initThread.interrupt(); - if (controller != null) { - controller.stop(); - } + } + } finally { + Ln.d("Screen streaming stopped"); + initThread.interrupt(); + if (controller != null) { + controller.stop(); + } - try { - initThread.join(); - if (controller != null) { - controller.join(); - } - } catch (InterruptedException e) { - // ignore + try { + initThread.join(); + if (controller != null) { + controller.join(); } + } catch (InterruptedException e) { + // ignore } } } From d5dff239c858d22e8f8aad7dc51524e149561299 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 15:30:46 +0100 Subject: [PATCH 0654/1133] Suggest commands with an explicit '=' --- server/src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4f9fd55f..5a092061 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -306,7 +306,7 @@ public final class Server { if (displayIds != null && displayIds.length > 0) { Ln.e("Try to use one of the available display ids:"); for (int id : displayIds) { - Ln.e(" scrcpy --display " + id); + Ln.e(" scrcpy --display=" + id); } } } else if (e instanceof InvalidEncoderException) { @@ -315,7 +315,7 @@ public final class Server { if (encoders != null && encoders.length > 0) { Ln.e("Try to use one of the available encoders:"); for (MediaCodecInfo encoder : encoders) { - Ln.e(" scrcpy --encoder '" + encoder.getName() + "'"); + Ln.e(" scrcpy --encoder='" + encoder.getName() + "'"); } } } From ebecbe6bc66f6d2c5d7fb18fe37641fdacb0a8df Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 16:08:08 +0100 Subject: [PATCH 0655/1133] Fix inconsistent quotes The encoder name started with a simple quote but ended with a double quote. Use a single quote for both. --- .../java/com/genymobile/scrcpy/InvalidEncoderException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java index 1efd2989..b38e29b1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java +++ b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java @@ -8,7 +8,7 @@ public class InvalidEncoderException extends RuntimeException { private final MediaCodecInfo[] availableEncoders; public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) { - super("There is no encoder having name '" + name + '"'); + super("There is no encoder having name '" + name + "'"); this.name = name; this.availableEncoders = availableEncoders; } From 0a151b96fe7f2331a75306341369acc3dc638ebf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Feb 2023 20:48:59 +0100 Subject: [PATCH 0656/1133] Accept muxing AV1 into MP4 container MP4 supports AV1. Refs d2dce5103832d6a9fc29f56a9795c22e44ceafa1 --- app/src/cli.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 93712113..12c4b701 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1749,13 +1749,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } - if (opts->record_format == SC_RECORD_FORMAT_MP4 - && opts->codec == SC_CODEC_AV1) { - LOGE("Could not mux AV1 stream into MP4 container " - "(record to mkv or select another video codec)"); - return false; - } - if (!opts->control) { if (opts->turn_screen_off) { LOGE("Could not request to turn screen off if control is disabled"); From 5973d4cdd7a355b6e73997e0a7d95bc41bc1e107 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 12:09:42 +0100 Subject: [PATCH 0657/1133] Initialize mouse_input_mode explicitly The explicit initialization was missing. It had no consequences because SC_MOUSE_INPUT_MODE_INJECT == 0. Fixes #3749 --- app/src/options.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/options.c b/app/src/options.c index 525795ae..a75e584e 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -16,6 +16,7 @@ const struct scrcpy_options scrcpy_options_default = { .codec = SC_CODEC_H264, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, + .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .port_range = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST, From 5d6bcc59662e520b48362163d65e4dabaaada965 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 19:47:27 +0100 Subject: [PATCH 0658/1133] Use enum for long options constants This avoids to manually assign values. --- app/src/cli.c | 84 ++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 12c4b701..1851bad6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -17,47 +17,49 @@ #define STR_IMPL_(x) #x #define STR(x) STR_IMPL_(x) -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 -#define OPT_LOCK_VIDEO_ORIENTATION 1013 -#define OPT_DISPLAY_ID 1014 -#define OPT_ROTATION 1015 -#define OPT_RENDER_DRIVER 1016 -#define OPT_NO_MIPMAPS 1017 -#define OPT_CODEC_OPTIONS 1018 -#define OPT_FORCE_ADB_FORWARD 1019 -#define OPT_DISABLE_SCREENSAVER 1020 -#define OPT_SHORTCUT_MOD 1021 -#define OPT_NO_KEY_REPEAT 1022 -#define OPT_FORWARD_ALL_CLICKS 1023 -#define OPT_LEGACY_PASTE 1024 -#define OPT_ENCODER_NAME 1025 -#define OPT_POWER_OFF_ON_CLOSE 1026 -#define OPT_V4L2_SINK 1027 -#define OPT_DISPLAY_BUFFER 1028 -#define OPT_V4L2_BUFFER 1029 -#define OPT_TUNNEL_HOST 1030 -#define OPT_TUNNEL_PORT 1031 -#define OPT_NO_CLIPBOARD_AUTOSYNC 1032 -#define OPT_TCPIP 1033 -#define OPT_RAW_KEY_EVENTS 1034 -#define OPT_NO_DOWNSIZE_ON_ERROR 1035 -#define OPT_OTG 1036 -#define OPT_NO_CLEANUP 1037 -#define OPT_PRINT_FPS 1038 -#define OPT_NO_POWER_ON 1039 -#define OPT_CODEC 1040 +enum { + OPT_RENDER_EXPIRED_FRAMES = 1000, + OPT_WINDOW_TITLE, + OPT_PUSH_TARGET, + OPT_ALWAYS_ON_TOP, + OPT_CROP, + OPT_RECORD_FORMAT, + OPT_PREFER_TEXT, + OPT_WINDOW_X, + OPT_WINDOW_Y, + OPT_WINDOW_WIDTH, + OPT_WINDOW_HEIGHT, + OPT_WINDOW_BORDERLESS, + OPT_MAX_FPS, + OPT_LOCK_VIDEO_ORIENTATION, + OPT_DISPLAY_ID, + OPT_ROTATION, + OPT_RENDER_DRIVER, + OPT_NO_MIPMAPS, + OPT_CODEC_OPTIONS, + OPT_FORCE_ADB_FORWARD, + OPT_DISABLE_SCREENSAVER, + OPT_SHORTCUT_MOD, + OPT_NO_KEY_REPEAT, + OPT_FORWARD_ALL_CLICKS, + OPT_LEGACY_PASTE, + OPT_ENCODER_NAME, + OPT_POWER_OFF_ON_CLOSE, + OPT_V4L2_SINK, + OPT_DISPLAY_BUFFER, + OPT_V4L2_BUFFER, + OPT_TUNNEL_HOST, + OPT_TUNNEL_PORT, + OPT_NO_CLIPBOARD_AUTOSYNC, + OPT_TCPIP, + OPT_RAW_KEY_EVENTS, + OPT_NO_DOWNSIZE_ON_ERROR, + OPT_OTG, + OPT_NO_CLEANUP, + OPT_PRINT_FPS, + OPT_NO_POWER_ON, + OPT_CODEC, +}; struct sc_option { char shortopt; From 3e3756a323c947678daa753cd1b8e0b110a833d1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 19:48:28 +0100 Subject: [PATCH 0659/1133] Add auto-completion for --codec option Add missing command to bash and zsh completion scripts. --- app/data/bash-completion/scrcpy | 5 +++++ app/data/zsh-completion/_scrcpy | 1 + 2 files changed, 6 insertions(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0d3a2559..61573770 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,6 +3,7 @@ _scrcpy() { local opts=" --always-on-top -b --bit-rate= + --codec= --codec-options= --crop= -d --select-usb @@ -64,6 +65,10 @@ _scrcpy() { _init_completion -s || return case "$prev" in + --codec) + COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 56c13fd0..c57111cc 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,6 +10,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' {-b,--bit-rate=}'[Encode the video at the given bit-rate]' + '--codec=[Select the video codec]:codec:(h264 h265 av1)' '--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' From 3d10fbd9b4b9451ecca5d7305d1e5c42416d63ad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 19:50:01 +0100 Subject: [PATCH 0660/1133] Fix --bit-rate option in bash completion script The option is --bit-rate, not --bitrate. --- app/data/bash-completion/scrcpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 61573770..0ddd8bcb 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -103,7 +103,7 @@ _scrcpy() { COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur})) return ;; - -b|--bitrate \ + -b|--bit-rate \ |--codec-options \ |--crop \ |--display \ From 0cea7fb24cf83b04797a96f579fbc34ab8c3184b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 19:54:43 +0100 Subject: [PATCH 0661/1133] Fix WSAStartup() error check on Windows --- app/src/util/net.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index ed4882cd..c762a10f 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -30,8 +30,8 @@ bool net_init(void) { #ifdef _WIN32 WSADATA wsa; - int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; - if (res < 0) { + int res = WSAStartup(MAKEWORD(2, 2), &wsa); + if (res) { LOGE("WSAStartup failed with error %d", res); return false; } From 0702be86d8b0dbdba2a74f4a0638f40457a30e51 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 19:58:20 +0100 Subject: [PATCH 0662/1133] Accept Windows Sockets from version 1.1 Version 2.2 is probably not necessary (1.1 is the version required by FFmpeg when network is enabled). Refs Refs --- app/src/util/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/util/net.c b/app/src/util/net.c index c762a10f..67317ead 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -30,7 +30,7 @@ bool net_init(void) { #ifdef _WIN32 WSADATA wsa; - int res = WSAStartup(MAKEWORD(2, 2), &wsa); + int res = WSAStartup(MAKEWORD(1, 1), &wsa); if (res) { LOGE("WSAStartup failed with error %d", res); return false; From 8e8b039a633a848bd2498cb6c937ba5d99972cc2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 20:03:04 +0100 Subject: [PATCH 0663/1133] Do not use avformat network Scrcpy does not use FFmpeg network features. Initialize network locally instead (useful only for Windows). The include block has been moved to fix the following warning: Please include winsock2.h before windows.h --- app/src/main.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index b3a468cc..185f1d8f 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -4,10 +4,6 @@ #include #include #include -#ifdef _WIN32 -#include -#include "util/str.h" -#endif #ifdef HAVE_V4L2 # include #endif @@ -19,8 +15,14 @@ #include "scrcpy.h" #include "usb/scrcpy_otg.h" #include "util/log.h" +#include "util/net.h" #include "version.h" +#ifdef _WIN32 +#include +#include "util/str.h" +#endif + int main_scrcpy(int argc, char *argv[]) { #ifdef _WIN32 @@ -69,7 +71,7 @@ main_scrcpy(int argc, char *argv[]) { } #endif - if (avformat_network_init()) { + if (!net_init()) { return SCRCPY_EXIT_FAILURE; } @@ -80,8 +82,6 @@ main_scrcpy(int argc, char *argv[]) { enum scrcpy_exit_code ret = scrcpy(&args.opts); #endif - avformat_network_deinit(); // ignore failure - return ret; } From 6b422e21bf82d5b2a7913598d14af2da5573173e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 20:51:12 +0100 Subject: [PATCH 0664/1133] Fix error message on icon loading failure --- app/src/icon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/icon.c b/app/src/icon.c index e709678f..a8588dd8 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -69,7 +69,7 @@ decode_image(const char *path) { } if (avformat_open_input(&ctx, path, NULL, NULL) < 0) { - LOGE("Could not open image codec: %s", path); + LOGE("Could not open icon image: %s", path); goto free_ctx; } From 3c3c07db05088963210c3522b21069dbe72d429c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 12:34:34 +0100 Subject: [PATCH 0665/1133] Initialize server->serial in all cases Running scrcpy --tcpip on a device already connected via TCP/IP did not initialize server->serial. As a consequence, in debug mode, an assertion failed: scrcpy: ../app/src/server.c:770: run_server: Assertion `server->serial' failed. In release mode, scrcpy failed with this error: adb: -s requires an argument --- app/src/server.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/server.c b/app/src/server.c index 96bff77e..83c177dc 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -689,6 +689,11 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server, if (is_already_tcpip) { // Nothing to do LOGI("Device already connected via TCP/IP: %s", serial); + server->serial = strdup(serial); + if (!server->serial) { + LOG_OOM(); + return false; + } return true; } From 389dd77b5060af9989527729bad5f72c0818d2e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 23:52:26 +0100 Subject: [PATCH 0666/1133] Fix MIN/MAX macros Expressions like "x < MAX(y, z)" were broken. --- app/src/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/common.h b/app/src/common.h index dccc8316..0382d094 100644 --- a/app/src/common.h +++ b/app/src/common.h @@ -5,8 +5,8 @@ #include "compat.h" #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) -#define MIN(X,Y) (X) < (Y) ? (X) : (Y) -#define MAX(X,Y) (X) > (Y) ? (X) : (Y) +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) #define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) ) #define container_of(ptr, type, member) \ From b5d41ad4f6017cf76fb7fad9e1af919eac34330f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 11:03:48 +0100 Subject: [PATCH 0667/1133] Fix useless garbage initialization The variable `p` was initialized with a garbage value (a `const char **` casted to `char *`). Fortunately, it was never read. Refs --- app/src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 83c177dc..c9164972 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -206,7 +206,7 @@ execute_server(struct sc_server *server, unsigned dyn_idx = count; // from there, the strings are allocated #define ADD_PARAM(fmt, ...) { \ - char *p = (char *) &cmd[count]; \ + char *p; \ if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \ goto end; \ } \ From a252194161a20b7aaf30e4f4a16f99f0057ac9e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Feb 2023 22:09:54 +0100 Subject: [PATCH 0668/1133] Upgrade gradle build tools to 7.4.0 Plugin version 7.4.0. Gradle version 7.5. Refs --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ecc7972e..f7e29b22 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:7.4.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 669386b8..2ec77e51 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 5bf52a98ed701ca8bb9ce1b9e769acedf9fa3315 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Feb 2023 22:40:26 +0100 Subject: [PATCH 0669/1133] Remove manifest package name As reported by gradle: > Setting the namespace via a source AndroidManifest.xml's package > attribute is deprecated. > > Please instead set the namespace (or testNamespace) in the module's > build.gradle file, as described here: > https://developer.android.com/studio/build/configure-app-module#set-namespace --- server/build.gradle | 1 + server/src/main/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index 44bd78e8..1d80d15f 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' android { + namespace 'com.genymobile.scrcpy' compileSdkVersion 33 defaultConfig { applicationId "com.genymobile.scrcpy" diff --git a/server/src/main/AndroidManifest.xml b/server/src/main/AndroidManifest.xml index ccd69d2f..a94ad86b 100644 --- a/server/src/main/AndroidManifest.xml +++ b/server/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + From 14a85fd61e8fba2f4cfbf97dfd639d19b7adb70e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Feb 2023 22:36:13 +0100 Subject: [PATCH 0670/1133] Silence lint warning about constant in API 29 MediaFormat.MIMETYPE_VIDEO_AV1 has been added in API 29, but it is not a problem to inline the constant in older versions. --- server/src/main/java/com/genymobile/scrcpy/VideoCodec.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java index 809d7dbc..e19b27f0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -1,10 +1,12 @@ package com.genymobile.scrcpy; +import android.annotation.SuppressLint; import android.media.MediaFormat; public enum VideoCodec { H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), + @SuppressLint("InlinedApi") // introduced in API 21 AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1); private final int id; // 4-byte ASCII representation of the name From a20615066d435cd3160f80196d25a2d7fb3e8ccc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 21:43:34 +0100 Subject: [PATCH 0671/1133] Simplify libusb prebuilt scripts In theory, include/ might be slightly different for win32 and win64 builds. Use each one separately to simplify. --- app/meson.build | 5 ++--- app/prebuilt-deps/prepare-libusb.sh | 7 +++---- cross_win32.txt | 3 +-- cross_win64.txt | 3 +-- release.mk | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/meson.build b/app/meson.build index 5d779756..a16a000b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -144,9 +144,8 @@ else ) prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') - prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root') - libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb - libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include' + libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/bin' + libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb + '/include' libusb = declare_dependency( dependencies: [ diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh index a0c3721d..47cf1df4 100755 --- a/app/prebuilt-deps/prepare-libusb.sh +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -22,13 +22,12 @@ get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" mkdir "$DEP_DIR" cd "$DEP_DIR" -# include/ is the same in all folders of the archive 7z x "../$FILENAME" \ libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \ + libusb-1.0.26-binaries/libusb-MinGW-Win32/include/ \ libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \ libusb-1.0.26-binaries/libusb-MinGW-x64/include/ -mv libusb-1.0.26-binaries/libusb-MinGW-Win32/bin MinGW-Win32 -mv libusb-1.0.26-binaries/libusb-MinGW-x64/bin MinGW-x64 -mv libusb-1.0.26-binaries/libusb-MinGW-x64/include . +mv libusb-1.0.26-binaries/libusb-MinGW-Win32 . +mv libusb-1.0.26-binaries/libusb-MinGW-x64 . rm -rf libusb-1.0.26-binaries diff --git a/cross_win32.txt b/cross_win32.txt index 32226949..e50c0bc8 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -21,5 +21,4 @@ ffmpeg_avformat = 'avformat-58' ffmpeg_avutil = 'avutil-56' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' -prebuilt_libusb_root = 'libusb-1.0.26' -prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32' +prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 4dde4ab1..2dc876a6 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -21,5 +21,4 @@ ffmpeg_avformat = 'avformat-59' ffmpeg_avutil = 'avutil-57' prebuilt_ffmpeg = 'ffmpeg-win64-5.1.2' prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' -prebuilt_libusb_root = 'libusb-1.0.26' -prebuilt_libusb = 'libusb-1.0.26/MinGW-x64' +prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 06443e1a..67578022 100644 --- a/release.mk +++ b/release.mk @@ -109,7 +109,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.26.1/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-Win32/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -128,7 +128,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.26.1/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.26/MinGW-x64/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)"; \ From 0fc62bfcd63fdccba759c1421ca7e4b5a2278cb9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 21:43:48 +0100 Subject: [PATCH 0672/1133] Use minimal prebuilt FFmpeg for Windows On the scrcpy-deps repo, I built FFmpeg 5.1.2 binaries for Windows with only the features used by scrcpy. For comparison, here are the sizes of the dll for FFmpeg 5.1.2: - before: 89M - after: 4.7M It also allows to upgrade the old FFmpeg version (4.3.1) used for win32. Refs Refs --- app/meson.build | 11 ++---- app/prebuilt-deps/prepare-ffmpeg-win32.sh | 45 ----------------------- app/prebuilt-deps/prepare-ffmpeg-win64.sh | 36 ------------------ app/prebuilt-deps/prepare-ffmpeg.sh | 30 +++++++++++++++ cross_win32.txt | 5 +-- cross_win64.txt | 5 +-- release.mk | 36 ++++++++---------- 7 files changed, 50 insertions(+), 118 deletions(-) delete mode 100755 app/prebuilt-deps/prepare-ffmpeg-win32.sh delete mode 100755 app/prebuilt-deps/prepare-ffmpeg-win64.sh create mode 100755 app/prebuilt-deps/prepare-ffmpeg.sh diff --git a/app/meson.build b/app/meson.build index a16a000b..f070db72 100644 --- a/app/meson.build +++ b/app/meson.build @@ -129,16 +129,11 @@ else ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' - # ffmpeg versions are different for win32 and win64 builds - ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec') - ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat') - ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil') - ffmpeg = declare_dependency( dependencies: [ - cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir), - cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir), - cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir), + cc.find_library('avcodec-59', dirs: ffmpeg_bin_dir), + cc.find_library('avformat-59', dirs: ffmpeg_bin_dir), + cc.find_library('avutil-57', dirs: ffmpeg_bin_dir), ], include_directories: include_directories(ffmpeg_include_dir) ) diff --git a/app/prebuilt-deps/prepare-ffmpeg-win32.sh b/app/prebuilt-deps/prepare-ffmpeg-win32.sh deleted file mode 100755 index 2a6a3841..00000000 --- a/app/prebuilt-deps/prepare-ffmpeg-win32.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -DEP_DIR=ffmpeg-win32-4.3.1 - -FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip -SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 - -FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip -SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \ - "$FILENAME_SHARED" "$SHA256SUM_SHARED" -get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \ - "$FILENAME_DEV" "$SHA256SUM_DEV" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared -unzip "../$FILENAME_SHARED" \ - "$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \ - "$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \ - "$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \ - "$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \ - "$ZIP_PREFIX_SHARED"/bin/swscale-5.dll - -ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev -unzip "../$FILENAME_DEV" \ - "$ZIP_PREFIX_DEV/include/*" - -mv "$ZIP_PREFIX_SHARED"/* . -mv "$ZIP_PREFIX_DEV"/* . -rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV" diff --git a/app/prebuilt-deps/prepare-ffmpeg-win64.sh b/app/prebuilt-deps/prepare-ffmpeg-win64.sh deleted file mode 100755 index f5d56e6f..00000000 --- a/app/prebuilt-deps/prepare-ffmpeg-win64.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -VERSION=5.1.2 -DEP_DIR=ffmpeg-win64-$VERSION - -FILENAME=ffmpeg-$VERSION-full_build-shared.7z -SHA256SUM=d9eb97b72d7cfdae4d0f7eaea59ccffb8c364d67d88018ea715d5e2e193f00e9 - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://github.com/GyanD/codexffmpeg/releases/download/$VERSION/$FILENAME" \ - "$FILENAME" "$SHA256SUM" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared -7z x "../$FILENAME" \ - "$ZIP_PREFIX"/bin/avutil-57.dll \ - "$ZIP_PREFIX"/bin/avcodec-59.dll \ - "$ZIP_PREFIX"/bin/avformat-59.dll \ - "$ZIP_PREFIX"/bin/swresample-4.dll \ - "$ZIP_PREFIX"/bin/swscale-6.dll \ - "$ZIP_PREFIX"/include -mv "$ZIP_PREFIX"/* . -rmdir "$ZIP_PREFIX" diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh new file mode 100755 index 00000000..dc8b1ca2 --- /dev/null +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e +DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DIR" +. common +mkdir -p "$PREBUILT_DATA_DIR" +cd "$PREBUILT_DATA_DIR" + +VERSION=5.1.2-scrcpy +DEP_DIR="ffmpeg-$VERSION" + +FILENAME="$DEP_DIR".7z +SHA256SUM=93f32ffc29ddb3466d669f7078d3fd8030c3388bc8a18bcfeefb6428fc5ceef1 + +if [[ -d "$DEP_DIR" ]] +then + echo "$DEP_DIR" found + exit 0 +fi + +get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \ + "$FILENAME" "$SHA256SUM" + +mkdir "$DEP_DIR" +cd "$DEP_DIR" + +ZIP_PREFIX=ffmpeg +7z x "../$FILENAME" +mv "$ZIP_PREFIX"/* . +rmdir "$ZIP_PREFIX" diff --git a/cross_win32.txt b/cross_win32.txt index e50c0bc8..f89463d9 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,9 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -ffmpeg_avcodec = 'avcodec-58' -ffmpeg_avformat = 'avformat-58' -ffmpeg_avutil = 'avutil-56' -prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' +prebuilt_ffmpeg = 'ffmpeg-5.1.2-scrcpy/win32' prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 2dc876a6..c30c46f2 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,9 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -ffmpeg_avcodec = 'avcodec-59' -ffmpeg_avformat = 'avformat-59' -ffmpeg_avutil = 'avutil-57' -prebuilt_ffmpeg = 'ffmpeg-win64-5.1.2' +prebuilt_ffmpeg = 'ffmpeg-5.1.2-scrcpy/win64' prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 67578022..9c2820c7 100644 --- a/release.mk +++ b/release.mk @@ -11,7 +11,7 @@ .PHONY: default clean \ test \ build-server \ - prepare-deps-win32 prepare-deps-win64 \ + prepare-deps \ build-win32 build-win64 \ dist-win32 dist-win64 \ zip-win32 zip-win64 \ @@ -62,19 +62,13 @@ build-server: meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) ninja -C "$(SERVER_BUILD_DIR)" -prepare-deps-win32: +prepare-deps: @app/prebuilt-deps/prepare-adb.sh @app/prebuilt-deps/prepare-sdl.sh - @app/prebuilt-deps/prepare-ffmpeg-win32.sh + @app/prebuilt-deps/prepare-ffmpeg.sh @app/prebuilt-deps/prepare-libusb.sh -prepare-deps-win64: - @app/prebuilt-deps/prepare-adb.sh - @app/prebuilt-deps/prepare-sdl.sh - @app/prebuilt-deps/prepare-ffmpeg-win64.sh - @app/prebuilt-deps/prepare-libusb.sh - -build-win32: prepare-deps-win32 +build-win32: prepare-deps [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ meson setup "$(WIN32_BUILD_DIR)" \ --cross-file cross_win32.txt \ @@ -83,7 +77,7 @@ build-win32: prepare-deps-win32 -Dportable=true ) ninja -C "$(WIN32_BUILD_DIR)" -build-win64: prepare-deps-win64 +build-win64: prepare-deps [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ meson setup "$(WIN64_BUILD_DIR)" \ --cross-file cross_win64.txt \ @@ -100,11 +94,11 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avutil-57.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avcodec-59.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avformat-59.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -119,11 +113,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-win64-5.1.2/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 9d60d7880bdc467545c883ccfc8d2609927894cb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 11:56:42 +0100 Subject: [PATCH 0673/1133] Upgrade FFmpeg (6.0) for Windows Use the latest version (specifically built for scrcpy). Refs --- app/meson.build | 6 +++--- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 20 ++++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/meson.build b/app/meson.build index f070db72..b6a772a9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -131,9 +131,9 @@ else ffmpeg = declare_dependency( dependencies: [ - cc.find_library('avcodec-59', dirs: ffmpeg_bin_dir), - cc.find_library('avformat-59', dirs: ffmpeg_bin_dir), - cc.find_library('avutil-57', dirs: ffmpeg_bin_dir), + cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir), + cc.find_library('avformat-60', dirs: ffmpeg_bin_dir), + cc.find_library('avutil-58', dirs: ffmpeg_bin_dir), ], include_directories: include_directories(ffmpeg_include_dir) ) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index dc8b1ca2..10e33903 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=5.1.2-scrcpy +VERSION=6.0-scrcpy DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=93f32ffc29ddb3466d669f7078d3fd8030c3388bc8a18bcfeefb6428fc5ceef1 +SHA256SUM=f3956295b4325a84aada05447ba3f314fbed96697811666d495de4de40d59f98 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index f89463d9..deb70b77 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-5.1.2-scrcpy/win32' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win32' prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index c30c46f2..3c4409dc 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-5.1.2-scrcpy/win64' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win64' prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 9c2820c7..f1084bd4 100644 --- a/release.mk +++ b/release.mk @@ -94,11 +94,11 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avutil-57.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avcodec-59.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/avformat-59.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -113,11 +113,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-5.1.2-scrcpy/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From f30fd963a104649e7d388cc68f3fbc5d635baa4a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 19:46:13 +0100 Subject: [PATCH 0674/1133] Upgrade FFmpeg custom builds for Windows Use a build which includes the pcm_s16le decoder, to support RAW audio. Refs --- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index 10e33903..b156099a 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.0-scrcpy +VERSION=6.0-scrcpy-2 DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=f3956295b4325a84aada05447ba3f314fbed96697811666d495de4de40d59f98 +SHA256SUM=98ef97f8607c97a5c4f9c5a0a991b78f105d002a3619145011d16ffb92501b14 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index deb70b77..a02e798a 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win32' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win32' prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 3c4409dc..126de36e 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy/win64' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win64' prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index f1084bd4..75e5a9c0 100644 --- a/release.mk +++ b/release.mk @@ -94,11 +94,11 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -113,11 +113,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 10e8295aea0163f80d88eab234be5b918a7571eb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 12:49:42 +0100 Subject: [PATCH 0675/1133] Move FFmpeg callback initialization Configure FFmpeg log redirection on start from a log helper. --- app/src/main.c | 2 ++ app/src/scrcpy.c | 39 --------------------------------------- app/src/util/log.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ app/src/util/log.h | 3 +++ 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/app/src/main.c b/app/src/main.c index 185f1d8f..cc3a85a7 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -75,6 +75,8 @@ main_scrcpy(int argc, char *argv[]) { return SCRCPY_EXIT_FAILURE; } + sc_log_configure(); + #ifdef HAVE_USB enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts) : scrcpy(&args.opts); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8932dd1d..afd04d6d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -198,43 +198,6 @@ await_for_server(bool *connected) { return false; } -static SDL_LogPriority -sdl_priority_from_av_level(int level) { - switch (level) { - case AV_LOG_PANIC: - case AV_LOG_FATAL: - return SDL_LOG_PRIORITY_CRITICAL; - case AV_LOG_ERROR: - return SDL_LOG_PRIORITY_ERROR; - case AV_LOG_WARNING: - return SDL_LOG_PRIORITY_WARN; - case AV_LOG_INFO: - return SDL_LOG_PRIORITY_INFO; - } - // do not forward others, which are too verbose - return 0; -} - -static void -av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { - (void) avcl; - SDL_LogPriority priority = sdl_priority_from_av_level(level); - if (priority == 0) { - return; - } - - size_t fmt_len = strlen(fmt); - char *local_fmt = malloc(fmt_len + 10); - if (!local_fmt) { - LOG_OOM(); - return; - } - memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' - memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0' - SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); - free(local_fmt); -} - static void sc_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, void *userdata) { (void) demuxer; @@ -426,8 +389,6 @@ scrcpy(struct scrcpy_options *options) { recorder_initialized = true; } - av_log_set_callback(av_log_callback); - static const struct sc_demuxer_callbacks demuxer_cbs = { .on_ended = sc_demuxer_on_ended, }; diff --git a/app/src/util/log.c b/app/src/util/log.c index 72cd2877..ef11d2d1 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -4,6 +4,7 @@ # include #endif #include +#include static SDL_LogPriority log_level_sc_to_sdl(enum sc_log_level level) { @@ -85,3 +86,46 @@ sc_log_windows_error(const char *prefix, int error) { return true; } #endif + +static SDL_LogPriority +sdl_priority_from_av_level(int level) { + switch (level) { + case AV_LOG_PANIC: + case AV_LOG_FATAL: + return SDL_LOG_PRIORITY_CRITICAL; + case AV_LOG_ERROR: + return SDL_LOG_PRIORITY_ERROR; + case AV_LOG_WARNING: + return SDL_LOG_PRIORITY_WARN; + case AV_LOG_INFO: + return SDL_LOG_PRIORITY_INFO; + } + // do not forward others, which are too verbose + return 0; +} + +static void +sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { + (void) avcl; + SDL_LogPriority priority = sdl_priority_from_av_level(level); + if (priority == 0) { + return; + } + + size_t fmt_len = strlen(fmt); + char *local_fmt = malloc(fmt_len + 10); + if (!local_fmt) { + LOG_OOM(); + return; + } + memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' + memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0' + SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); + free(local_fmt); +} + +void +sc_log_configure() { + // Redirect FFmpeg logs to SDL logs + av_log_set_callback(sc_av_log_callback); +} diff --git a/app/src/util/log.h b/app/src/util/log.h index 6bd8506c..8e1b73a2 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -35,4 +35,7 @@ bool sc_log_windows_error(const char *prefix, int error); #endif +void +sc_log_configure(); + #endif From e30e692b3640e5e8db4fb5c31445194d1fad31b6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 13:25:04 +0100 Subject: [PATCH 0676/1133] Print FFmpeg logs FFmpeg logs are redirected to a specific SDL log category. Initialize the log level for this category to print them as expected. --- app/src/util/log.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/util/log.c b/app/src/util/log.c index ef11d2d1..25b1f26e 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -48,6 +48,7 @@ void sc_set_log_level(enum sc_log_level level) { SDL_LogPriority sdl_log = log_level_sc_to_sdl(level); SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log); + SDL_LogSetPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log); } enum sc_log_level @@ -120,7 +121,7 @@ sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { } memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0' - SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); + SDL_LogMessageV(SDL_LOG_CATEGORY_CUSTOM, priority, local_fmt, vl); free(local_fmt); } From c78254fcd1c8d88fbc258d2a50ee925c282c5c64 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 18:41:22 +0100 Subject: [PATCH 0677/1133] Split server stop() and join() For consistency with the other components, call stop() and join() separately. This allows to stop all components, then join them all. --- app/src/scrcpy.c | 4 ++++ app/src/server.c | 3 +++ app/src/server.h | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index afd04d6d..e96fa187 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -698,6 +698,10 @@ end: sc_file_pusher_destroy(&s->file_pusher); } + if (server_started) { + sc_server_join(&s->server); + } + sc_server_destroy(&s->server); return ret; diff --git a/app/src/server.c b/app/src/server.c index c9164972..413f02ee 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -909,7 +909,10 @@ sc_server_stop(struct sc_server *server) { sc_cond_signal(&server->cond_stopped); sc_intr_interrupt(&server->intr); sc_mutex_unlock(&server->mutex); +} +void +sc_server_join(struct sc_server *server) { sc_thread_join(&server->thread, NULL); } diff --git a/app/src/server.h b/app/src/server.h index d6b1401e..c05b1e5b 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -108,6 +108,10 @@ sc_server_start(struct sc_server *server); void sc_server_stop(struct sc_server *server); +// join the server thread +void +sc_server_join(struct sc_server *server); + // close and release sockets void sc_server_destroy(struct sc_server *server); From 9f8e96e895b8af51be2b1123a483c7d4d3decdf7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 19:08:24 +0100 Subject: [PATCH 0678/1133] Fix --no-clipboard-autosync bash completion Fix typo. --- app/data/bash-completion/scrcpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0ddd8bcb..4590b6a8 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -24,7 +24,7 @@ _scrcpy() { -M --hid-mouse -m --max-size= --no-cleanup - --no-clipboard-on-error + --no-clipboard-autosync --no-downsize-on-error -n --no-control -N --no-display From b43938fa66bb854e45c97a76b4600edecdb0fb9b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 19:36:46 +0100 Subject: [PATCH 0679/1133] Do not print stacktraces when unnecessary User-friendly error messages are printed on specific configuration exceptions. In that case, do not print the stacktrace. Also handle the user-friendly error message directly where the error occurs, and print multiline messages in a single log call, to avoid confusing interleaving. --- .../scrcpy/ConfigurationException.java | 7 ++++ .../java/com/genymobile/scrcpy/Device.java | 18 +++++++++-- .../scrcpy/InvalidDisplayIdException.java | 21 ------------ .../scrcpy/InvalidEncoderException.java | 23 ------------- .../com/genymobile/scrcpy/ScreenEncoder.java | 20 +++++++++--- .../java/com/genymobile/scrcpy/Server.java | 32 ++++--------------- 6 files changed, 44 insertions(+), 77 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/ConfigurationException.java delete mode 100644 server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java delete mode 100644 server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java diff --git a/server/src/main/java/com/genymobile/scrcpy/ConfigurationException.java b/server/src/main/java/com/genymobile/scrcpy/ConfigurationException.java new file mode 100644 index 00000000..76c8f52e --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/ConfigurationException.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy; + +public class ConfigurationException extends Exception { + public ConfigurationException(String message) { + super(message); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 30e64fd7..c7f7c1f8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -61,12 +61,12 @@ public final class Device { private final boolean supportsInputEvents; - public Device(Options options) { + public Device(Options options) throws ConfigurationException { displayId = options.getDisplayId(); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { - int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); - throw new InvalidDisplayIdException(displayId, displayIds); + Ln.e(buildUnknownDisplayIdMessage(displayId)); + throw new ConfigurationException("Unknown display id: " + displayId); } int displayInfoFlags = displayInfo.getFlags(); @@ -130,6 +130,18 @@ public final class Device { } } + private static String buildUnknownDisplayIdMessage(int displayId) { + StringBuilder msg = new StringBuilder("Display ").append(displayId).append(" not found"); + int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); + if (displayIds != null && displayIds.length > 0) { + msg.append("\nTry to use one of the available display ids:"); + for (int id : displayIds) { + msg.append("\n scrcpy --display=").append(id); + } + } + return msg.toString(); + } + public synchronized void setMaxSize(int newMaxSize) { maxSize = newMaxSize; screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java deleted file mode 100644 index 81e3b903..00000000 --- a/server/src/main/java/com/genymobile/scrcpy/InvalidDisplayIdException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.genymobile.scrcpy; - -public class InvalidDisplayIdException extends RuntimeException { - - private final int displayId; - private final int[] availableDisplayIds; - - public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) { - super("There is no display having id " + displayId); - this.displayId = displayId; - this.availableDisplayIds = availableDisplayIds; - } - - public int getDisplayId() { - return displayId; - } - - public int[] getAvailableDisplayIds() { - return availableDisplayIds; - } -} diff --git a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java b/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java deleted file mode 100644 index b38e29b1..00000000 --- a/server/src/main/java/com/genymobile/scrcpy/InvalidEncoderException.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.genymobile.scrcpy; - -import android.media.MediaCodecInfo; - -public class InvalidEncoderException extends RuntimeException { - - private final String name; - private final MediaCodecInfo[] availableEncoders; - - public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) { - super("There is no encoder having name '" + name + "'"); - this.name = name; - this.availableEncoders = availableEncoders; - } - - public String getName() { - return name; - } - - public MediaCodecInfo[] getAvailableEncoders() { - return availableEncoders; - } -} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index fed6f6c3..44ba75d1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -63,7 +63,7 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, Callbacks callbacks) throws IOException { + public void streamScreen(Device device, Callbacks callbacks) throws IOException, ConfigurationException { MediaCodec codec = createCodec(videoMimeType, encoderName); MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); @@ -207,14 +207,14 @@ public class ScreenEncoder implements Device.RotationListener { return result.toArray(new MediaCodecInfo[result.size()]); } - private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException { + private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException, ConfigurationException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - MediaCodecInfo[] encoders = listEncoders(videoMimeType); - throw new InvalidEncoderException(encoderName, encoders); + Ln.e(buildUnknownEncoderMessage(videoMimeType, encoderName)); + throw new ConfigurationException("Unknown encoder: " + encoderName); } } MediaCodec codec = MediaCodec.createEncoderByType(videoMimeType); @@ -222,6 +222,18 @@ public class ScreenEncoder implements Device.RotationListener { return codec; } + private static String buildUnknownEncoderMessage(String videoMimeType, String encoderName) { + StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' not found"); + MediaCodecInfo[] encoders = listEncoders(videoMimeType); + if (encoders != null && encoders.length > 0) { + msg.append("\nTry to use one of the available encoders:"); + for (MediaCodecInfo encoder : encoders) { + msg.append("\n scrcpy --encoder='").append(encoder.getName()).append("'"); + } + } + return msg.toString(); + } + private static void setCodecOption(MediaFormat format, CodecOption codecOption) { String key = codecOption.getKey(); Object value = codecOption.getValue(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5a092061..027050af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,7 +1,6 @@ package com.genymobile.scrcpy; import android.graphics.Rect; -import android.media.MediaCodecInfo; import android.os.BatteryManager; import android.os.Build; @@ -59,7 +58,7 @@ public final class Server { } } - private static void scrcpy(Options options) throws IOException { + private static void scrcpy(Options options) throws IOException, ConfigurationException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); List codecOptions = options.getCodecOptions(); @@ -299,38 +298,19 @@ public final class Server { return new Rect(x, y, x + width, y + height); } - private static void suggestFix(Throwable e) { - if (e instanceof InvalidDisplayIdException) { - InvalidDisplayIdException idie = (InvalidDisplayIdException) e; - int[] displayIds = idie.getAvailableDisplayIds(); - if (displayIds != null && displayIds.length > 0) { - Ln.e("Try to use one of the available display ids:"); - for (int id : displayIds) { - Ln.e(" scrcpy --display=" + id); - } - } - } else if (e instanceof InvalidEncoderException) { - InvalidEncoderException iee = (InvalidEncoderException) e; - MediaCodecInfo[] encoders = iee.getAvailableEncoders(); - if (encoders != null && encoders.length > 0) { - Ln.e("Try to use one of the available encoders:"); - for (MediaCodecInfo encoder : encoders) { - Ln.e(" scrcpy --encoder='" + encoder.getName() + "'"); - } - } - } - } - public static void main(String... args) throws Exception { Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Ln.e("Exception on thread " + t, e); - suggestFix(e); }); Options options = createOptions(args); Ln.initLogLevel(options.getLogLevel()); - scrcpy(options); + try { + scrcpy(options); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } } } From fa9976366895c1adbfdbf98fb624107e71d4b24e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:06:30 +0100 Subject: [PATCH 0680/1133] Fix --encoder documentation Mention that it depends on the codec provided by --codec (which is not necessarily H264 anymore). --- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index c57111cc..961565e7 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -18,7 +18,7 @@ arguments=( '--display=[Specify the display id to mirror]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' {-e,--select-tcpip}'[Use TCP/IP device]' - '--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]' + '--encoder=[Use a specific MediaCodec encoder]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-f,--fullscreen}'[Start in fullscreen]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 8f028d7c..0f1147da 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -82,7 +82,7 @@ Also see \fB\-d\fR (\fB\-\-select\-usb\fR). .TP .BI "\-\-encoder " name -Use a specific MediaCodec encoder (must be a H.264 encoder). +Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-codec\fR). .TP .B \-\-force\-adb\-forward diff --git a/app/src/cli.c b/app/src/cli.c index 1851bad6..ab460732 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -175,7 +175,8 @@ static const struct sc_option options[] = { .longopt_id = OPT_ENCODER_NAME, .longopt = "encoder", .argdesc = "name", - .text = "Use a specific MediaCodec encoder (must be a H.264 encoder).", + .text = "Use a specific MediaCodec encoder (depending on the codec " + "provided by --codec).", }, { .longopt_id = OPT_FORCE_ADB_FORWARD, From 181fb555bb72ac5ba5460db6ec8efbf7366a12f3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Feb 2023 08:41:03 +0100 Subject: [PATCH 0681/1133] Change PTS origin type from uint64_t to int64_t It is initialized from AVPacket.pts, which is an int64_t. --- app/src/recorder.c | 6 ++---- app/src/recorder.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 455e1db1..d75f1b12 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -11,8 +11,6 @@ /** Downcast packet_sink to recorder */ #define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink) -#define SC_PTS_ORIGIN_NONE UINT64_C(-1) - static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVOutputFormat * @@ -171,7 +169,7 @@ run_recorder(void *data) { sc_mutex_unlock(&recorder->mutex); - if (recorder->pts_origin == SC_PTS_ORIGIN_NONE + if (recorder->pts_origin == AV_NOPTS_VALUE && rec->packet->pts != AV_NOPTS_VALUE) { // First PTS received recorder->pts_origin = rec->packet->pts; @@ -257,7 +255,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { recorder->failed = false; recorder->header_written = false; recorder->previous = NULL; - recorder->pts_origin = SC_PTS_ORIGIN_NONE; + recorder->pts_origin = AV_NOPTS_VALUE; const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); diff --git a/app/src/recorder.h b/app/src/recorder.h index a03c91d7..e6c66f99 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -28,7 +28,7 @@ struct sc_recorder { struct sc_size declared_frame_size; bool header_written; - uint64_t pts_origin; + int64_t pts_origin; sc_thread thread; sc_mutex mutex; From b6744e788708e330ceef618d0961e1a2ce0a98e8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Feb 2023 08:46:11 +0100 Subject: [PATCH 0682/1133] Move pts_origin to a local variable It is only used from run_recorder(). --- app/src/recorder.c | 9 +++++---- app/src/recorder.h | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index d75f1b12..f71f6322 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -136,6 +136,8 @@ static int run_recorder(void *data) { struct sc_recorder *recorder = data; + int64_t pts_origin = AV_NOPTS_VALUE; + for (;;) { sc_mutex_lock(&recorder->mutex); @@ -169,15 +171,15 @@ run_recorder(void *data) { sc_mutex_unlock(&recorder->mutex); - if (recorder->pts_origin == AV_NOPTS_VALUE + if (pts_origin == AV_NOPTS_VALUE && rec->packet->pts != AV_NOPTS_VALUE) { // First PTS received - recorder->pts_origin = rec->packet->pts; + pts_origin = rec->packet->pts; } if (rec->packet->pts != AV_NOPTS_VALUE) { // Set PTS relatve to the origin - rec->packet->pts -= recorder->pts_origin; + rec->packet->pts -= pts_origin; rec->packet->dts = rec->packet->pts; } @@ -255,7 +257,6 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { recorder->failed = false; recorder->header_written = false; recorder->previous = NULL; - recorder->pts_origin = AV_NOPTS_VALUE; const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); diff --git a/app/src/recorder.h b/app/src/recorder.h index e6c66f99..373278e6 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -28,8 +28,6 @@ struct sc_recorder { struct sc_size declared_frame_size; bool header_written; - int64_t pts_origin; - sc_thread thread; sc_mutex mutex; sc_cond queue_cond; From db5751a76a9303e13adc470b45c0a203fc72980c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Feb 2023 09:01:48 +0100 Subject: [PATCH 0683/1133] Move previous packet to a local variable It is only used from run_recorder(). --- app/src/recorder.c | 14 ++++++++------ app/src/recorder.h | 6 ------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index f71f6322..c52bbb4d 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -138,6 +138,10 @@ run_recorder(void *data) { int64_t pts_origin = AV_NOPTS_VALUE; + // We can write a packet only once we received the next one so that we can + // set its duration (next_pts - current_pts) + struct sc_record_packet *previous = NULL; + for (;;) { sc_mutex_lock(&recorder->mutex); @@ -150,7 +154,7 @@ run_recorder(void *data) { if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { sc_mutex_unlock(&recorder->mutex); - struct sc_record_packet *last = recorder->previous; + struct sc_record_packet *last = previous; if (last) { // assign an arbitrary duration to the last packet last->packet->duration = 100000; @@ -183,12 +187,9 @@ run_recorder(void *data) { rec->packet->dts = rec->packet->pts; } - // recorder->previous is only written from this thread, no need to lock - struct sc_record_packet *previous = recorder->previous; - recorder->previous = rec; - if (!previous) { // we just received the first packet + previous = rec; continue; } @@ -212,6 +213,8 @@ run_recorder(void *data) { sc_mutex_unlock(&recorder->mutex); break; } + + previous = rec; } if (!recorder->failed) { @@ -256,7 +259,6 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { recorder->stopped = false; recorder->failed = false; recorder->header_written = false; - recorder->previous = NULL; const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); diff --git a/app/src/recorder.h b/app/src/recorder.h index 373278e6..05d3857e 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -34,12 +34,6 @@ struct sc_recorder { bool stopped; // set on recorder_close() bool failed; // set on packet write failure struct sc_recorder_queue queue; - - // we can write a packet only once we received the next one so that we can - // set its duration (next_pts - current_pts) - // "previous" is only accessed from the recorder thread, so it does not - // need to be protected by the mutex - struct sc_record_packet *previous; }; bool From b1b33e3eaf00b94d6a7d7a3c625b8bbc8c7c8014 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Feb 2023 18:10:24 +0100 Subject: [PATCH 0684/1133] Report recorder errors Stop scrcpy on recorder errors. It was previously indirectly stopped by the demuxer, which failed to push packets to a recorder in error. Report it directly instead: - it avoids to wait for the next demuxer call; - it will allow to open the target file from a separate thread and stop immediately on any I/O error. --- app/src/events.h | 1 + app/src/recorder.c | 13 ++++++++++--- app/src/recorder.h | 11 ++++++++++- app/src/scrcpy.c | 24 ++++++++++++++++++++---- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/app/src/events.h b/app/src/events.h index 7fa10761..0a45b652 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -4,3 +4,4 @@ #define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) #define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) #define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5) +#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) diff --git a/app/src/recorder.c b/app/src/recorder.c index c52bbb4d..beca48aa 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -240,6 +240,9 @@ run_recorder(void *data) { LOGD("Recorder thread ended"); + recorder->cbs->on_ended(recorder, !recorder->failed, + recorder->cbs_userdata); + return 0; } @@ -387,10 +390,10 @@ sc_recorder_packet_sink_push(struct sc_packet_sink *sink, } bool -sc_recorder_init(struct sc_recorder *recorder, - const char *filename, +sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, - struct sc_size declared_frame_size) { + struct sc_size declared_frame_size, + const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { recorder->filename = strdup(filename); if (!recorder->filename) { LOG_OOM(); @@ -400,6 +403,10 @@ sc_recorder_init(struct sc_recorder *recorder, recorder->format = format; recorder->declared_frame_size = declared_frame_size; + assert(cbs && cbs->on_ended); + recorder->cbs = cbs; + recorder->cbs_userdata = cbs_userdata; + static const struct sc_packet_sink_ops ops = { .open = sc_recorder_packet_sink_open, .close = sc_recorder_packet_sink_close, diff --git a/app/src/recorder.h b/app/src/recorder.h index 05d3857e..de5827e3 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -34,12 +34,21 @@ struct sc_recorder { bool stopped; // set on recorder_close() bool failed; // set on packet write failure struct sc_recorder_queue queue; + + const struct sc_recorder_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_recorder_callbacks { + void (*on_ended)(struct sc_recorder *recorder, bool success, + void *userdata); }; bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, - struct sc_size declared_frame_size); + struct sc_size declared_frame_size, + const struct sc_recorder_callbacks *cbs, void *cbs_userdata); void sc_recorder_destroy(struct sc_recorder *recorder); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e96fa187..5a43a313 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -161,6 +161,9 @@ event_loop(struct scrcpy *s) { case SC_EVENT_DEMUXER_ERROR: LOGE("Demuxer error"); return SCRCPY_EXIT_FAILURE; + case SC_EVENT_RECORDER_ERROR: + LOGE("Recorder error"); + return SCRCPY_EXIT_FAILURE; case SDL_QUIT: LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; @@ -198,6 +201,17 @@ await_for_server(bool *connected) { return false; } +static void +sc_recorder_on_ended(struct sc_recorder *recorder, bool success, + void *userdata) { + (void) recorder; + (void) userdata; + + if (!success) { + PUSH_EVENT(SC_EVENT_RECORDER_ERROR); + } +} + static void sc_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, void *userdata) { (void) demuxer; @@ -379,10 +393,12 @@ scrcpy(struct scrcpy_options *options) { struct sc_recorder *rec = NULL; if (options->record_filename) { - if (!sc_recorder_init(&s->recorder, - options->record_filename, - options->record_format, - info->frame_size)) { + static const struct sc_recorder_callbacks recorder_cbs = { + .on_ended = sc_recorder_on_ended, + }; + if (!sc_recorder_init(&s->recorder, options->record_filename, + options->record_format, info->frame_size, + &recorder_cbs, NULL)) { goto end; } rec = &s->recorder; From fb2913559144c1f3b062f08b7225216f0e31b3ee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Feb 2023 08:40:44 +0100 Subject: [PATCH 0685/1133] Initialize recorder fields from init() The recorder has two initialization phases: one to initialize the concrete recorder object, and one to open its packet_sink trait. Initialize mutex and condvar as part of the object initialization. If there were several packet_sink traits (spoiler: one for video, one for audio), then the mutex and condvar would still be initialized only once. --- app/src/recorder.c | 53 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index beca48aa..2488a3b5 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -248,33 +248,18 @@ run_recorder(void *data) { static bool sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { - bool ok = sc_mutex_init(&recorder->mutex); - if (!ok) { - return false; - } - - ok = sc_cond_init(&recorder->queue_cond); - if (!ok) { - goto error_mutex_destroy; - } - - sc_queue_init(&recorder->queue); - recorder->stopped = false; - recorder->failed = false; - recorder->header_written = false; - const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); const AVOutputFormat *format = find_muxer(format_name); if (!format) { LOGE("Could not find muxer"); - goto error_cond_destroy; + return false; } recorder->ctx = avformat_alloc_context(); if (!recorder->ctx) { LOG_OOM(); - goto error_cond_destroy; + return false; } // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() @@ -306,8 +291,8 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { } LOGD("Starting recorder thread"); - ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", - recorder); + bool ok = sc_thread_create(&recorder->thread, run_recorder, + "scrcpy-recorder", recorder); if (!ok) { LOGE("Could not start recorder thread"); goto error_avio_close; @@ -321,10 +306,6 @@ error_avio_close: avio_close(recorder->ctx->pb); error_avformat_free_context: avformat_free_context(recorder->ctx); -error_cond_destroy: - sc_cond_destroy(&recorder->queue_cond); -error_mutex_destroy: - sc_mutex_destroy(&recorder->mutex); return false; } @@ -340,8 +321,6 @@ sc_recorder_close(struct sc_recorder *recorder) { avio_close(recorder->ctx->pb); avformat_free_context(recorder->ctx); - sc_cond_destroy(&recorder->queue_cond); - sc_mutex_destroy(&recorder->mutex); } static bool @@ -400,6 +379,21 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, return false; } + bool ok = sc_mutex_init(&recorder->mutex); + if (!ok) { + goto error_free_filename; + } + + ok = sc_cond_init(&recorder->queue_cond); + if (!ok) { + goto error_mutex_destroy; + } + + sc_queue_init(&recorder->queue); + recorder->stopped = false; + recorder->failed = false; + recorder->header_written = false; + recorder->format = format; recorder->declared_frame_size = declared_frame_size; @@ -416,9 +410,18 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->packet_sink.ops = &ops; return true; + +error_mutex_destroy: + sc_mutex_destroy(&recorder->mutex); +error_free_filename: + free(recorder->filename); + + return false; } void sc_recorder_destroy(struct sc_recorder *recorder) { + sc_cond_destroy(&recorder->queue_cond); + sc_mutex_destroy(&recorder->mutex); free(recorder->filename); } From 6b5dfef92357421fc033bf99a5ceeb006c322811 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Feb 2023 09:37:36 +0100 Subject: [PATCH 0686/1133] Inline packet_sink impl in recorder Remove useless wrappers. --- app/src/recorder.c | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 2488a3b5..998a2f7b 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -247,7 +247,11 @@ run_recorder(void *data) { } static bool -sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { +sc_recorder_packet_sink_open(struct sc_packet_sink *sink, + const AVCodec *codec) { + struct sc_recorder *recorder = DOWNCAST(sink); + assert(codec); + const char *format_name = sc_recorder_get_format_name(recorder->format); assert(format_name); const AVOutputFormat *format = find_muxer(format_name); @@ -271,13 +275,13 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { av_dict_set(&recorder->ctx->metadata, "comment", "Recorded by scrcpy " SCRCPY_VERSION, 0); - AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec); + AVStream *ostream = avformat_new_stream(recorder->ctx, codec); if (!ostream) { goto error_avformat_free_context; } ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codecpar->codec_id = input_codec->id; + ostream->codecpar->codec_id = codec->id; ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->width = recorder->declared_frame_size.width; ostream->codecpar->height = recorder->declared_frame_size.height; @@ -311,7 +315,9 @@ error_avformat_free_context: } static void -sc_recorder_close(struct sc_recorder *recorder) { +sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST(sink); + sc_mutex_lock(&recorder->mutex); recorder->stopped = true; sc_cond_signal(&recorder->queue_cond); @@ -324,7 +330,10 @@ sc_recorder_close(struct sc_recorder *recorder) { } static bool -sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) { +sc_recorder_packet_sink_push(struct sc_packet_sink *sink, + const AVPacket *packet) { + struct sc_recorder *recorder = DOWNCAST(sink); + sc_mutex_lock(&recorder->mutex); assert(!recorder->stopped); @@ -348,26 +357,6 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) { return true; } -static bool -sc_recorder_packet_sink_open(struct sc_packet_sink *sink, - const AVCodec *codec) { - struct sc_recorder *recorder = DOWNCAST(sink); - return sc_recorder_open(recorder, codec); -} - -static void -sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { - struct sc_recorder *recorder = DOWNCAST(sink); - sc_recorder_close(recorder); -} - -static bool -sc_recorder_packet_sink_push(struct sc_packet_sink *sink, - const AVPacket *packet) { - struct sc_recorder *recorder = DOWNCAST(sink); - return sc_recorder_push(recorder, packet); -} - bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, From a039124d5d21c3886aacb4463a747edd69da1a46 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Feb 2023 09:25:50 +0100 Subject: [PATCH 0687/1133] Open recording file from the recorder thread The recorder opened the target file from the packet sink open() callback, called by the demuxer. Only then the recorder thread was started. One golden rule for the recorder is to never block the demuxer for I/O, because it would impact mirroring. This rule is respected on recording packets, but not for the initial recorder opening. Therefore, start the recorder thread from sc_recorder_init(), open the file immediately from the recorder thread, then make it wait for the stream to start (on packet sink open()). Now that the recorder can report errors directly (rather than making the demuxer call fail), it is possible to report file opening error even before the packet sink is open. --- app/src/recorder.c | 250 +++++++++++++++++++++++------------- app/src/recorder.h | 14 +- app/src/scrcpy.c | 4 + app/src/trait/packet_sink.h | 1 + 4 files changed, 178 insertions(+), 91 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 998a2f7b..784ef7ee 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -132,10 +132,76 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) { return av_write_frame(recorder->ctx, packet) >= 0; } -static int -run_recorder(void *data) { - struct sc_recorder *recorder = data; +static bool +sc_recorder_open_output_file(struct sc_recorder *recorder) { + const char *format_name = sc_recorder_get_format_name(recorder->format); + assert(format_name); + const AVOutputFormat *format = find_muxer(format_name); + if (!format) { + LOGE("Could not find muxer"); + return false; + } + recorder->ctx = avformat_alloc_context(); + if (!recorder->ctx) { + LOG_OOM(); + return false; + } + + int ret = avio_open(&recorder->ctx->pb, recorder->filename, + AVIO_FLAG_WRITE); + if (ret < 0) { + LOGE("Failed to open output file: %s", recorder->filename); + avformat_free_context(recorder->ctx); + return false; + } + + // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() + // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat + // still expects a pointer-to-non-const (it has not be updated accordingly) + // + recorder->ctx->oformat = (AVOutputFormat *) format; + + av_dict_set(&recorder->ctx->metadata, "comment", + "Recorded by scrcpy " SCRCPY_VERSION, 0); + + LOGI("Recording started to %s file: %s", format_name, recorder->filename); + return true; +} + +static void +sc_recorder_close_output_file(struct sc_recorder *recorder) { + avio_close(recorder->ctx->pb); + avformat_free_context(recorder->ctx); +} + +static bool +sc_recorder_wait_video_stream(struct sc_recorder *recorder) { + sc_mutex_lock(&recorder->mutex); + while (!recorder->codec && !recorder->stopped) { + sc_cond_wait(&recorder->stream_cond, &recorder->mutex); + } + const AVCodec *codec = recorder->codec; + sc_mutex_unlock(&recorder->mutex); + + if (codec) { + AVStream *ostream = avformat_new_stream(recorder->ctx, codec); + if (!ostream) { + return false; + } + + ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + ostream->codecpar->codec_id = codec->id; + ostream->codecpar->format = AV_PIX_FMT_YUV420P; + ostream->codecpar->width = recorder->declared_frame_size.width; + ostream->codecpar->height = recorder->declared_frame_size.height; + } + + return true; +} + +static bool +sc_recorder_process_packets(struct sc_recorder *recorder) { int64_t pts_origin = AV_NOPTS_VALUE; // We can write a packet only once we received the next one so that we can @@ -206,42 +272,70 @@ run_recorder(void *data) { if (!ok) { LOGE("Could not record packet"); - sc_mutex_lock(&recorder->mutex); - recorder->failed = true; - // discard pending packets - sc_recorder_queue_clear(&recorder->queue); - sc_mutex_unlock(&recorder->mutex); - break; + return false; } previous = rec; } - if (!recorder->failed) { - if (recorder->header_written) { - int ret = av_write_trailer(recorder->ctx); - if (ret < 0) { - LOGE("Failed to write trailer to %s", recorder->filename); - recorder->failed = true; - } - } else { - // the recorded file is empty - recorder->failed = true; - } + if (!recorder->header_written) { + // the recorded file is empty + return false; } - if (recorder->failed) { - LOGE("Recording failed to %s", recorder->filename); - } else { + int ret = av_write_trailer(recorder->ctx); + if (ret < 0) { + LOGE("Failed to write trailer to %s", recorder->filename); + return false; + } + + return true; +} + +static bool +sc_recorder_record(struct sc_recorder *recorder) { + bool ok = sc_recorder_open_output_file(recorder); + if (!ok) { + return false; + } + + ok = sc_recorder_wait_video_stream(recorder); + if (!ok) { + sc_recorder_close_output_file(recorder); + return false; + } + + // If recorder->stopped, process any queued packet anyway + + ok = sc_recorder_process_packets(recorder); + sc_recorder_close_output_file(recorder); + return ok; +} + +static int +run_recorder(void *data) { + struct sc_recorder *recorder = data; + + bool success = sc_recorder_record(recorder); + + sc_mutex_lock(&recorder->mutex); + // Prevent the producer to push any new packet + recorder->stopped = true; + // Discard pending packets + sc_recorder_queue_clear(&recorder->queue); + sc_mutex_unlock(&recorder->mutex); + + if (success) { const char *format_name = sc_recorder_get_format_name(recorder->format); LOGI("Recording complete to %s file: %s", format_name, recorder->filename); + } else { + LOGE("Recording failed to %s", recorder->filename); } LOGD("Recorder thread ended"); - recorder->cbs->on_ended(recorder, !recorder->failed, - recorder->cbs_userdata); + recorder->cbs->on_ended(recorder, success, recorder->cbs_userdata); return 0; } @@ -252,66 +346,17 @@ sc_recorder_packet_sink_open(struct sc_packet_sink *sink, struct sc_recorder *recorder = DOWNCAST(sink); assert(codec); - const char *format_name = sc_recorder_get_format_name(recorder->format); - assert(format_name); - const AVOutputFormat *format = find_muxer(format_name); - if (!format) { - LOGE("Could not find muxer"); - return false; - } - - recorder->ctx = avformat_alloc_context(); - if (!recorder->ctx) { - LOG_OOM(); + sc_mutex_lock(&recorder->mutex); + if (recorder->stopped) { + sc_mutex_unlock(&recorder->mutex); return false; } - // contrary to the deprecated API (av_oformat_next()), av_muxer_iterate() - // returns (on purpose) a pointer-to-const, but AVFormatContext.oformat - // still expects a pointer-to-non-const (it has not be updated accordingly) - // - recorder->ctx->oformat = (AVOutputFormat *) format; - - av_dict_set(&recorder->ctx->metadata, "comment", - "Recorded by scrcpy " SCRCPY_VERSION, 0); - - AVStream *ostream = avformat_new_stream(recorder->ctx, codec); - if (!ostream) { - goto error_avformat_free_context; - } - - ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codecpar->codec_id = codec->id; - ostream->codecpar->format = AV_PIX_FMT_YUV420P; - ostream->codecpar->width = recorder->declared_frame_size.width; - ostream->codecpar->height = recorder->declared_frame_size.height; - - int ret = avio_open(&recorder->ctx->pb, recorder->filename, - AVIO_FLAG_WRITE); - if (ret < 0) { - LOGE("Failed to open output file: %s", recorder->filename); - // ostream will be cleaned up during context cleaning - goto error_avformat_free_context; - } - - LOGD("Starting recorder thread"); - bool ok = sc_thread_create(&recorder->thread, run_recorder, - "scrcpy-recorder", recorder); - if (!ok) { - LOGE("Could not start recorder thread"); - goto error_avio_close; - } - - LOGI("Recording started to %s file: %s", format_name, recorder->filename); + recorder->codec = codec; + sc_cond_signal(&recorder->stream_cond); + sc_mutex_unlock(&recorder->mutex); return true; - -error_avio_close: - avio_close(recorder->ctx->pb); -error_avformat_free_context: - avformat_free_context(recorder->ctx); - - return false; } static void @@ -319,14 +364,10 @@ sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST(sink); sc_mutex_lock(&recorder->mutex); + // EOS also stops the recorder recorder->stopped = true; sc_cond_signal(&recorder->queue_cond); sc_mutex_unlock(&recorder->mutex); - - sc_thread_join(&recorder->thread, NULL); - - avio_close(recorder->ctx->pb); - avformat_free_context(recorder->ctx); } static bool @@ -335,10 +376,9 @@ sc_recorder_packet_sink_push(struct sc_packet_sink *sink, struct sc_recorder *recorder = DOWNCAST(sink); sc_mutex_lock(&recorder->mutex); - assert(!recorder->stopped); - if (recorder->failed) { - // reject any new packet (this will stop the stream) + if (recorder->stopped) { + // reject any new packet sc_mutex_unlock(&recorder->mutex); return false; } @@ -378,11 +418,17 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_mutex_destroy; } + ok = sc_cond_init(&recorder->stream_cond); + if (!ok) { + goto error_queue_cond_destroy; + } + sc_queue_init(&recorder->queue); recorder->stopped = false; - recorder->failed = false; recorder->header_written = false; + recorder->codec = NULL; + recorder->format = format; recorder->declared_frame_size = declared_frame_size; @@ -398,8 +444,19 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->packet_sink.ops = &ops; + ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", + recorder); + if (!ok) { + LOGE("Could not start recorder thread"); + goto error_stream_cond_destroy; + } + return true; +error_stream_cond_destroy: + sc_cond_destroy(&recorder->stream_cond); +error_queue_cond_destroy: + sc_cond_destroy(&recorder->queue_cond); error_mutex_destroy: sc_mutex_destroy(&recorder->mutex); error_free_filename: @@ -408,8 +465,23 @@ error_free_filename: return false; } +void +sc_recorder_stop(struct sc_recorder *recorder) { + sc_mutex_lock(&recorder->mutex); + recorder->stopped = true; + sc_cond_signal(&recorder->queue_cond); + sc_cond_signal(&recorder->stream_cond); + sc_mutex_unlock(&recorder->mutex); +} + +void +sc_recorder_join(struct sc_recorder *recorder) { + sc_thread_join(&recorder->thread, NULL); +} + void sc_recorder_destroy(struct sc_recorder *recorder) { + sc_cond_destroy(&recorder->stream_cond); sc_cond_destroy(&recorder->queue_cond); sc_mutex_destroy(&recorder->mutex); free(recorder->filename); diff --git a/app/src/recorder.h b/app/src/recorder.h index de5827e3..cab71678 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -31,10 +31,14 @@ struct sc_recorder { sc_thread thread; sc_mutex mutex; sc_cond queue_cond; - bool stopped; // set on recorder_close() - bool failed; // set on packet write failure + // set on sc_recorder_stop(), packet_sink close or recording failure + bool stopped; struct sc_recorder_queue queue; + // wake up the recorder thread once the codec in known + sc_cond stream_cond; + const AVCodec *codec; + const struct sc_recorder_callbacks *cbs; void *cbs_userdata; }; @@ -50,6 +54,12 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); +void +sc_recorder_stop(struct sc_recorder *recorder); + +void +sc_recorder_join(struct sc_recorder *recorder); + void sc_recorder_destroy(struct sc_recorder *recorder); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5a43a313..90c6bd9b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -660,6 +660,9 @@ end: if (file_pusher_initialized) { sc_file_pusher_stop(&s->file_pusher); } + if (recorder_initialized) { + sc_recorder_stop(&s->recorder); + } if (screen_initialized) { sc_screen_interrupt(&s->screen); } @@ -706,6 +709,7 @@ end: } if (recorder_initialized) { + sc_recorder_join(&s->recorder); sc_recorder_destroy(&s->recorder); } diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h index 1fef765f..9fc9fd24 100644 --- a/app/src/trait/packet_sink.h +++ b/app/src/trait/packet_sink.h @@ -19,6 +19,7 @@ struct sc_packet_sink { }; struct sc_packet_sink_ops { + /* The codec instance is static, it is valid until the end of the program */ bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); void (*close)(struct sc_packet_sink *sink); bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); From 3c407773e9ddf9debd1de71ff0d12f571bd36718 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 11:00:34 +0100 Subject: [PATCH 0688/1133] Add start() function for recorder For consistency with the other components, do not start the internal thread from an init() function. --- app/src/recorder.c | 21 ++++++++++++--------- app/src/recorder.h | 3 +++ app/src/scrcpy.c | 13 +++++++++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 784ef7ee..aa3aea0e 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -444,17 +444,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->packet_sink.ops = &ops; - ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", - recorder); - if (!ok) { - LOGE("Could not start recorder thread"); - goto error_stream_cond_destroy; - } - return true; -error_stream_cond_destroy: - sc_cond_destroy(&recorder->stream_cond); error_queue_cond_destroy: sc_cond_destroy(&recorder->queue_cond); error_mutex_destroy: @@ -465,6 +456,18 @@ error_free_filename: return false; } +bool +sc_recorder_start(struct sc_recorder *recorder) { + bool ok = sc_thread_create(&recorder->thread, run_recorder, + "scrcpy-recorder", recorder); + if (!ok) { + LOGE("Could not start recorder thread"); + return false; + } + + return true; +} + void sc_recorder_stop(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); diff --git a/app/src/recorder.h b/app/src/recorder.h index cab71678..e98d0ea2 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -54,6 +54,9 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); +bool +sc_recorder_start(struct sc_recorder *recorder); + void sc_recorder_stop(struct sc_recorder *recorder); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 90c6bd9b..b636a03a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -277,6 +277,7 @@ scrcpy(struct scrcpy_options *options) { bool server_started = false; bool file_pusher_initialized = false; bool recorder_initialized = false; + bool recorder_started = false; #ifdef HAVE_V4L2 bool v4l2_sink_initialized = false; #endif @@ -401,8 +402,14 @@ scrcpy(struct scrcpy_options *options) { &recorder_cbs, NULL)) { goto end; } - rec = &s->recorder; recorder_initialized = true; + + if (!sc_recorder_start(&s->recorder)) { + goto end; + } + recorder_started = true; + + rec = &s->recorder; } static const struct sc_demuxer_callbacks demuxer_cbs = { @@ -708,8 +715,10 @@ end: sc_controller_destroy(&s->controller); } - if (recorder_initialized) { + if (recorder_started) { sc_recorder_join(&s->recorder); + } + if (recorder_initialized) { sc_recorder_destroy(&s->recorder); } From 4b246cd963ebe0cafa2fe2743e4f9b066dcec293 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 12:07:05 +0100 Subject: [PATCH 0689/1133] Move last packet recording Write the last packet at the end. --- app/src/recorder.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index aa3aea0e..85f0fd2a 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -220,19 +220,6 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { sc_mutex_unlock(&recorder->mutex); - struct sc_record_packet *last = previous; - if (last) { - // assign an arbitrary duration to the last packet - last->packet->duration = 100000; - bool ok = sc_recorder_write(recorder, last->packet); - if (!ok) { - // failing to write the last frame is not very serious, no - // future frame may depend on it, so the resulting file - // will still be valid - LOGW("Could not record last packet"); - } - sc_record_packet_delete(last); - } break; } @@ -283,6 +270,21 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { return false; } + // Write the last packet + struct sc_record_packet *last = previous; + if (last) { + // assign an arbitrary duration to the last packet + last->packet->duration = 100000; + bool ok = sc_recorder_write(recorder, last->packet); + if (!ok) { + // failing to write the last frame is not very serious, no + // future frame may depend on it, so the resulting file + // will still be valid + LOGW("Could not record last packet"); + } + sc_record_packet_delete(last); + } + int ret = av_write_trailer(recorder->ctx); if (ret < 0) { LOGE("Failed to write trailer to %s", recorder->filename); From f9efe48aac25c37c4b974b2794fbe61a84d9e6db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 12:07:30 +0100 Subject: [PATCH 0690/1133] Refactor recorder logic Process the initial config packet (necessary to write the header) separately. --- app/src/recorder.c | 123 ++++++++++++++++++++++++--------------------- app/src/recorder.h | 1 - 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 85f0fd2a..7cc69778 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -93,13 +93,7 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) { ostream->codecpar->extradata = extradata; ostream->codecpar->extradata_size = packet->size; - int ret = avformat_write_header(recorder->ctx, NULL); - if (ret < 0) { - LOGE("Failed to write header to %s", recorder->filename); - return false; - } - - return true; + return avformat_write_header(recorder->ctx, NULL) >= 0; } static void @@ -110,24 +104,6 @@ sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) { static bool sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) { - if (!recorder->header_written) { - if (packet->pts != AV_NOPTS_VALUE) { - LOGE("The first packet is not a config packet"); - return false; - } - bool ok = sc_recorder_write_header(recorder, packet); - if (!ok) { - return false; - } - recorder->header_written = true; - return true; - } - - if (packet->pts == AV_NOPTS_VALUE) { - // ignore config packets - return true; - } - sc_recorder_rescale_packet(recorder, packet); return av_write_frame(recorder->ctx, packet) >= 0; } @@ -200,6 +176,40 @@ sc_recorder_wait_video_stream(struct sc_recorder *recorder) { return true; } +static bool +sc_recorder_process_header(struct sc_recorder *recorder) { + sc_mutex_lock(&recorder->mutex); + + while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + sc_cond_wait(&recorder->queue_cond, &recorder->mutex); + } + + if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + + struct sc_record_packet *rec; + sc_queue_take(&recorder->queue, next, &rec); + + sc_mutex_unlock(&recorder->mutex); + + if (rec->packet->pts != AV_NOPTS_VALUE) { + LOGE("The first packet is not a config packet"); + sc_record_packet_delete(rec); + return false; + } + + bool ok = sc_recorder_write_header(recorder, rec->packet); + sc_record_packet_delete(rec); + if (!ok) { + LOGE("Failed to write header to %s", recorder->filename); + return false; + } + + return true; +} + static bool sc_recorder_process_packets(struct sc_recorder *recorder) { int64_t pts_origin = AV_NOPTS_VALUE; @@ -208,6 +218,11 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // set its duration (next_pts - current_pts) struct sc_record_packet *previous = NULL; + bool header_written = sc_recorder_process_header(recorder); + if (!header_written) { + return false; + } + for (;;) { sc_mutex_lock(&recorder->mutex); @@ -228,46 +243,43 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { sc_mutex_unlock(&recorder->mutex); - if (pts_origin == AV_NOPTS_VALUE - && rec->packet->pts != AV_NOPTS_VALUE) { - // First PTS received - pts_origin = rec->packet->pts; - } + if (rec->packet->pts == AV_NOPTS_VALUE) { + // Ignore further config packets (e.g. on device orientation + // change). The next non-config packet will have the config packet + // data prepended. + sc_record_packet_delete(rec); + } else { + assert(rec->packet->pts != AV_NOPTS_VALUE); + + if (!previous) { + // This is the first non-config packet + assert(pts_origin == AV_NOPTS_VALUE); + pts_origin = rec->packet->pts; + rec->packet->pts = 0; + rec->packet->dts = 0; + previous = rec; + continue; + } + + assert(previous); + assert(pts_origin != AV_NOPTS_VALUE); - if (rec->packet->pts != AV_NOPTS_VALUE) { - // Set PTS relatve to the origin rec->packet->pts -= pts_origin; rec->packet->dts = rec->packet->pts; - } - if (!previous) { - // we just received the first packet - previous = rec; - continue; - } - - // config packets have no PTS, we must ignore them - if (rec->packet->pts != AV_NOPTS_VALUE - && previous->packet->pts != AV_NOPTS_VALUE) { // we now know the duration of the previous packet previous->packet->duration = rec->packet->pts - previous->packet->pts; - } - bool ok = sc_recorder_write(recorder, previous->packet); - sc_record_packet_delete(previous); - if (!ok) { - LOGE("Could not record packet"); + bool ok = sc_recorder_write(recorder, previous->packet); + sc_record_packet_delete(previous); + if (!ok) { + LOGE("Could not record packet"); + return false; + } - return false; + previous = rec; } - - previous = rec; - } - - if (!recorder->header_written) { - // the recorded file is empty - return false; } // Write the last packet @@ -427,7 +439,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, sc_queue_init(&recorder->queue); recorder->stopped = false; - recorder->header_written = false; recorder->codec = NULL; diff --git a/app/src/recorder.h b/app/src/recorder.h index e98d0ea2..287030bc 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -26,7 +26,6 @@ struct sc_recorder { enum sc_record_format format; AVFormatContext *ctx; struct sc_size declared_frame_size; - bool header_written; sc_thread thread; sc_mutex mutex; From ef6a3b97a73f3aa85cb2226b21f4cc1b4372fb12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 12:36:59 +0100 Subject: [PATCH 0691/1133] Reorder initialization Initialize components in the pipeline order: demuxer first, decoder and recorder second. --- app/src/scrcpy.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b636a03a..68665125 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -382,17 +382,20 @@ scrcpy(struct scrcpy_options *options) { file_pusher_initialized = true; } - struct sc_decoder *dec = NULL; + static const struct sc_demuxer_callbacks demuxer_cbs = { + .on_ended = sc_demuxer_on_ended, + }; + sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); + bool needs_decoder = options->display; #ifdef HAVE_V4L2 needs_decoder |= !!options->v4l2_device; #endif if (needs_decoder) { sc_decoder_init(&s->decoder); - dec = &s->decoder; + sc_demuxer_add_sink(&s->demuxer, &s->decoder.packet_sink); } - struct sc_recorder *rec = NULL; if (options->record_filename) { static const struct sc_recorder_callbacks recorder_cbs = { .on_ended = sc_recorder_on_ended, @@ -409,20 +412,7 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - rec = &s->recorder; - } - - static const struct sc_demuxer_callbacks demuxer_cbs = { - .on_ended = sc_demuxer_on_ended, - }; - sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); - - if (dec) { - sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink); - } - - if (rec) { - sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink); + sc_demuxer_add_sink(&s->demuxer, &s->recorder.packet_sink); } struct sc_controller *controller = NULL; From 5b2ec662220d87aa95d3794e7748a00c1163bcd6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Feb 2023 16:53:44 +0100 Subject: [PATCH 0692/1133] Simplify error handling on socket creation On any error, all previously opened sockets must be closed. Handle these errors in a single catch-block. Currently, there are only 2 sockets, but this will simplify even more with more sockets. Note: this commit is better displayed with --ignore-space-change (-b). --- .../genymobile/scrcpy/DesktopConnection.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 1f8f46e4..3cb36a09 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -58,34 +58,34 @@ public final class DesktopConnection implements Closeable { public static DesktopConnection open(int scid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { String socketName = getSocketName(scid); - LocalSocket videoSocket; + LocalSocket videoSocket = null; LocalSocket controlSocket = null; - if (tunnelForward) { - try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { - videoSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); - } - if (control) { - try { + try { + if (tunnelForward) { + try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { + videoSocket = localServerSocket.accept(); + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + } + if (control) { controlSocket = localServerSocket.accept(); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; } } - } - } else { - videoSocket = connect(socketName); - if (control) { - try { + } else { + videoSocket = connect(socketName); + if (control) { controlSocket = connect(socketName); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; } } + } catch (IOException | RuntimeException e) { + if (videoSocket != null) { + videoSocket.close(); + } + if (controlSocket != null) { + controlSocket.close(); + } + throw e; } return new DesktopConnection(videoSocket, controlSocket); From ae29e5b56200035a81c7321c1617fe05928fc5b0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 11:44:18 +0100 Subject: [PATCH 0693/1133] Use VideoStreamer directly from ScreenEncoder The Callbacks interface notifies new packets. But in addition, the screen encoder will need to write headers on start. We could add a function onStart(), but for simplicity, just remove the interface, which brings no value, and call the streamer directly. Refs 87972e2022686b1176bfaf0c678e703856c2b027 --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 12 ++++-------- .../java/com/genymobile/scrcpy/VideoStreamer.java | 5 ++--- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 44ba75d1..3ca2e80c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -21,10 +21,6 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ScreenEncoder implements Device.RotationListener { - public interface Callbacks { - void onPacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException; - } - private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; @@ -63,7 +59,7 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, Callbacks callbacks) throws IOException, ConfigurationException { + public void streamScreen(Device device, VideoStreamer streamer) throws IOException, ConfigurationException { MediaCodec codec = createCodec(videoMimeType, encoderName); MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); @@ -92,7 +88,7 @@ public class ScreenEncoder implements Device.RotationListener { codec.start(); - alive = encode(codec, callbacks); + alive = encode(codec, streamer); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); } catch (IllegalStateException | IllegalArgumentException e) { @@ -161,7 +157,7 @@ public class ScreenEncoder implements Device.RotationListener { return 0; } - private boolean encode(MediaCodec codec, Callbacks callbacks) throws IOException { + private boolean encode(MediaCodec codec, VideoStreamer streamer) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); @@ -184,7 +180,7 @@ public class ScreenEncoder implements Device.RotationListener { consecutiveErrors = 0; } - callbacks.onPacket(codecBuffer, bufferInfo); + streamer.writePacket(codecBuffer, bufferInfo); } } finally { if (outputBufferId >= 0) { diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java index 943c641d..cbde141e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java @@ -6,7 +6,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; -public final class VideoStreamer implements ScreenEncoder.Callbacks { +public final class VideoStreamer { private static final long PACKET_FLAG_CONFIG = 1L << 63; private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; @@ -28,8 +28,7 @@ public final class VideoStreamer implements ScreenEncoder.Callbacks { IO.writeFully(fd, buffer); } - @Override - public void onPacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { + public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { if (sendFrameMeta) { writeFrameMeta(fd, bufferInfo, codecBuffer.remaining()); } From 51628201b77c7f7c990e26471aa88c5ef930061c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 11:57:37 +0100 Subject: [PATCH 0694/1133] Write streamer header from ScreenEncoder The screen encoder is responsible for writing data to the video streamer. --- .../com/genymobile/scrcpy/ScreenEncoder.java | 3 +++ .../java/com/genymobile/scrcpy/Server.java | 5 +---- .../com/genymobile/scrcpy/VideoStreamer.java | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 3ca2e80c..c86bc9a5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -64,6 +64,9 @@ public class ScreenEncoder implements Device.RotationListener { MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); + + streamer.writeHeader(); + boolean alive; try { do { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 027050af..d75e8310 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -104,10 +104,7 @@ public final class Server { try { // synchronous - VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), options.getSendFrameMeta()); - if (options.getSendCodecId()) { - videoStreamer.writeHeader(codec.getId()); - } + VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { // Broken pipe is expected on close, because the socket is closed by the client diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java index cbde141e..5858d7d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java @@ -12,20 +12,26 @@ public final class VideoStreamer { private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; private final FileDescriptor fd; + private final VideoCodec codec; + private final boolean sendCodecId; private final boolean sendFrameMeta; private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); - public VideoStreamer(FileDescriptor fd, boolean sendFrameMeta) { + public VideoStreamer(FileDescriptor fd, VideoCodec codec, boolean sendCodecId, boolean sendFrameMeta) { this.fd = fd; + this.codec = codec; + this.sendCodecId = sendCodecId; this.sendFrameMeta = sendFrameMeta; } - public void writeHeader(int codecId) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(4); - buffer.putInt(codecId); - buffer.flip(); - IO.writeFully(fd, buffer); + public void writeHeader() throws IOException { + if (sendCodecId) { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(codec.getId()); + buffer.flip(); + IO.writeFully(fd, buffer); + } } public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { From ae9b08b90540d0f357255b97e5b2d3d51b9b0d4e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 14:09:20 +0100 Subject: [PATCH 0695/1133] Move screen encoder initialization This prepares further refactors. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d75e8310..ed1dc1bb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -91,8 +91,6 @@ public final class Server { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); } - ScreenEncoder screenEncoder = new ScreenEncoder(codec.getMimeType(), options.getBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName(), options.getDownsizeOnError()); if (control) { controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); @@ -102,9 +100,11 @@ public final class Server { device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text)); } + VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); + ScreenEncoder screenEncoder = new ScreenEncoder(codec.getMimeType(), options.getBitRate(), options.getMaxFps(), codecOptions, + options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous - VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); screenEncoder.streamScreen(device, videoStreamer); } catch (IOException e) { // Broken pipe is expected on close, because the socket is closed by the client From c2267979917215415befb67115bce5cbca1e5006 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 13:46:19 +0100 Subject: [PATCH 0696/1133] Pass all args to ScreenEncoder constructor There is no good reason to pass some of them in the constructor and some others as parameters of the streamScreen() method. --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 12 ++++++++---- .../src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- .../java/com/genymobile/scrcpy/VideoStreamer.java | 4 ++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index c86bc9a5..6f47c7f0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -31,7 +31,8 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); - private final String videoMimeType; + private final Device device; + private final VideoStreamer streamer; private final String encoderName; private final List codecOptions; private final int bitRate; @@ -41,8 +42,10 @@ public class ScreenEncoder implements Device.RotationListener { private boolean firstFrameSent; private int consecutiveErrors; - public ScreenEncoder(String videoMimeType, int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { - this.videoMimeType = videoMimeType; + public ScreenEncoder(Device device, VideoStreamer streamer, int bitRate, int maxFps, List codecOptions, String encoderName, + boolean downsizeOnError) { + this.device = device; + this.streamer = streamer; this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; @@ -59,7 +62,8 @@ public class ScreenEncoder implements Device.RotationListener { return rotationChanged.getAndSet(false); } - public void streamScreen(Device device, VideoStreamer streamer) throws IOException, ConfigurationException { + public void streamScreen() throws IOException, ConfigurationException { + String videoMimeType = streamer.getCodec().getMimeType(); MediaCodec codec = createCodec(videoMimeType, encoderName); MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ed1dc1bb..85234c4e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -101,11 +101,11 @@ public final class Server { } VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(codec.getMimeType(), options.getBitRate(), options.getMaxFps(), codecOptions, + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous - screenEncoder.streamScreen(device, videoStreamer); + screenEncoder.streamScreen(); } catch (IOException e) { // Broken pipe is expected on close, because the socket is closed by the client if (!IO.isBrokenPipe(e)) { diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java index 5858d7d8..24f48082 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java @@ -25,6 +25,10 @@ public final class VideoStreamer { this.sendFrameMeta = sendFrameMeta; } + public VideoCodec getCodec() { + return codec; + } + public void writeHeader() throws IOException { if (sendCodecId) { ByteBuffer buffer = ByteBuffer.allocate(4); From 10ce0f376a89579408337b084be3e05ebcd18d92 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 14:52:09 +0100 Subject: [PATCH 0697/1133] Make streamer independent of codec type Rename VideoStreamer to Streamer, and extract a Codec interface which will also support audio codecs. PR #3757 --- .../main/java/com/genymobile/scrcpy/Codec.java | 16 ++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 6 +++--- .../main/java/com/genymobile/scrcpy/Server.java | 6 +++--- .../scrcpy/{VideoStreamer.java => Streamer.java} | 8 ++++---- .../java/com/genymobile/scrcpy/VideoCodec.java | 14 +++++++++++++- 5 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/Codec.java rename server/src/main/java/com/genymobile/scrcpy/{VideoStreamer.java => Streamer.java} (89%) diff --git a/server/src/main/java/com/genymobile/scrcpy/Codec.java b/server/src/main/java/com/genymobile/scrcpy/Codec.java new file mode 100644 index 00000000..50e8acad --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/Codec.java @@ -0,0 +1,16 @@ +package com.genymobile.scrcpy; + +public interface Codec { + + enum Type { + VIDEO, + } + + Type getType(); + + int getId(); + + String getName(); + + String getMimeType(); +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 6f47c7f0..a79ce079 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -32,7 +32,7 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final Device device; - private final VideoStreamer streamer; + private final Streamer streamer; private final String encoderName; private final List codecOptions; private final int bitRate; @@ -42,7 +42,7 @@ public class ScreenEncoder implements Device.RotationListener { private boolean firstFrameSent; private int consecutiveErrors; - public ScreenEncoder(Device device, VideoStreamer streamer, int bitRate, int maxFps, List codecOptions, String encoderName, + public ScreenEncoder(Device device, Streamer streamer, int bitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { this.device = device; this.streamer = streamer; @@ -164,7 +164,7 @@ public class ScreenEncoder implements Device.RotationListener { return 0; } - private boolean encode(MediaCodec codec, VideoStreamer streamer) throws IOException { + private boolean encode(MediaCodec codec, Streamer streamer) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 85234c4e..d570a9fb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -100,9 +100,9 @@ public final class Server { device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text)); } - VideoStreamer videoStreamer = new VideoStreamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName(), options.getDownsizeOnError()); + Streamer videoStreamer = new Streamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), + codecOptions, options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous screenEncoder.streamScreen(); diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java similarity index 89% rename from server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java rename to server/src/main/java/com/genymobile/scrcpy/Streamer.java index 24f48082..d6bf6780 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoStreamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -6,26 +6,26 @@ import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; -public final class VideoStreamer { +public final class Streamer { private static final long PACKET_FLAG_CONFIG = 1L << 63; private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; private final FileDescriptor fd; - private final VideoCodec codec; + private final Codec codec; private final boolean sendCodecId; private final boolean sendFrameMeta; private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); - public VideoStreamer(FileDescriptor fd, VideoCodec codec, boolean sendCodecId, boolean sendFrameMeta) { + public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecId, boolean sendFrameMeta) { this.fd = fd; this.codec = codec; this.sendCodecId = sendCodecId; this.sendFrameMeta = sendFrameMeta; } - public VideoCodec getCodec() { + public Codec getCodec() { return codec; } diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java index e19b27f0..43531f1e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -3,7 +3,7 @@ package com.genymobile.scrcpy; import android.annotation.SuppressLint; import android.media.MediaFormat; -public enum VideoCodec { +public enum VideoCodec implements Codec { H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), @SuppressLint("InlinedApi") // introduced in API 21 @@ -19,10 +19,22 @@ public enum VideoCodec { this.mimeType = mimeType; } + @Override + public Type getType() { + return Type.VIDEO; + } + + @Override public int getId() { return id; } + @Override + public String getName() { + return name; + } + + @Override public String getMimeType() { return mimeType; } From 90d88a6927901345afbe81550c7a489304a61e63 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 19:51:50 +0100 Subject: [PATCH 0698/1133] Rename "codec" variable to "mediaCodec" This will allow to use "codec" for the Codec type. PR #3757 --- .../com/genymobile/scrcpy/ScreenEncoder.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index a79ce079..c697cfa2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener { public void streamScreen() throws IOException, ConfigurationException { String videoMimeType = streamer.getCodec().getMimeType(); - MediaCodec codec = createCodec(videoMimeType, encoderName); + MediaCodec mediaCodec = createMediaCodec(videoMimeType, encoderName); MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); @@ -84,8 +84,8 @@ public class ScreenEncoder implements Device.RotationListener { Surface surface = null; try { - codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); - surface = codec.createInputSurface(); + mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + surface = mediaCodec.createInputSurface(); // does not include the locked video orientation Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); @@ -93,11 +93,11 @@ public class ScreenEncoder implements Device.RotationListener { int layerStack = device.getLayerStack(); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); - codec.start(); + mediaCodec.start(); - alive = encode(codec, streamer); + alive = encode(mediaCodec, streamer); // do not call stop() on exception, it would trigger an IllegalStateException - codec.stop(); + mediaCodec.stop(); } catch (IllegalStateException | IllegalArgumentException e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); if (!prepareRetry(device, screenInfo)) { @@ -106,14 +106,14 @@ public class ScreenEncoder implements Device.RotationListener { Ln.i("Retrying..."); alive = true; } finally { - codec.reset(); + mediaCodec.reset(); if (surface != null) { surface.release(); } } } while (alive); } finally { - codec.release(); + mediaCodec.release(); device.setRotationListener(null); SurfaceControl.destroyDisplay(display); } @@ -210,7 +210,7 @@ public class ScreenEncoder implements Device.RotationListener { return result.toArray(new MediaCodecInfo[result.size()]); } - private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException, ConfigurationException { + private static MediaCodec createMediaCodec(String videoMimeType, String encoderName) throws IOException, ConfigurationException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); try { @@ -220,9 +220,9 @@ public class ScreenEncoder implements Device.RotationListener { throw new ConfigurationException("Unknown encoder: " + encoderName); } } - MediaCodec codec = MediaCodec.createEncoderByType(videoMimeType); - Ln.d("Using encoder: '" + codec.getName() + "'"); - return codec; + MediaCodec mediaCodec = MediaCodec.createEncoderByType(videoMimeType); + Ln.d("Using encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; } private static String buildUnknownEncoderMessage(String videoMimeType, String encoderName) { From 10ef8da95d9f5d66b3755b88b4d1fda032163260 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 19:58:05 +0100 Subject: [PATCH 0699/1133] Improve error message for unknown encoder The provided encoder name depends on the selected codec. Improve the error message and the suggestions. PR #3757 --- .../com/genymobile/scrcpy/ScreenEncoder.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index c697cfa2..fdd23bf3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -63,9 +63,9 @@ public class ScreenEncoder implements Device.RotationListener { } public void streamScreen() throws IOException, ConfigurationException { - String videoMimeType = streamer.getCodec().getMimeType(); - MediaCodec mediaCodec = createMediaCodec(videoMimeType, encoderName); - MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions); + Codec codec = streamer.getCodec(); + MediaCodec mediaCodec = createMediaCodec(codec, encoderName); + MediaFormat format = createFormat(codec.getMimeType(), bitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); @@ -210,28 +210,28 @@ public class ScreenEncoder implements Device.RotationListener { return result.toArray(new MediaCodecInfo[result.size()]); } - private static MediaCodec createMediaCodec(String videoMimeType, String encoderName) throws IOException, ConfigurationException { + private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e(buildUnknownEncoderMessage(videoMimeType, encoderName)); + Ln.e(buildUnknownEncoderMessage(codec, encoderName)); throw new ConfigurationException("Unknown encoder: " + encoderName); } } - MediaCodec mediaCodec = MediaCodec.createEncoderByType(videoMimeType); + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); Ln.d("Using encoder: '" + mediaCodec.getName() + "'"); return mediaCodec; } - private static String buildUnknownEncoderMessage(String videoMimeType, String encoderName) { - StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' not found"); - MediaCodecInfo[] encoders = listEncoders(videoMimeType); + private static String buildUnknownEncoderMessage(Codec codec, String encoderName) { + StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); + MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); if (encoders != null && encoders.length > 0) { msg.append("\nTry to use one of the available encoders:"); for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --encoder='").append(encoder.getName()).append("'"); + msg.append("\n scrcpy --codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); } } return msg.toString(); From a5541b3476fe32306de7cb02472cf9077dd45498 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 27 Jan 2023 20:13:37 +0800 Subject: [PATCH 0700/1133] Add a fake Android Context Since scrcpy-server is not an Android application (it's a java executable), it has no Context. Some features will require a Context instance to get the package name and the UID. Add a FakeContext for this purpose. PR #3757 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/FakeContext.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 server/src/main/java/com/genymobile/scrcpy/FakeContext.java diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java new file mode 100644 index 00000000..ddd6177b --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -0,0 +1,40 @@ +package com.genymobile.scrcpy; + +import android.annotation.TargetApi; +import android.content.AttributionSource; +import android.content.ContextWrapper; +import android.os.Build; +import android.os.Process; + +public final class FakeContext extends ContextWrapper { + + public static final String PACKAGE_NAME = "com.android.shell"; + + private static final FakeContext INSTANCE = new FakeContext(); + + public static FakeContext get() { + return INSTANCE; + } + + private FakeContext() { + super(null); + } + + @Override + public String getPackageName() { + return PACKAGE_NAME; + } + + @Override + public String getOpPackageName() { + return PACKAGE_NAME; + } + + @TargetApi(Build.VERSION_CODES.S) + @Override + public AttributionSource getAttributionSource() { + AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID); + builder.setPackageName(PACKAGE_NAME); + return builder.build(); + } +} From 9a815ceba8eccc53c724ef0eb79c6ab277446ad9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Jan 2023 22:31:15 +0100 Subject: [PATCH 0701/1133] Use AttributionSource from FakeContext FakeContext already provides an AttributeSource instance. PR #3757 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- .../scrcpy/wrappers/ContentProvider.java | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 47eae64d..854d4136 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -1,9 +1,12 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.SettingsException; import android.annotation.SuppressLint; +import android.content.AttributionSource; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -51,11 +54,10 @@ public class ContentProvider implements Closeable { @SuppressLint("PrivateApi") private Method getCallMethod() throws NoSuchMethodException { if (callMethod == null) { - try { - Class attributionSourceClass = Class.forName("android.content.AttributionSource"); - callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + callMethod = provider.getClass().getMethod("call", AttributionSource.class, String.class, String.class, String.class, Bundle.class); callMethodVersion = 0; - } catch (NoSuchMethodException | ClassNotFoundException e0) { + } else { // old versions try { callMethod = provider.getClass() @@ -75,40 +77,29 @@ public class ContentProvider implements Closeable { return callMethod; } - @SuppressLint("PrivateApi") - private Object getAttributionSource() - throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - if (attributionSource == null) { - Class cl = Class.forName("android.content.AttributionSource$Builder"); - Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID); - cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME); - attributionSource = cl.getDeclaredMethod("build").invoke(builder); - } - - return attributionSource; - } - private Bundle call(String callMethod, String arg, Bundle extras) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { try { Method method = getCallMethod(); Object[] args; - switch (callMethodVersion) { - case 0: - args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras}; - break; - case 1: - args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; - break; - case 2: - args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; - break; - default: - args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; - break; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && callMethodVersion == 0) { + args = new Object[]{FakeContext.get().getAttributionSource(), "settings", callMethod, arg, extras}; + } else { + switch (callMethodVersion) { + case 1: + args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + break; + case 2: + args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; + break; + default: + args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; + break; + } } return (Bundle) method.invoke(provider, args); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) { + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); throw e; } From 820189b6a6b570388187f166b8def499d73ced8a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Jan 2023 22:32:33 +0100 Subject: [PATCH 0702/1133] Use PACKAGE_NAME from FakeContext Remove duplicated constant. PR #3757 --- .../scrcpy/wrappers/ClipboardManager.java | 19 ++++++++++--------- .../scrcpy/wrappers/ContentProvider.java | 6 +++--- .../scrcpy/wrappers/ServiceManager.java | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index f43a76bc..e0af7923 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Ln; import android.content.ClipData; @@ -58,22 +59,22 @@ public class ClipboardManager { private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME); + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME); } if (alternativeMethod) { - return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); } - return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); } private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME); + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME); } else if (alternativeMethod) { - method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); } else { - method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); } } @@ -106,11 +107,11 @@ public class ClipboardManager { private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager, IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - method.invoke(manager, listener, ServiceManager.PACKAGE_NAME); + method.invoke(manager, listener, FakeContext.PACKAGE_NAME); } else if (alternativeMethod) { - method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, null, ServiceManager.USER_ID); + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); } else { - method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 854d4136..2bdbe175 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -88,13 +88,13 @@ public class ContentProvider implements Closeable { } else { switch (callMethodVersion) { case 1: - args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; + args = new Object[]{FakeContext.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; break; case 2: - args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; + args = new Object[]{FakeContext.PACKAGE_NAME, "settings", callMethod, arg, extras}; break; default: - args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; + args = new Object[]{FakeContext.PACKAGE_NAME, callMethod, arg, extras}; break; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index cb6863b6..ff4b5e23 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -10,7 +10,6 @@ import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class ServiceManager { - public static final String PACKAGE_NAME = "com.android.shell"; public static final int USER_ID = 0; private static final Method GET_SERVICE_METHOD; From 42285ae86906447708def6783d3edee433624406 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Feb 2023 22:29:58 +0100 Subject: [PATCH 0703/1133] Use ROOT_UID from FakeContext Remove USER_ID from ServiceManager, and replace it by a constant in FakeContext. This is the same as android.os.Process.ROOT_UID, but this constant has been introduced in API 29. PR #3757 --- .../main/java/com/genymobile/scrcpy/FakeContext.java | 1 + .../genymobile/scrcpy/wrappers/ActivityManager.java | 5 +++-- .../genymobile/scrcpy/wrappers/ClipboardManager.java | 12 ++++++------ .../genymobile/scrcpy/wrappers/ContentProvider.java | 4 ++-- .../genymobile/scrcpy/wrappers/ServiceManager.java | 2 -- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index ddd6177b..844d6bd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -9,6 +9,7 @@ import android.os.Process; public final class FakeContext extends ContextWrapper { public static final String PACKAGE_NAME = "com.android.shell"; + public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 private static final FakeContext INSTANCE = new FakeContext(); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 93ed4528..76aab5f1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Ln; import android.os.Binder; @@ -48,10 +49,10 @@ public class ActivityManager { Object[] args; if (getContentProviderExternalMethodNewVersion) { // new version - args = new Object[]{name, ServiceManager.USER_ID, token, null}; + args = new Object[]{name, FakeContext.ROOT_UID, token, null}; } else { // old version - args = new Object[]{name, ServiceManager.USER_ID, token}; + args = new Object[]{name, FakeContext.ROOT_UID, token}; } // ContentProviderHolder providerHolder = getContentProviderExternal(...); Object providerHolder = method.invoke(manager, args); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index e0af7923..0c1777ec 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -62,9 +62,9 @@ public class ClipboardManager { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME); } if (alternativeMethod) { - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); } - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); } private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData) @@ -72,9 +72,9 @@ public class ClipboardManager { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, clipData, FakeContext.PACKAGE_NAME); } else if (alternativeMethod) { - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); } else { - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); } } @@ -109,9 +109,9 @@ public class ClipboardManager { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, listener, FakeContext.PACKAGE_NAME); } else if (alternativeMethod) { - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, ServiceManager.USER_ID); + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); } else { - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, ServiceManager.USER_ID); + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 2bdbe175..4917f5eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -138,7 +138,7 @@ public class ContentProvider implements Closeable { public String getValue(String table, String key) throws SettingsException { String method = getGetMethod(table); Bundle arg = new Bundle(); - arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); + arg.putInt(CALL_METHOD_USER_KEY, FakeContext.ROOT_UID); try { Bundle bundle = call(method, key, arg); if (bundle == null) { @@ -154,7 +154,7 @@ public class ContentProvider implements Closeable { public void putValue(String table, String key, String value) throws SettingsException { String method = getPutMethod(table); Bundle arg = new Bundle(); - arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); + arg.putInt(CALL_METHOD_USER_KEY, FakeContext.ROOT_UID); arg.putString(NAME_VALUE_TABLE_VALUE, value); try { call(method, key, arg); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index ff4b5e23..ee2f0fa9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -10,8 +10,6 @@ import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class ServiceManager { - public static final int USER_ID = 0; - private static final Method GET_SERVICE_METHOD; static { try { From 84ba6435bb39c1bee194fd658a7aeb913f4ca0d9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Jan 2023 22:33:30 +0100 Subject: [PATCH 0704/1133] Use shell package name for workarounds For consistency. PR #3757 --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 0f473bc1..e2345e11 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -50,7 +50,7 @@ public final class Workarounds { Object appBindData = appBindDataConstructor.newInstance(); ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = "com.genymobile.scrcpy"; + applicationInfo.packageName = FakeContext.PACKAGE_NAME; // appBindData.appInfo = applicationInfo; Field appInfoField = appBindDataClass.getDeclaredField("appInfo"); From 8487ddba644b5d2aad762bea586654f9a7891b5e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Jan 2023 22:48:50 +0100 Subject: [PATCH 0705/1133] Use FakeContext for Application instance This will expose the correct package name and UID to the application context. PR #3757 --- .../java/com/genymobile/scrcpy/Workarounds.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index e2345e11..64cc1272 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -2,14 +2,12 @@ package com.genymobile.scrcpy; import android.annotation.SuppressLint; import android.app.Application; -import android.app.Instrumentation; -import android.content.Context; +import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.os.Looper; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.Method; public final class Workarounds { private Workarounds() { @@ -62,11 +60,10 @@ public final class Workarounds { mBoundApplicationField.setAccessible(true); mBoundApplicationField.set(activityThread, appBindData); - // Context ctx = activityThread.getSystemContext(); - Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext"); - Context ctx = (Context) getSystemContextMethod.invoke(activityThread); - - Application app = Instrumentation.newApplication(Application.class, ctx); + Application app = Application.class.newInstance(); + Field baseField = ContextWrapper.class.getDeclaredField("mBase"); + baseField.setAccessible(true); + baseField.set(app, FakeContext.get()); // activityThread.mInitialApplication = app; Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication"); From 3cf03e4a4bea7b93766f6e5fb66d8e8878a6a906 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:27:34 +0100 Subject: [PATCH 0706/1133] Add --no-audio option Audio will be enabled by default (when supported). Add an option to disable it. PR #3757 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/src/cli.c | 9 +++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 3 +++ app/src/server.h | 1 + server/src/main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 4 ++++ 10 files changed, 31 insertions(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 4590b6a8..22dc4cee 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -23,6 +23,7 @@ _scrcpy() { --max-fps= -M --hid-mouse -m --max-size= + --no-audio --no-cleanup --no-clipboard-autosync --no-downsize-on-error diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 961565e7..17e1de9f 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -29,6 +29,7 @@ arguments=( '--max-fps=[Limit the frame rate of screen capture]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' {-m,--max-size=}'[Limit both the width and height of the video to value]' + '--no-audio[Disable audio forwarding]' '--no-cleanup[Disable device cleanup actions on exit]' '--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' diff --git a/app/src/cli.c b/app/src/cli.c index ab460732..c1ee1e6d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -59,6 +59,7 @@ enum { OPT_PRINT_FPS, OPT_NO_POWER_ON, OPT_CODEC, + OPT_NO_AUDIO, }; struct sc_option { @@ -267,6 +268,11 @@ static const struct sc_option options[] = { "is preserved.\n" "Default is 0 (unlimited).", }, + { + .longopt_id = OPT_NO_AUDIO, + .longopt = "no-audio", + .text = "Disable audio forwarding.", + }, { .longopt_id = OPT_NO_CLEANUP, .longopt = "no-cleanup", @@ -1630,6 +1636,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_DOWNSIZE_ON_ERROR: opts->downsize_on_error = false; break; + case OPT_NO_AUDIO: + opts->audio = false; + break; case OPT_NO_CLEANUP: opts->cleanup = false; break; diff --git a/app/src/options.c b/app/src/options.c index a75e584e..0854067f 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -67,4 +67,5 @@ const struct scrcpy_options scrcpy_options_default = { .cleanup = true, .start_fps_counter = false, .power_on = true, + .audio = true, }; diff --git a/app/src/options.h b/app/src/options.h index b9d237e0..7bf30011 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -147,6 +147,7 @@ struct scrcpy_options { bool cleanup; bool start_fps_counter; bool power_on; + bool audio; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 68665125..6d63a7a1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -312,6 +312,7 @@ scrcpy(struct scrcpy_options *options) { .lock_video_orientation = options->lock_video_orientation, .control = options->control, .display_id = options->display_id, + .audio = options->audio, .show_touches = options->show_touches, .stay_awake = options->stay_awake, .codec_options = options->codec_options, diff --git a/app/src/server.c b/app/src/server.c index 413f02ee..0ff0d3c8 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -217,6 +217,9 @@ execute_server(struct sc_server *server, ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + if (!params->audio) { + ADD_PARAM("audio=false"); + } if (params->codec != SC_CODEC_H264) { ADD_PARAM("codec=%s", sc_server_get_codec_name(params->codec)); } diff --git a/app/src/server.h b/app/src/server.h index c05b1e5b..95e24b41 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -38,6 +38,7 @@ struct sc_server_params { int8_t lock_video_orientation; bool control; uint32_t display_id; + bool audio; bool show_touches; bool stay_awake; bool force_adb_forward; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 5c59ec8e..07789974 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -8,6 +8,7 @@ public class Options { private Ln.Level logLevel = Ln.Level.DEBUG; private int scid = -1; // 31-bit non-negative value, or -1 + private boolean audio = true; private int maxSize; private VideoCodec codec = VideoCodec.H264; private int bitRate = 8000000; @@ -49,6 +50,14 @@ public class Options { this.scid = scid; } + public boolean getAudio() { + return audio; + } + + public void setAudio(boolean audio) { + this.audio = audio; + } + public int getMaxSize() { return maxSize; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index d570a9fb..2aeeb79e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -169,6 +169,10 @@ public final class Server { Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); options.setLogLevel(level); break; + case "audio": + boolean audio = Boolean.parseBoolean(value); + options.setAudio(audio); + break; case "codec": VideoCodec codec = VideoCodec.findByName(value); if (codec == null) { From e841241a8ea135772a339687b9e7c45779c7864c Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:50:42 +0100 Subject: [PATCH 0707/1133] Add a new socket for audio stream When audio is enabled, open a new socket to send the audio stream from the device to the client. PR #3757 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/server.c | 39 +++++++++++++++++++ app/src/server.h | 1 + .../genymobile/scrcpy/DesktopConnection.java | 30 ++++++++++++-- .../java/com/genymobile/scrcpy/Server.java | 3 +- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 0ff0d3c8..88e3421f 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -391,6 +391,7 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, server->stopped = false; server->video_socket = SC_SOCKET_NONE; + server->audio_socket = SC_SOCKET_NONE; server->control_socket = SC_SOCKET_NONE; sc_adb_tunnel_init(&server->tunnel); @@ -434,9 +435,11 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { const char *serial = server->serial; assert(serial); + bool audio = server->params.audio; bool control = server->params.control; sc_socket video_socket = SC_SOCKET_NONE; + sc_socket audio_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE; if (!tunnel->forward) { video_socket = net_accept_intr(&server->intr, tunnel->server_socket); @@ -444,6 +447,14 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { goto fail; } + if (audio) { + audio_socket = + net_accept_intr(&server->intr, tunnel->server_socket); + if (audio_socket == SC_SOCKET_NONE) { + goto fail; + } + } + if (control) { control_socket = net_accept_intr(&server->intr, tunnel->server_socket); @@ -470,6 +481,18 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { goto fail; } + if (audio) { + audio_socket = net_socket(); + if (audio_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, + tunnel_port); + if (!ok) { + goto fail; + } + } + if (control) { // we know that the device is listening, we don't need several // attempts @@ -496,9 +519,11 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { } assert(video_socket != SC_SOCKET_NONE); + assert(!audio || audio_socket != SC_SOCKET_NONE); assert(!control || control_socket != SC_SOCKET_NONE); server->video_socket = video_socket; + server->audio_socket = audio_socket; server->control_socket = control_socket; return true; @@ -510,6 +535,12 @@ fail: } } + if (audio_socket != SC_SOCKET_NONE) { + if (!net_close(audio_socket)) { + LOGW("Could not close audio socket"); + } + } + if (control_socket != SC_SOCKET_NONE) { if (!net_close(control_socket)) { LOGW("Could not close control socket"); @@ -860,6 +891,11 @@ run_server(void *data) { assert(server->video_socket != SC_SOCKET_NONE); net_interrupt(server->video_socket); + if (server->audio_socket != SC_SOCKET_NONE) { + // There is no audio_socket if --no-audio is set + net_interrupt(server->audio_socket); + } + if (server->control_socket != SC_SOCKET_NONE) { // There is no control_socket if --no-control is set net_interrupt(server->control_socket); @@ -924,6 +960,9 @@ sc_server_destroy(struct sc_server *server) { if (server->video_socket != SC_SOCKET_NONE) { net_close(server->video_socket); } + if (server->audio_socket != SC_SOCKET_NONE) { + net_close(server->audio_socket); + } if (server->control_socket != SC_SOCKET_NONE) { net_close(server->control_socket); } diff --git a/app/src/server.h b/app/src/server.h index 95e24b41..3005ebd2 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -70,6 +70,7 @@ struct sc_server { struct sc_adb_tunnel tunnel; sc_socket video_socket; + sc_socket audio_socket; sc_socket control_socket; const struct sc_server_callbacks *cbs; diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 3cb36a09..3e743621 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -20,6 +20,9 @@ public final class DesktopConnection implements Closeable { private final LocalSocket videoSocket; private final FileDescriptor videoFd; + private final LocalSocket audioSocket; + private final FileDescriptor audioFd; + private final LocalSocket controlSocket; private final InputStream controlInputStream; private final OutputStream controlOutputStream; @@ -27,9 +30,10 @@ public final class DesktopConnection implements Closeable { private final ControlMessageReader reader = new ControlMessageReader(); private final DeviceMessageWriter writer = new DeviceMessageWriter(); - private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { + private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; this.controlSocket = controlSocket; + this.audioSocket = audioSocket; if (controlSocket != null) { controlInputStream = controlSocket.getInputStream(); controlOutputStream = controlSocket.getOutputStream(); @@ -38,6 +42,7 @@ public final class DesktopConnection implements Closeable { controlOutputStream = null; } videoFd = videoSocket.getFileDescriptor(); + audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; } private static LocalSocket connect(String abstractName) throws IOException { @@ -55,10 +60,11 @@ public final class DesktopConnection implements Closeable { return SOCKET_NAME_PREFIX + String.format("_%08x", scid); } - public static DesktopConnection open(int scid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { + public static DesktopConnection open(int scid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException { String socketName = getSocketName(scid); LocalSocket videoSocket = null; + LocalSocket audioSocket = null; LocalSocket controlSocket = null; try { if (tunnelForward) { @@ -68,12 +74,18 @@ public final class DesktopConnection implements Closeable { // send one byte so the client may read() to detect a connection error videoSocket.getOutputStream().write(0); } + if (audio) { + audioSocket = localServerSocket.accept(); + } if (control) { controlSocket = localServerSocket.accept(); } } } else { videoSocket = connect(socketName); + if (audio) { + audioSocket = connect(socketName); + } if (control) { controlSocket = connect(socketName); } @@ -82,19 +94,27 @@ public final class DesktopConnection implements Closeable { if (videoSocket != null) { videoSocket.close(); } + if (audioSocket != null) { + audioSocket.close(); + } if (controlSocket != null) { controlSocket.close(); } throw e; } - return new DesktopConnection(videoSocket, controlSocket); + return new DesktopConnection(videoSocket, audioSocket, controlSocket); } public void close() throws IOException { videoSocket.shutdownInput(); videoSocket.shutdownOutput(); videoSocket.close(); + if (audioSocket != null) { + audioSocket.shutdownInput(); + audioSocket.shutdownOutput(); + audioSocket.close(); + } if (controlSocket != null) { controlSocket.shutdownInput(); controlSocket.shutdownOutput(); @@ -121,6 +141,10 @@ public final class DesktopConnection implements Closeable { return videoFd; } + public FileDescriptor getAudioFd() { + return audioFd; + } + public ControlMessage receiveControlMessage() throws IOException { ControlMessage msg = reader.next(); while (msg == null) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2aeeb79e..55b38c6d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -68,6 +68,7 @@ public final class Server { int scid = options.getScid(); boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); + boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); Workarounds.prepareMainLooper(); @@ -85,7 +86,7 @@ public final class Server { Controller controller = null; - try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, control, sendDummyByte)) { + try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { VideoCodec codec = options.getCodec(); if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); From 11d32616a91f90ec49515a8f72f386bb9322766b Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 27 Jan 2023 20:13:37 +0800 Subject: [PATCH 0708/1133] Capture device audio Create an AudioRecorder to capture the audio source REMOTE_SUBMIX. For now, the captured packets are just logged into the console. PR #3757 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/AudioEncoder.java | 80 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 38 ++++++--- 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java new file mode 100644 index 00000000..45e217fc --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -0,0 +1,80 @@ +package com.genymobile.scrcpy; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.os.Build; + +public final class AudioEncoder { + + private static final int SAMPLE_RATE = 48000; + private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; + private static final int CHANNELS = 2; + private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; + private static final int BYTES_PER_SAMPLE = 2; + + private static final int READ_MS = 5; // milliseconds + private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; + + private Thread thread; + + private static AudioFormat createAudioFormat() { + AudioFormat.Builder builder = new AudioFormat.Builder(); + builder.setEncoding(FORMAT); + builder.setSampleRate(SAMPLE_RATE); + builder.setChannelMask(CHANNEL_CONFIG); + return builder.build(); + } + + @TargetApi(Build.VERSION_CODES.M) + @SuppressLint({"WrongConstant", "MissingPermission"}) + private static AudioRecord createAudioRecord() { + AudioRecord.Builder builder = new AudioRecord.Builder(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // On older APIs, Workarounds.fillAppInfo() must be called beforehand + builder.setContext(FakeContext.get()); + } + builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX); + builder.setAudioFormat(createAudioFormat()); + int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT); + // This buffer size does not impact latency + builder.setBufferSizeInBytes(8 * minBufferSize); + return builder.build(); + } + + public void start() { + AudioRecord recorder = createAudioRecord(); + + thread = new Thread(() -> { + recorder.startRecording(); + try { + byte[] buf = new byte[READ_SIZE]; + while (!Thread.currentThread().isInterrupted()) { + int r = recorder.read(buf, 0, READ_SIZE); + if (r > 0) { + Ln.i("Audio captured: " + r + " bytes"); + } else { + Ln.e("Audio capture error: " + r); + } + } + } finally { + recorder.stop(); + } + }); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + } + } + + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 55b38c6d..c32e4612 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -72,19 +72,28 @@ public final class Server { boolean sendDummyByte = options.getSendDummyByte(); Workarounds.prepareMainLooper(); - if (Build.BRAND.equalsIgnoreCase("meizu")) { - // Workarounds must be applied for Meizu phones: - // - - // - - // - - // - // But only apply when strictly necessary, since workarounds can cause other issues: - // - - // - + + // Workarounds must be applied for Meizu phones: + // - + // - + // - + // + // But only apply when strictly necessary, since workarounds can cause other issues: + // - + // - + boolean mustFillAppInfo = Build.BRAND.equalsIgnoreCase("meizu"); + + // Before Android 11, audio is not supported. + // Since Android 12, we can properly set a context on the AudioRecord. + // Only on Android 11 we must fill app info for the AudioRecord to work. + mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R; + + if (mustFillAppInfo) { Workarounds.fillAppInfo(); } Controller controller = null; + AudioEncoder audioEncoder = null; try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { VideoCodec codec = options.getCodec(); @@ -101,6 +110,11 @@ public final class Server { device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text)); } + if (audio) { + audioEncoder = new AudioEncoder(); + audioEncoder.start(); + } + Streamer videoStreamer = new Streamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); @@ -116,12 +130,18 @@ public final class Server { } finally { Ln.d("Screen streaming stopped"); initThread.interrupt(); + if (audioEncoder != null) { + audioEncoder.stop(); + } if (controller != null) { controller.stop(); } try { initThread.join(); + if (audioEncoder != null) { + audioEncoder.join(); + } if (controller != null) { controller.join(); } From 464a35b05e7896ce5173a1fa3e27f00e0899f19e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 14:07:32 +0100 Subject: [PATCH 0709/1133] Make streamer more generic Expose a method to write a packet from raw metadata (without BufferInfo). --- .../java/com/genymobile/scrcpy/Streamer.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index d6bf6780..ae437104 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -38,23 +38,30 @@ public final class Streamer { } } - public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { + public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { if (sendFrameMeta) { - writeFrameMeta(fd, bufferInfo, codecBuffer.remaining()); + writeFrameMeta(fd, buffer.remaining(), pts, config, keyFrame); } - IO.writeFully(fd, codecBuffer); + IO.writeFully(fd, buffer); + } + + public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException { + long pts = bufferInfo.presentationTimeUs; + boolean config = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; + boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0; + writePacket(codecBuffer, pts, config, keyFrame); } - private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException { + private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException { headerBuffer.clear(); long ptsAndFlags; - if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + if (config) { ptsAndFlags = PACKET_FLAG_CONFIG; // non-media data packet } else { - ptsAndFlags = bufferInfo.presentationTimeUs; - if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { + ptsAndFlags = pts; + if (keyFrame) { ptsAndFlags |= PACKET_FLAG_KEY_FRAME; } } From 5eed2c52c2f38192544d909ee639a3f02720fe56 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 7 Feb 2023 23:08:57 +0100 Subject: [PATCH 0710/1133] Encode recorded audio on the device For now, the encoded packets are just logged into the console. PR #3757 --- .../com/genymobile/scrcpy/AudioEncoder.java | 274 +++++++++++++++++- 1 file changed, 260 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 45e217fc..7b571126 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -4,21 +4,63 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.media.AudioFormat; import android.media.AudioRecord; +import android.media.AudioTimestamp; +import android.media.MediaCodec; +import android.media.MediaFormat; import android.media.MediaRecorder; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; public final class AudioEncoder { + private static class InputTask { + private final int index; + + InputTask(int index) { + this.index = index; + } + } + + private static class OutputTask { + private final int index; + private final MediaCodec.BufferInfo bufferInfo; + + OutputTask(int index, MediaCodec.BufferInfo bufferInfo) { + this.index = index; + this.bufferInfo = bufferInfo; + } + } + + private static final String MIMETYPE = MediaFormat.MIMETYPE_AUDIO_OPUS; private static final int SAMPLE_RATE = 48000; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; private static final int CHANNELS = 2; private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int BYTES_PER_SAMPLE = 2; + private static final int BIT_RATE = 128000; private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; + // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). + // So many pending tasks would lead to an unacceptable delay anyway. + private final BlockingQueue inputTasks = new ArrayBlockingQueue<>(64); + private final BlockingQueue outputTasks = new ArrayBlockingQueue<>(64); + private Thread thread; + private HandlerThread mediaCodecThread; + + private Thread inputThread; + private Thread outputThread; + + private boolean ended; private static AudioFormat createAudioFormat() { AudioFormat.Builder builder = new AudioFormat.Builder(); @@ -44,23 +86,80 @@ public final class AudioEncoder { return builder.build(); } - public void start() { - AudioRecord recorder = createAudioRecord(); + private static MediaFormat createFormat() { + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, MIMETYPE); + format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); + return format; + } + + @TargetApi(Build.VERSION_CODES.N) + private void inputThread(MediaCodec mediaCodec, AudioRecord recorder) throws IOException, InterruptedException { + final AudioTimestamp timestamp = new AudioTimestamp(); + long previousPts = 0; + long nextPts = 0; + + while (!Thread.currentThread().isInterrupted()) { + InputTask task = inputTasks.take(); + ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); + int r = recorder.read(buffer, READ_SIZE); + if (r < 0) { + throw new IOException("Could not read audio: " + r); + } + + long pts; + + int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); + if (ret == AudioRecord.SUCCESS) { + pts = timestamp.nanoTime / 1000; + } else { + if (nextPts == 0) { + Ln.w("Could not get any audio timestamp"); + } + // compute from previous timestamp and packet size + pts = nextPts; + } + + long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); + nextPts = pts + durationUs; + if (previousPts != 0 && pts < previousPts) { + // Audio PTS may come from two sources: + // - recorder.getTimestamp() if the call works; + // - an estimation from the previous PTS and the packet size as a fallback. + // + // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. + pts = previousPts + 1; + } + + previousPts = pts; + + mediaCodec.queueInputBuffer(task.index, 0, r, pts, 0); + } + } + + private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException { + while (!Thread.currentThread().isInterrupted()) { + OutputTask task = outputTasks.take(); + ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index); + try { + Ln.i("Audio packet [pts=" + task.bufferInfo.presentationTimeUs + "] " + buffer.remaining() + " bytes"); + } finally { + mediaCodec.releaseOutputBuffer(task.index, false); + } + } + } + + public void start() { thread = new Thread(() -> { - recorder.startRecording(); try { - byte[] buf = new byte[READ_SIZE]; - while (!Thread.currentThread().isInterrupted()) { - int r = recorder.read(buf, 0, READ_SIZE); - if (r > 0) { - Ln.i("Audio captured: " + r + " bytes"); - } else { - Ln.e("Audio capture error: " + r); - } - } + encode(); + } catch (IOException e) { + Ln.e("Audio encoding error", e); } finally { - recorder.stop(); + Ln.d("Audio encoder stopped"); } }); thread.start(); @@ -68,7 +167,8 @@ public final class AudioEncoder { public void stop() { if (thread != null) { - thread.interrupt(); + // Just wake up the blocking wait from the thread, so that it properly releases all its resources and terminates + end(); } } @@ -77,4 +177,150 @@ public final class AudioEncoder { thread.join(); } } + + private synchronized void end() { + ended = true; + notify(); + } + + private synchronized void waitEnded() { + try { + while (!ended) { + wait(); + } + } catch (InterruptedException e) { + // ignore + } + } + + @TargetApi(Build.VERSION_CODES.M) + public void encode() throws IOException { + MediaCodec mediaCodec = null; + AudioRecord recorder = null; + + boolean mediaCodecStarted = false; + boolean recorderStarted = false; + try { + mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException + + mediaCodecThread = new HandlerThread("AudioEncoder"); + mediaCodecThread.start(); + + MediaFormat format = createFormat(); + mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); + mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + + recorder = createAudioRecord(); + recorder.startRecording(); + recorderStarted = true; + + final MediaCodec mediaCodecRef = mediaCodec; + final AudioRecord recorderRef = recorder; + inputThread = new Thread(() -> { + try { + inputThread(mediaCodecRef, recorderRef); + } catch (IOException | InterruptedException e) { + Ln.e("Audio capture error", e); + } finally { + end(); + } + }); + + outputThread = new Thread(() -> { + try { + outputThread(mediaCodecRef); + } catch (InterruptedException e) { + // this is expected on close + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Audio encoding error", e); + } + } finally { + end(); + } + }); + + mediaCodec.start(); + mediaCodecStarted = true; + inputThread.start(); + outputThread.start(); + + waitEnded(); + } finally { + // Cleanup everything (either at the end or on error at any step of the initialization) + if (mediaCodecThread != null) { + Looper looper = mediaCodecThread.getLooper(); + if (looper != null) { + looper.quitSafely(); + } + } + if (inputThread != null) { + inputThread.interrupt(); + } + if (outputThread != null) { + outputThread.interrupt(); + } + + try { + if (mediaCodecThread != null) { + mediaCodecThread.join(); + } + if (inputThread != null) { + inputThread.join(); + } + if (outputThread != null) { + outputThread.join(); + } + } catch (InterruptedException e) { + // Should never happen + throw new AssertionError(e); + } + + if (mediaCodec != null) { + if (mediaCodecStarted) { + mediaCodec.stop(); + } + mediaCodec.release(); + } + if (recorder != null) { + if (recorderStarted) { + recorder.stop(); + } + recorder.release(); + } + } + } + + private class EncoderCallback extends MediaCodec.Callback { + @TargetApi(Build.VERSION_CODES.N) + @Override + public void onInputBufferAvailable(MediaCodec codec, int index) { + try { + inputTasks.put(new InputTask(index)); + } catch (InterruptedException e) { + end(); + } + } + + @Override + public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo bufferInfo) { + try { + outputTasks.put(new OutputTask(index, bufferInfo)); + } catch (InterruptedException e) { + end(); + } + } + + @Override + public void onError(MediaCodec codec, MediaCodec.CodecException e) { + Ln.e("MediaCodec error", e); + end(); + } + + @Override + public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { + // ignore + } + } } From 7cf5cf5875b217a5d2e141214d1dc344bdde4e73 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Feb 2023 21:37:16 +0100 Subject: [PATCH 0711/1133] Use a streamer to send the audio stream Send each encoded audio packet using a streamer. PR #3757 --- .../com/genymobile/scrcpy/AudioCodec.java | 46 +++++++++++++++++++ .../com/genymobile/scrcpy/AudioEncoder.java | 10 +++- .../java/com/genymobile/scrcpy/Codec.java | 1 + .../java/com/genymobile/scrcpy/Server.java | 3 +- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioCodec.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java new file mode 100644 index 00000000..4d9e3201 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -0,0 +1,46 @@ +package com.genymobile.scrcpy; + +import android.media.MediaFormat; + +public enum AudioCodec implements Codec { + OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS); + + private final int id; // 4-byte ASCII representation of the name + private final String name; + private final String mimeType; + + AudioCodec(int id, String name, String mimeType) { + this.id = id; + this.name = name; + this.mimeType = mimeType; + } + + @Override + public Type getType() { + return Type.AUDIO; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getMimeType() { + return mimeType; + } + + public static AudioCodec findByName(String name) { + for (AudioCodec codec : values()) { + if (codec.name.equals(name)) { + return codec; + } + } + return null; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 7b571126..74481f99 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -49,6 +49,8 @@ public final class AudioEncoder { private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; + private final Streamer streamer; + // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // So many pending tasks would lead to an unacceptable delay anyway. private final BlockingQueue inputTasks = new ArrayBlockingQueue<>(64); @@ -62,6 +64,10 @@ public final class AudioEncoder { private boolean ended; + public AudioEncoder(Streamer streamer) { + this.streamer = streamer; + } + private static AudioFormat createAudioFormat() { AudioFormat.Builder builder = new AudioFormat.Builder(); builder.setEncoding(FORMAT); @@ -141,11 +147,13 @@ public final class AudioEncoder { } private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException { + streamer.writeHeader(); + while (!Thread.currentThread().isInterrupted()) { OutputTask task = outputTasks.take(); ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index); try { - Ln.i("Audio packet [pts=" + task.bufferInfo.presentationTimeUs + "] " + buffer.remaining() + " bytes"); + streamer.writePacket(buffer, task.bufferInfo); } finally { mediaCodec.releaseOutputBuffer(task.index, false); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Codec.java b/server/src/main/java/com/genymobile/scrcpy/Codec.java index 50e8acad..7e905af3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Codec.java +++ b/server/src/main/java/com/genymobile/scrcpy/Codec.java @@ -4,6 +4,7 @@ public interface Codec { enum Type { VIDEO, + AUDIO, } Type getType(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c32e4612..eb0c1384 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,7 +111,8 @@ public final class Server { } if (audio) { - audioEncoder = new AudioEncoder(); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), AudioCodec.OPUS, options.getSendCodecId(), options.getSendFrameMeta()); + audioEncoder = new AudioEncoder(audioStreamer); audioEncoder.start(); } From 15556d1f3be9aa0cf98c43205b516e622e9c7856 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 17:35:54 +0100 Subject: [PATCH 0712/1133] Extract OPUS extradata For OPUS codec, FFmpeg expects the raw extradata, but MediaCodec wraps it in some structure. Fix the config packet to send only the raw extradata. PR #3757 --- .../java/com/genymobile/scrcpy/Streamer.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index ae437104..77d9eefa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -11,6 +11,8 @@ public final class Streamer { private static final long PACKET_FLAG_CONFIG = 1L << 63; private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; + private static final long AOPUSHDR = 0x5244485355504F41L; // "AOPUSHDR" in ASCII (little-endian) + private final FileDescriptor fd; private final Codec codec; private final boolean sendCodecId; @@ -39,6 +41,10 @@ public final class Streamer { } public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { + if (config && codec == AudioCodec.OPUS) { + fixOpusConfigPacket(buffer); + } + if (sendFrameMeta) { writeFrameMeta(fd, buffer.remaining(), pts, config, keyFrame); } @@ -71,4 +77,44 @@ public final class Streamer { headerBuffer.flip(); IO.writeFully(fd, headerBuffer); } + + private static void fixOpusConfigPacket(ByteBuffer buffer) throws IOException { + // Here is an example of the config packet received for an OPUS stream: + // + // 00000000 41 4f 50 55 53 48 44 52 13 00 00 00 00 00 00 00 |AOPUSHDR........| + // -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA ------------------- + // 00000010 4f 70 75 73 48 65 61 64 01 01 38 01 80 bb 00 00 |OpusHead..8.....| + // 00000020 00 00 00 |... | + // ------------------------------------------------------------------------------ + // 00000020 41 4f 50 55 53 44 4c 59 08 00 00 00 00 | AOPUSDLY.....| + // 00000030 00 00 00 a0 2e 63 00 00 00 00 00 41 4f 50 55 53 |.....c.....AOPUS| + // 00000040 50 52 4c 08 00 00 00 00 00 00 00 00 b4 c4 04 00 |PRL.............| + // 00000050 00 00 00 |...| + // + // Each "section" is prefixed by a 64-bit ID and a 64-bit length. + // + // + + if (buffer.remaining() < 16) { + throw new IOException("Not enough data in OPUS config packet"); + } + + long id = buffer.getLong(); + if (id != AOPUSHDR) { + throw new IOException("OPUS header not found"); + } + + long sizeLong = buffer.getLong(); + if (sizeLong < 0 || sizeLong >= 0x7FFFFFFF) { + throw new IOException("Invalid block size in OPUS header: " + sizeLong); + } + + int size = (int) sizeLong; + if (buffer.remaining() < size) { + throw new IOException("Not enough data in OPUS header (invalid size: " + size + ")"); + } + + // Set the buffer to point to the OPUS header slice + buffer.limit(buffer.position() + size); + } } From e9876788c9901d43d75031b090c81c0e604ed994 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 10:08:01 +0100 Subject: [PATCH 0713/1133] Rename demuxer to video_demuxer There will be another demuxer instance for audio. PR #3757 --- app/src/scrcpy.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6d63a7a1..e4e1355c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -40,7 +40,7 @@ struct scrcpy { struct sc_server server; struct sc_screen screen; - struct sc_demuxer demuxer; + struct sc_demuxer video_demuxer; struct sc_decoder decoder; struct sc_recorder recorder; #ifdef HAVE_V4L2 @@ -213,7 +213,8 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success, } static void -sc_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, void *userdata) { +sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, + void *userdata) { (void) demuxer; (void) userdata; @@ -281,7 +282,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_V4L2 bool v4l2_sink_initialized = false; #endif - bool demuxer_started = false; + bool video_demuxer_started = false; #ifdef HAVE_USB bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; @@ -383,10 +384,11 @@ scrcpy(struct scrcpy_options *options) { file_pusher_initialized = true; } - static const struct sc_demuxer_callbacks demuxer_cbs = { - .on_ended = sc_demuxer_on_ended, + static const struct sc_demuxer_callbacks video_demuxer_cbs = { + .on_ended = sc_video_demuxer_on_ended, }; - sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); + sc_demuxer_init(&s->video_demuxer, s->server.video_socket, + &video_demuxer_cbs, NULL); bool needs_decoder = options->display; #ifdef HAVE_V4L2 @@ -394,7 +396,7 @@ scrcpy(struct scrcpy_options *options) { #endif if (needs_decoder) { sc_decoder_init(&s->decoder); - sc_demuxer_add_sink(&s->demuxer, &s->decoder.packet_sink); + sc_demuxer_add_sink(&s->video_demuxer, &s->decoder.packet_sink); } if (options->record_filename) { @@ -413,7 +415,7 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - sc_demuxer_add_sink(&s->demuxer, &s->recorder.packet_sink); + sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.packet_sink); } struct sc_controller *controller = NULL; @@ -621,17 +623,17 @@ aoa_hid_end: #endif // now we consumed the header values, the socket receives the video stream - // start the demuxer - if (!sc_demuxer_start(&s->demuxer)) { + // start the video demuxer + if (!sc_demuxer_start(&s->video_demuxer)) { goto end; } - demuxer_started = true; + video_demuxer_started = true; ret = event_loop(s); LOGD("quit..."); // Close the window immediately on closing, because screen_destroy() may - // only be called once the demuxer thread is joined (it may take time) + // only be called once the video demuxer thread is joined (it may take time) sc_screen_hide_window(&s->screen); end: @@ -672,8 +674,8 @@ end: // now that the sockets are shutdown, the demuxer and controller are // interrupted, we can join them - if (demuxer_started) { - sc_demuxer_join(&s->demuxer); + if (video_demuxer_started) { + sc_demuxer_join(&s->video_demuxer); } #ifdef HAVE_V4L2 @@ -692,8 +694,9 @@ end: } #endif - // Destroy the screen only after the demuxer is guaranteed to be finished, - // because otherwise the screen could receive new frames after destruction + // Destroy the screen only after the video demuxer is guaranteed to be + // finished, because otherwise the screen could receive new frames after + // destruction if (screen_initialized) { sc_screen_join(&s->screen); sc_screen_destroy(&s->screen); From d499f890e7fe019ac3c60bec7ceb61d39aaca97b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 00:13:54 +0100 Subject: [PATCH 0714/1133] Give a name to demuxer instances This will be useful in logs. PR #3757 --- app/src/demuxer.c | 13 +++++++------ app/src/demuxer.h | 5 ++++- app/src/scrcpy.c | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c83d6bfa..e5f07628 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -122,7 +122,7 @@ static bool sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { bool ok = push_packet_to_sinks(demuxer, packet); if (!ok) { - LOGE("Could not process packet"); + LOGE("Demuxer '%s': could not process packet", demuxer->name); return false; } @@ -177,7 +177,7 @@ run_demuxer(void *data) { const AVCodec *codec = avcodec_find_decoder(codec_id); if (!codec) { - LOGE("Decoder not found"); + LOGE("Demuxer '%s': decoder not found", demuxer->name); goto end; } @@ -217,7 +217,7 @@ run_demuxer(void *data) { } } - LOGD("End of frames"); + LOGD("Demuxer '%s': end of frames", demuxer->name); sc_packet_merger_destroy(&merger); @@ -231,8 +231,9 @@ end: } void -sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, +sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) { + demuxer->name = name; // statically allocated demuxer->socket = socket; demuxer->sink_count = 0; @@ -252,12 +253,12 @@ sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) { bool sc_demuxer_start(struct sc_demuxer *demuxer) { - LOGD("Starting demuxer thread"); + LOGD("Demuxer '%s': starting thread", demuxer->name); bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer", demuxer); if (!ok) { - LOGE("Could not start demuxer thread"); + LOGE("Demuxer '%s': could not start thread", demuxer->name); return false; } return true; diff --git a/app/src/demuxer.h b/app/src/demuxer.h index e403fe35..73166b41 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -15,6 +15,8 @@ #define SC_DEMUXER_MAX_SINKS 2 struct sc_demuxer { + const char *name; // must be statically allocated (e.g. a string literal) + sc_socket socket; sc_thread thread; @@ -29,8 +31,9 @@ struct sc_demuxer_callbacks { void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata); }; +// The name must be statically allocated (e.g. a string literal) void -sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket, +sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, const struct sc_demuxer_callbacks *cbs, void *cbs_userdata); void diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index e4e1355c..820f3328 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -387,7 +387,7 @@ scrcpy(struct scrcpy_options *options) { static const struct sc_demuxer_callbacks video_demuxer_cbs = { .on_ended = sc_video_demuxer_on_ended, }; - sc_demuxer_init(&s->video_demuxer, s->server.video_socket, + sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, &video_demuxer_cbs, NULL); bool needs_decoder = options->display; From f60b5767f4aec15d07e8053e25d5a68961618411 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 13:49:53 +0100 Subject: [PATCH 0715/1133] Force --no-audio if no display and no recording The client does not use the audio stream if there is no display and no recording (i.e. only V4L2), so disable audio so that the device does not attempt to capture it. PR #3757 --- app/src/cli.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index c1ee1e6d..4f023da0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1740,6 +1740,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif + if (opts->audio && !opts->display && !opts->record_filename) { + LOGI("No display and no recording: audio disabled"); + opts->audio = false; + } + if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { LOGI("Tunnel host/port is set, " "--force-adb-forward automatically enabled."); From de430bc4aa3b63e8a602032a42613d9672f7c181 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 6 Feb 2023 10:33:47 +0100 Subject: [PATCH 0716/1133] Add an audio demuxer Add a demuxer which will read the stream from the audio socket. PR #3757 --- app/src/demuxer.c | 5 +++++ app/src/scrcpy.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index e5f07628..c5898060 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -23,6 +23,7 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { #define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII #define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII #define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII +#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; @@ -30,6 +31,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { return AV_CODEC_ID_HEVC; case SC_CODEC_ID_AV1: return AV_CODEC_ID_AV1; + case SC_CODEC_ID_OPUS: + return AV_CODEC_ID_OPUS; default: LOGE("Unknown codec id 0x%08" PRIx32, codec_id); return AV_CODEC_ID_NONE; @@ -233,6 +236,8 @@ end: void sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) { + assert(socket != SC_SOCKET_NONE); + demuxer->name = name; // statically allocated demuxer->socket = socket; demuxer->sink_count = 0; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 820f3328..0c846123 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -41,6 +41,7 @@ struct scrcpy { struct sc_server server; struct sc_screen screen; struct sc_demuxer video_demuxer; + struct sc_demuxer audio_demuxer; struct sc_decoder decoder; struct sc_recorder recorder; #ifdef HAVE_V4L2 @@ -225,6 +226,16 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, } } +static void +sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, + void *userdata) { + (void) demuxer; + (void) eos; + (void) userdata; + + // Contrary to the video demuxer, keep mirroring if only the audio fails +} + static void sc_server_on_connection_failed(struct sc_server *server, void *userdata) { (void) server; @@ -283,6 +294,7 @@ scrcpy(struct scrcpy_options *options) { bool v4l2_sink_initialized = false; #endif bool video_demuxer_started = false; + bool audio_demuxer_started = false; #ifdef HAVE_USB bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; @@ -390,6 +402,14 @@ scrcpy(struct scrcpy_options *options) { sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, &video_demuxer_cbs, NULL); + if (options->audio) { + static const struct sc_demuxer_callbacks audio_demuxer_cbs = { + .on_ended = sc_audio_demuxer_on_ended, + }; + sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket, + &audio_demuxer_cbs, NULL); + } + bool needs_decoder = options->display; #ifdef HAVE_V4L2 needs_decoder |= !!options->v4l2_device; @@ -629,6 +649,13 @@ aoa_hid_end: } video_demuxer_started = true; + if (options->audio) { + if (!sc_demuxer_start(&s->audio_demuxer)) { + goto end; + } + audio_demuxer_started = true; + } + ret = event_loop(s); LOGD("quit..."); @@ -678,6 +705,10 @@ end: sc_demuxer_join(&s->video_demuxer); } + if (audio_demuxer_started) { + sc_demuxer_join(&s->audio_demuxer); + } + #ifdef HAVE_V4L2 if (v4l2_sink_initialized) { sc_v4l2_sink_destroy(&s->v4l2_sink); From 609b098a97322b73de8ec6cd375926ac1f6f8307 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 17:37:58 +0100 Subject: [PATCH 0717/1133] Do not merge config audio packets For video streams (at least H.264 and H.265), the config packet containing SPS/PPS must be prepended to the next packet (the following keyframe). For audio streams (at least OPUS), they must not be merged. PR #3757 --- app/src/demuxer.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c5898060..968e6db0 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -188,8 +188,15 @@ run_demuxer(void *data) { goto end; } + // Config packets must be merged with the next non-config packet only for + // video streams + bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO; + struct sc_packet_merger merger; - sc_packet_merger_init(&merger); + + if (must_merge_config_packet) { + sc_packet_merger_init(&merger); + } AVPacket *packet = av_packet_alloc(); if (!packet) { @@ -205,11 +212,13 @@ run_demuxer(void *data) { break; } - // Prepend any config packet to the next media packet - ok = sc_packet_merger_merge(&merger, packet); - if (!ok) { - av_packet_unref(packet); - break; + if (must_merge_config_packet) { + // Prepend any config packet to the next media packet + ok = sc_packet_merger_merge(&merger, packet); + if (!ok) { + av_packet_unref(packet); + break; + } } ok = sc_demuxer_push_packet(demuxer, packet); @@ -222,7 +231,9 @@ run_demuxer(void *data) { LOGD("Demuxer '%s': end of frames", demuxer->name); - sc_packet_merger_destroy(&merger); + if (must_merge_config_packet) { + sc_packet_merger_destroy(&merger); + } av_packet_free(&packet); finally_close_sinks: From 3d29f6ef0684aa9ef7a47690aa82326b6f830293 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Feb 2023 10:06:10 +0100 Subject: [PATCH 0718/1133] Rename video-specific variables in recorder This paves the way to add audio-specific variables. PR #3757 --- app/src/recorder.c | 58 ++++++++++++++++++++++++---------------------- app/src/recorder.h | 8 +++---- app/src/scrcpy.c | 2 +- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 7cc69778..86817681 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -8,8 +8,9 @@ #include "util/log.h" #include "util/str.h" -/** Downcast packet_sink to recorder */ -#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink) +/** Downcast video packet_sink to recorder */ +#define DOWNCAST_VIDEO(SINK) \ + container_of(SINK, struct sc_recorder, video_packet_sink) static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us @@ -154,10 +155,10 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { static bool sc_recorder_wait_video_stream(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->codec && !recorder->stopped) { + while (!recorder->video_codec && !recorder->stopped) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } - const AVCodec *codec = recorder->codec; + const AVCodec *codec = recorder->video_codec; sc_mutex_unlock(&recorder->mutex); if (codec) { @@ -180,17 +181,17 @@ static bool sc_recorder_process_header(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + while (!recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } - if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { sc_mutex_unlock(&recorder->mutex); return false; } struct sc_record_packet *rec; - sc_queue_take(&recorder->queue, next, &rec); + sc_queue_take(&recorder->video_queue, next, &rec); sc_mutex_unlock(&recorder->mutex); @@ -226,20 +227,21 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { for (;;) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + while (!recorder->stopped + && sc_queue_is_empty(&recorder->video_queue)) { sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } // if stopped is set, continue to process the remaining events (to // finish the recording) before actually stopping - if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { + if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { sc_mutex_unlock(&recorder->mutex); break; } struct sc_record_packet *rec; - sc_queue_take(&recorder->queue, next, &rec); + sc_queue_take(&recorder->video_queue, next, &rec); sc_mutex_unlock(&recorder->mutex); @@ -336,7 +338,7 @@ run_recorder(void *data) { // Prevent the producer to push any new packet recorder->stopped = true; // Discard pending packets - sc_recorder_queue_clear(&recorder->queue); + sc_recorder_queue_clear(&recorder->video_queue); sc_mutex_unlock(&recorder->mutex); if (success) { @@ -355,9 +357,9 @@ run_recorder(void *data) { } static bool -sc_recorder_packet_sink_open(struct sc_packet_sink *sink, - const AVCodec *codec) { - struct sc_recorder *recorder = DOWNCAST(sink); +sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, + const AVCodec *codec) { + struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); assert(codec); sc_mutex_lock(&recorder->mutex); @@ -366,7 +368,7 @@ sc_recorder_packet_sink_open(struct sc_packet_sink *sink, return false; } - recorder->codec = codec; + recorder->video_codec = codec; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); @@ -374,8 +376,8 @@ sc_recorder_packet_sink_open(struct sc_packet_sink *sink, } static void -sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { - struct sc_recorder *recorder = DOWNCAST(sink); +sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder @@ -385,9 +387,9 @@ sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { } static bool -sc_recorder_packet_sink_push(struct sc_packet_sink *sink, - const AVPacket *packet) { - struct sc_recorder *recorder = DOWNCAST(sink); +sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, + const AVPacket *packet) { + struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); sc_mutex_lock(&recorder->mutex); @@ -404,7 +406,7 @@ sc_recorder_packet_sink_push(struct sc_packet_sink *sink, return false; } - sc_queue_push(&recorder->queue, next, rec); + sc_queue_push(&recorder->video_queue, next, rec); sc_cond_signal(&recorder->queue_cond); sc_mutex_unlock(&recorder->mutex); @@ -437,10 +439,10 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_queue_cond_destroy; } - sc_queue_init(&recorder->queue); + sc_queue_init(&recorder->video_queue); recorder->stopped = false; - recorder->codec = NULL; + recorder->video_codec = NULL; recorder->format = format; recorder->declared_frame_size = declared_frame_size; @@ -449,13 +451,13 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->cbs = cbs; recorder->cbs_userdata = cbs_userdata; - static const struct sc_packet_sink_ops ops = { - .open = sc_recorder_packet_sink_open, - .close = sc_recorder_packet_sink_close, - .push = sc_recorder_packet_sink_push, + static const struct sc_packet_sink_ops video_ops = { + .open = sc_recorder_video_packet_sink_open, + .close = sc_recorder_video_packet_sink_close, + .push = sc_recorder_video_packet_sink_push, }; - recorder->packet_sink.ops = &ops; + recorder->video_packet_sink.ops = &video_ops; return true; diff --git a/app/src/recorder.h b/app/src/recorder.h index 287030bc..b7a10e5e 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -20,7 +20,7 @@ struct sc_record_packet { struct sc_recorder_queue SC_QUEUE(struct sc_record_packet); struct sc_recorder { - struct sc_packet_sink packet_sink; // packet sink trait + struct sc_packet_sink video_packet_sink; // packet sink trait char *filename; enum sc_record_format format; @@ -32,11 +32,11 @@ struct sc_recorder { sc_cond queue_cond; // set on sc_recorder_stop(), packet_sink close or recording failure bool stopped; - struct sc_recorder_queue queue; + struct sc_recorder_queue video_queue; - // wake up the recorder thread once the codec in known + // wake up the recorder thread once the video codec is known sc_cond stream_cond; - const AVCodec *codec; + const AVCodec *video_codec; const struct sc_recorder_callbacks *cbs; void *cbs_userdata; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0c846123..437ae650 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -435,7 +435,7 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.packet_sink); + sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.video_packet_sink); } struct sc_controller *controller = NULL; From 7de062221465fef6c6be5998b3884bdc833c8526 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:02:43 +0100 Subject: [PATCH 0719/1133] Add audio recording support Make the recorder accept two input sources (video and audio), and mux them into a single file. PR #3757 --- app/src/recorder.c | 393 ++++++++++++++++++++++++++++++++++++--------- app/src/recorder.h | 14 +- app/src/scrcpy.c | 8 +- 3 files changed, 334 insertions(+), 81 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 86817681..93c3321f 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -8,9 +8,11 @@ #include "util/log.h" #include "util/str.h" -/** Downcast video packet_sink to recorder */ +/** Downcast packet sinks to recorder */ #define DOWNCAST_VIDEO(SINK) \ container_of(SINK, struct sc_recorder, video_packet_sink) +#define DOWNCAST_AUDIO(SINK) \ + container_of(SINK, struct sc_recorder, audio_packet_sink) static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us @@ -79,9 +81,7 @@ sc_recorder_get_format_name(enum sc_record_format format) { } static bool -sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) { - AVStream *ostream = recorder->ctx->streams[0]; - +sc_recorder_set_extradata(AVStream *ostream, const AVPacket *packet) { uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); if (!extradata) { LOG_OOM(); @@ -93,20 +93,32 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) { ostream->codecpar->extradata = extradata; ostream->codecpar->extradata_size = packet->size; - - return avformat_write_header(recorder->ctx, NULL) >= 0; + return true; } -static void -sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) { - AVStream *ostream = recorder->ctx->streams[0]; - av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); +static inline void +sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) { + av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, stream->time_base); } static bool -sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) { - sc_recorder_rescale_packet(recorder, packet); - return av_write_frame(recorder->ctx, packet) >= 0; +sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index, + AVPacket *packet) { + AVStream *stream = recorder->ctx->streams[stream_index]; + sc_recorder_rescale_packet(stream, packet); + return av_interleaved_write_frame(recorder->ctx, packet) >= 0; +} + +static inline bool +sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) { + return sc_recorder_write_stream(recorder, recorder->video_stream_index, + packet); +} + +static inline bool +sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) { + return sc_recorder_write_stream(recorder, recorder->audio_stream_index, + packet); } static bool @@ -162,134 +174,270 @@ sc_recorder_wait_video_stream(struct sc_recorder *recorder) { sc_mutex_unlock(&recorder->mutex); if (codec) { - AVStream *ostream = avformat_new_stream(recorder->ctx, codec); - if (!ostream) { + AVStream *stream = avformat_new_stream(recorder->ctx, codec); + if (!stream) { return false; } - ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codecpar->codec_id = codec->id; - ostream->codecpar->format = AV_PIX_FMT_YUV420P; - ostream->codecpar->width = recorder->declared_frame_size.width; - ostream->codecpar->height = recorder->declared_frame_size.height; + stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + stream->codecpar->codec_id = codec->id; + stream->codecpar->format = AV_PIX_FMT_YUV420P; + stream->codecpar->width = recorder->declared_frame_size.width; + stream->codecpar->height = recorder->declared_frame_size.height; + + recorder->video_stream_index = stream->index; } return true; } +static bool +sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { + sc_mutex_lock(&recorder->mutex); + while (!recorder->audio_codec && !recorder->stopped) { + sc_cond_wait(&recorder->stream_cond, &recorder->mutex); + } + const AVCodec *codec = recorder->audio_codec; + sc_mutex_unlock(&recorder->mutex); + + if (codec) { + AVStream *stream = avformat_new_stream(recorder->ctx, codec); + if (!stream) { + return false; + } + + stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + stream->codecpar->codec_id = codec->id; + stream->codecpar->ch_layout.nb_channels = 2; + stream->codecpar->sample_rate = 48000; + + recorder->audio_stream_index = stream->index; + } + + return true; +} + +static inline bool +sc_recorder_has_empty_queues(struct sc_recorder *recorder) { + if (sc_queue_is_empty(&recorder->video_queue)) { + // The video queue is empty + return true; + } + + if (recorder->audio && sc_queue_is_empty(&recorder->audio_queue)) { + // The audio queue is empty (when audio is enabled) + return true; + } + + // No queue is empty + return false; +} + static bool sc_recorder_process_header(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { + while (!recorder->stopped && sc_recorder_has_empty_queues(recorder)) { sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } - if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { + if (sc_recorder_has_empty_queues(recorder)) { + assert(recorder->stopped); sc_mutex_unlock(&recorder->mutex); return false; } - struct sc_record_packet *rec; - sc_queue_take(&recorder->video_queue, next, &rec); + struct sc_record_packet *video_pkt; + sc_queue_take(&recorder->video_queue, next, &video_pkt); + + struct sc_record_packet *audio_pkt; + if (recorder->audio) { + sc_queue_take(&recorder->audio_queue, next, &audio_pkt); + } sc_mutex_unlock(&recorder->mutex); - if (rec->packet->pts != AV_NOPTS_VALUE) { - LOGE("The first packet is not a config packet"); - sc_record_packet_delete(rec); - return false; + int ret = false; + + if (video_pkt->packet->pts != AV_NOPTS_VALUE) { + LOGE("The first video packet is not a config packet"); + goto end; } - bool ok = sc_recorder_write_header(recorder, rec->packet); - sc_record_packet_delete(rec); + assert(recorder->video_stream_index >= 0); + AVStream *video_stream = + recorder->ctx->streams[recorder->video_stream_index]; + bool ok = sc_recorder_set_extradata(video_stream, video_pkt->packet); + if (!ok) { + goto end; + } + + if (recorder->audio) { + if (audio_pkt->packet->pts != AV_NOPTS_VALUE) { + LOGE("The first audio packet is not a config packet"); + goto end; + } + + assert(recorder->audio_stream_index >= 0); + AVStream *audio_stream = + recorder->ctx->streams[recorder->audio_stream_index]; + ok = sc_recorder_set_extradata(audio_stream, audio_pkt->packet); + if (!ok) { + goto end; + } + } + + ok = avformat_write_header(recorder->ctx, NULL) >= 0; if (!ok) { LOGE("Failed to write header to %s", recorder->filename); - return false; + goto end; } - return true; + ret = true; + +end: + sc_record_packet_delete(video_pkt); + if (recorder->audio) { + sc_record_packet_delete(audio_pkt); + } + + return ret; } static bool sc_recorder_process_packets(struct sc_recorder *recorder) { int64_t pts_origin = AV_NOPTS_VALUE; - // We can write a packet only once we received the next one so that we can - // set its duration (next_pts - current_pts) - struct sc_record_packet *previous = NULL; - bool header_written = sc_recorder_process_header(recorder); if (!header_written) { return false; } + struct sc_record_packet *video_pkt = NULL; + struct sc_record_packet *audio_pkt = NULL; + + // We can write a video packet only once we received the next one so that + // we can set its duration (next_pts - current_pts) + struct sc_record_packet *video_pkt_previous = NULL; + + bool error = false; + for (;;) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped - && sc_queue_is_empty(&recorder->video_queue)) { + while (!recorder->stopped) { + if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) { + // A new packet may be assigned to video_pkt and be processed + break; + } + if (recorder->audio && !audio_pkt + && !sc_queue_is_empty(&recorder->audio_queue)) { + // A new packet may be assigned to audio_pkt and be processed + break; + } sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } - // if stopped is set, continue to process the remaining events (to - // finish the recording) before actually stopping + // If stopped is set, continue to process the remaining events (to + // finish the recording) before actually stopping. + + // If there is no audio, then the audio_queue will remain empty forever + // and audio_pkt will always be NULL. + assert(recorder->audio + || (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue))); + + if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) { + sc_queue_take(&recorder->video_queue, next, &video_pkt); + } + + if (!audio_pkt && !sc_queue_is_empty(&recorder->audio_queue)) { + sc_queue_take(&recorder->audio_queue, next, &audio_pkt); + } - if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) { + if (recorder->stopped && !video_pkt && !audio_pkt) { + assert(sc_queue_is_empty(&recorder->video_queue)); + assert(sc_queue_is_empty(&recorder->audio_queue)); sc_mutex_unlock(&recorder->mutex); break; } - struct sc_record_packet *rec; - sc_queue_take(&recorder->video_queue, next, &rec); + assert(video_pkt || audio_pkt); // at least one sc_mutex_unlock(&recorder->mutex); - if (rec->packet->pts == AV_NOPTS_VALUE) { - // Ignore further config packets (e.g. on device orientation - // change). The next non-config packet will have the config packet - // data prepended. - sc_record_packet_delete(rec); - } else { - assert(rec->packet->pts != AV_NOPTS_VALUE); - - if (!previous) { - // This is the first non-config packet - assert(pts_origin == AV_NOPTS_VALUE); - pts_origin = rec->packet->pts; - rec->packet->pts = 0; - rec->packet->dts = 0; - previous = rec; + // Ignore further config packets (e.g. on device orientation + // change). The next non-config packet will have the config packet + // data prepended. + if (video_pkt && video_pkt->packet->pts == AV_NOPTS_VALUE) { + sc_record_packet_delete(video_pkt); + video_pkt = NULL; + } + + if (audio_pkt && audio_pkt->packet->pts == AV_NOPTS_VALUE) { + sc_record_packet_delete(audio_pkt); + audio_pkt= NULL; + } + + if (pts_origin == AV_NOPTS_VALUE) { + if (!recorder->audio) { + assert(video_pkt); + pts_origin = video_pkt->packet->pts; + } else if (video_pkt && audio_pkt) { + pts_origin = + MIN(video_pkt->packet->pts, audio_pkt->packet->pts); + } else { + // We need both video and audio packets to initialize pts_origin continue; } + } - assert(previous); - assert(pts_origin != AV_NOPTS_VALUE); + assert(pts_origin != AV_NOPTS_VALUE); + + if (video_pkt) { + video_pkt->packet->pts -= pts_origin; + video_pkt->packet->dts = video_pkt->packet->pts; + + if (video_pkt_previous) { + // we now know the duration of the previous packet + video_pkt_previous->packet->duration = + video_pkt->packet->pts - video_pkt_previous->packet->pts; + + bool ok = sc_recorder_write_video(recorder, + video_pkt_previous->packet); + sc_record_packet_delete(video_pkt_previous); + if (!ok) { + LOGE("Could not record video packet"); + error = true; + goto end; + } + } - rec->packet->pts -= pts_origin; - rec->packet->dts = rec->packet->pts; + video_pkt_previous = video_pkt; + video_pkt = NULL; + } - // we now know the duration of the previous packet - previous->packet->duration = - rec->packet->pts - previous->packet->pts; + if (audio_pkt) { + audio_pkt->packet->pts -= pts_origin; + audio_pkt->packet->dts = audio_pkt->packet->pts; - bool ok = sc_recorder_write(recorder, previous->packet); - sc_record_packet_delete(previous); + bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet); if (!ok) { - LOGE("Could not record packet"); - return false; + LOGE("Could not record audio packet"); + error = true; + goto end; } - previous = rec; + sc_record_packet_delete(audio_pkt); + audio_pkt = NULL; } } - // Write the last packet - struct sc_record_packet *last = previous; + // Write the last video packet + struct sc_record_packet *last = video_pkt_previous; if (last) { // assign an arbitrary duration to the last packet last->packet->duration = 100000; - bool ok = sc_recorder_write(recorder, last->packet); + bool ok = sc_recorder_write_video(recorder, last->packet); if (!ok) { // failing to write the last frame is not very serious, no // future frame may depend on it, so the resulting file @@ -302,10 +450,18 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { int ret = av_write_trailer(recorder->ctx); if (ret < 0) { LOGE("Failed to write trailer to %s", recorder->filename); - return false; + error = false; } - return true; +end: + if (video_pkt) { + sc_record_packet_delete(video_pkt); + } + if (audio_pkt) { + sc_record_packet_delete(audio_pkt); + } + + return !error; } static bool @@ -321,6 +477,14 @@ sc_recorder_record(struct sc_recorder *recorder) { return false; } + if (recorder->audio) { + ok = sc_recorder_wait_audio_stream(recorder); + if (!ok) { + sc_recorder_close_output_file(recorder); + return false; + } + } + // If recorder->stopped, process any queued packet anyway ok = sc_recorder_process_packets(recorder); @@ -339,6 +503,7 @@ run_recorder(void *data) { recorder->stopped = true; // Discard pending packets sc_recorder_queue_clear(&recorder->video_queue); + sc_recorder_queue_clear(&recorder->audio_queue); sc_mutex_unlock(&recorder->mutex); if (success) { @@ -406,6 +571,8 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, return false; } + rec->packet->stream_index = recorder->video_stream_index; + sc_queue_push(&recorder->video_queue, next, rec); sc_cond_signal(&recorder->queue_cond); @@ -413,9 +580,66 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, return true; } +static bool +sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, + const AVCodec *codec) { + struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); + assert(recorder->audio); + assert(codec); + + sc_mutex_lock(&recorder->mutex); + recorder->audio_codec = codec; + sc_cond_signal(&recorder->stream_cond); + sc_mutex_unlock(&recorder->mutex); + + return true; +} + +static void +sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); + assert(recorder->audio); + + sc_mutex_lock(&recorder->mutex); + // EOS also stops the recorder + recorder->stopped = true; + sc_cond_signal(&recorder->queue_cond); + sc_mutex_unlock(&recorder->mutex); +} + +static bool +sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, + const AVPacket *packet) { + struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); + assert(recorder->audio); + + sc_mutex_lock(&recorder->mutex); + + if (recorder->stopped) { + // reject any new packet + sc_mutex_unlock(&recorder->mutex); + return false; + } + + struct sc_record_packet *rec = sc_record_packet_new(packet); + if (!rec) { + LOG_OOM(); + sc_mutex_unlock(&recorder->mutex); + return false; + } + + rec->packet->stream_index = recorder->audio_stream_index; + + sc_queue_push(&recorder->audio_queue, next, rec); + sc_cond_signal(&recorder->queue_cond); + + sc_mutex_unlock(&recorder->mutex); + return true; +} + bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, + enum sc_record_format format, bool audio, struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { recorder->filename = strdup(filename); @@ -439,10 +663,17 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_queue_cond_destroy; } + recorder->audio = audio; + sc_queue_init(&recorder->video_queue); + sc_queue_init(&recorder->audio_queue); recorder->stopped = false; recorder->video_codec = NULL; + recorder->audio_codec = NULL; + + recorder->video_stream_index = -1; + recorder->audio_stream_index = -1; recorder->format = format; recorder->declared_frame_size = declared_frame_size; @@ -459,6 +690,16 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_packet_sink.ops = &video_ops; + if (audio) { + static const struct sc_packet_sink_ops audio_ops = { + .open = sc_recorder_audio_packet_sink_open, + .close = sc_recorder_audio_packet_sink_close, + .push = sc_recorder_audio_packet_sink_push, + }; + + recorder->audio_packet_sink.ops = &audio_ops; + } + return true; error_queue_cond_destroy: diff --git a/app/src/recorder.h b/app/src/recorder.h index b7a10e5e..ed880de0 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -20,7 +20,10 @@ struct sc_record_packet { struct sc_recorder_queue SC_QUEUE(struct sc_record_packet); struct sc_recorder { - struct sc_packet_sink video_packet_sink; // packet sink trait + struct sc_packet_sink video_packet_sink; + struct sc_packet_sink audio_packet_sink; + + bool audio; char *filename; enum sc_record_format format; @@ -33,10 +36,15 @@ struct sc_recorder { // set on sc_recorder_stop(), packet_sink close or recording failure bool stopped; struct sc_recorder_queue video_queue; + struct sc_recorder_queue audio_queue; - // wake up the recorder thread once the video codec is known + // wake up the recorder thread once the video or audio codec is known sc_cond stream_cond; const AVCodec *video_codec; + const AVCodec *audio_codec; + + int video_stream_index; + int audio_stream_index; const struct sc_recorder_callbacks *cbs; void *cbs_userdata; @@ -49,7 +57,7 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, + enum sc_record_format format, bool audio, struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 437ae650..6750f6a1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -424,8 +424,8 @@ scrcpy(struct scrcpy_options *options) { .on_ended = sc_recorder_on_ended, }; if (!sc_recorder_init(&s->recorder, options->record_filename, - options->record_format, info->frame_size, - &recorder_cbs, NULL)) { + options->record_format, options->audio, + info->frame_size, &recorder_cbs, NULL)) { goto end; } recorder_initialized = true; @@ -436,6 +436,10 @@ scrcpy(struct scrcpy_options *options) { recorder_started = true; sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.video_packet_sink); + if (options->audio) { + sc_demuxer_add_sink(&s->audio_demuxer, + &s->recorder.audio_packet_sink); + } } struct sc_controller *controller = NULL; From 13a3395a332eca55c884edc05a5052457b519f60 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:09:18 +0100 Subject: [PATCH 0720/1133] Disable audio on initialization error By default, audio is enabled (--no-audio must be explicitly passed to disable it). However, some devices may not support audio capture (typically devices below Android 11, or Android 11 when the shell application is not foreground on start). In that case, make the server notify the client to dynamically disable audio forwarding so that it does not wait indefinitely for an audio stream. Also disable audio on unknown codec or missing decoder on the client-side, for the same reasons. PR #3757 --- app/src/demuxer.c | 28 +++++++++++++-- app/src/recorder.c | 34 ++++++++++++++++++- app/src/recorder.h | 11 ++++++ app/src/trait/packet_sink.h | 10 ++++++ .../com/genymobile/scrcpy/AudioEncoder.java | 4 +++ .../java/com/genymobile/scrcpy/Streamer.java | 6 ++++ 6 files changed, 90 insertions(+), 3 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 968e6db0..482f2e04 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -158,6 +158,16 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) { return true; } +static void +sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) { + for (unsigned i = 0; i < demuxer->sink_count; ++i) { + struct sc_packet_sink *sink = demuxer->sinks[i]; + if (sink->ops->disable) { + sink->ops->disable(sink); + } + } +} + static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; @@ -168,19 +178,33 @@ run_demuxer(void *data) { uint32_t raw_codec_id; bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); if (!ok) { + LOGE("Demuxer '%s': stream disabled due to connection error", + demuxer->name); + eos = true; + goto end; + } + + if (raw_codec_id == 0) { + LOGW("Demuxer '%s': stream explicitly disabled by the device", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); eos = true; goto end; } enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id); if (codec_id == AV_CODEC_ID_NONE) { - // Error already logged + LOGE("Demuxer '%s': stream disabled due to unsupported codec", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); goto end; } const AVCodec *codec = avcodec_find_decoder(codec_id); if (!codec) { - LOGE("Demuxer '%s': decoder not found", demuxer->name); + LOGE("Demuxer '%s': stream disabled due to missing decoder", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); goto end; } diff --git a/app/src/recorder.c b/app/src/recorder.c index 93c3321f..c694f022 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -194,9 +194,17 @@ sc_recorder_wait_video_stream(struct sc_recorder *recorder) { static bool sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->audio_codec && !recorder->stopped) { + while (!recorder->audio_codec && !recorder->audio_disabled + && !recorder->stopped) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } + + if (recorder->audio_disabled) { + // Reset audio flag. From there, the recorder thread may access this + // flag without any mutex. + recorder->audio = false; + } + const AVCodec *codec = recorder->audio_codec; sc_mutex_unlock(&recorder->mutex); @@ -585,6 +593,8 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); assert(codec); sc_mutex_lock(&recorder->mutex); @@ -599,6 +609,8 @@ static void sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder @@ -612,6 +624,8 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); sc_mutex_lock(&recorder->mutex); @@ -637,6 +651,22 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, return true; } +static void +sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); + assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); + assert(!recorder->audio_codec); + + LOGW("Audio stream recording disabled"); + + sc_mutex_lock(&recorder->mutex); + recorder->audio_disabled = true; + sc_cond_signal(&recorder->stream_cond); + sc_mutex_unlock(&recorder->mutex); +} + bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool audio, @@ -671,6 +701,7 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_codec = NULL; recorder->audio_codec = NULL; + recorder->audio_disabled = false; recorder->video_stream_index = -1; recorder->audio_stream_index = -1; @@ -695,6 +726,7 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, .open = sc_recorder_audio_packet_sink_open, .close = sc_recorder_audio_packet_sink_close, .push = sc_recorder_audio_packet_sink_push, + .disable = sc_recorder_audio_packet_sink_disable, }; recorder->audio_packet_sink.ops = &audio_ops; diff --git a/app/src/recorder.h b/app/src/recorder.h index ed880de0..6fe72401 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -23,6 +23,14 @@ struct sc_recorder { struct sc_packet_sink video_packet_sink; struct sc_packet_sink audio_packet_sink; + /* The audio flag is unprotected: + * - it is initialized from sc_recorder_init() from the main thread; + * - it may be reset once from the recorder thread if the audio is + * disabled dynamically. + * + * Therefore, once the recorder thread is started, only the recorder thread + * may access it without data races. + */ bool audio; char *filename; @@ -42,6 +50,9 @@ struct sc_recorder { sc_cond stream_cond; const AVCodec *video_codec; const AVCodec *audio_codec; + // Instead of providing an audio_codec, the demuxer may notify that the + // stream is disabled if the device could not capture audio + bool audio_disabled; int video_stream_index; int audio_stream_index; diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h index 9fc9fd24..099c8c52 100644 --- a/app/src/trait/packet_sink.h +++ b/app/src/trait/packet_sink.h @@ -23,6 +23,16 @@ struct sc_packet_sink_ops { bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); void (*close)(struct sc_packet_sink *sink); bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); + + /*/ + * Called when the input stream has been disabled at runtime. + * + * If it is called, then open(), close() and push() will never be called. + * + * It is useful to notify the recorder that the requested audio stream has + * finally been disabled because the device could not capture it. + */ + void (*disable)(struct sc_packet_sink *sink); }; #endif diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 74481f99..8bc25e8e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -255,6 +255,10 @@ public final class AudioEncoder { outputThread.start(); waitEnded(); + } catch (Throwable e) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(); + throw e; } finally { // Cleanup everything (either at the end or on error at any step of the initialization) if (mediaCodecThread != null) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index 77d9eefa..7cc065eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -40,6 +40,12 @@ public final class Streamer { } } + public void writeDisableStream() throws IOException { + // Writing 0 (32-bit) as codec-id means that the device disables the stream (because it could not capture) + byte[] zeros = new byte[4]; + IO.writeFully(fd, zeros, 0, zeros.length); + } + public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { if (config && codec == AudioCodec.OPUS) { fixOpusConfigPacket(buffer); From 80c0780b7779acc27a563ee5acdd3ac645108763 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 15:24:08 +0100 Subject: [PATCH 0721/1133] Disable audio before Android 11 The permission "android.permission.RECORD_AUDIO" has been added for shell in Android 11. Moreover, on lower versions, it may make the server segfault on the device (happened on a Nexus 5 with Android 6.0.1). Refs PR #3757 --- .../src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 8bc25e8e..d06898d6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -203,6 +203,12 @@ public final class AudioEncoder { @TargetApi(Build.VERSION_CODES.M) public void encode() throws IOException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + Ln.w("Audio disabled: it is not supported before Android 11"); + streamer.writeDisableStream(); + return; + } + MediaCodec mediaCodec = null; AudioRecord recorder = null; From 17d5301c0fd6f93c5b9d4dd325e4303739d93ffb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 00:55:36 +0100 Subject: [PATCH 0722/1133] Record at least video packets on stop If the recorder is stopped while it has not received any audio packet yet, make sure the video stream is correctly recorded. PR #3757 --- app/src/recorder.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index c694f022..9fc15dac 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -249,8 +249,10 @@ sc_recorder_process_header(struct sc_recorder *recorder) { sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } - if (sc_recorder_has_empty_queues(recorder)) { + if (sc_queue_is_empty(&recorder->video_queue)) { assert(recorder->stopped); + // Don't process anything if there are not at least video packets (when + // the recorder is stopped) sc_mutex_unlock(&recorder->mutex); return false; } @@ -258,8 +260,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { struct sc_record_packet *video_pkt; sc_queue_take(&recorder->video_queue, next, &video_pkt); - struct sc_record_packet *audio_pkt; - if (recorder->audio) { + struct sc_record_packet *audio_pkt = NULL; + if (!sc_queue_is_empty(&recorder->audio_queue)) { + assert(recorder->audio); sc_queue_take(&recorder->audio_queue, next, &audio_pkt); } @@ -280,7 +283,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { goto end; } - if (recorder->audio) { + if (audio_pkt) { if (audio_pkt->packet->pts != AV_NOPTS_VALUE) { LOGE("The first audio packet is not a config packet"); goto end; @@ -305,7 +308,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { end: sc_record_packet_delete(video_pkt); - if (recorder->audio) { + if (audio_pkt) { sc_record_packet_delete(audio_pkt); } @@ -393,6 +396,16 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { } else if (video_pkt && audio_pkt) { pts_origin = MIN(video_pkt->packet->pts, audio_pkt->packet->pts); + } else if (recorder->stopped) { + if (video_pkt) { + // The recorder is stopped without audio, record the video + // packets + pts_origin = video_pkt->packet->pts; + } else { + // Fail if there is no video + error = true; + goto end; + } } else { // We need both video and audio packets to initialize pts_origin continue; From a1802dab763d5031bc26576e45ab1d388a8904fc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:21:14 +0100 Subject: [PATCH 0723/1133] Remove default bit-rate on client side If no bit-rate is passed, let the server use the default value (8Mbps). This avoids to define a default value on both sides, and to pass the default bit-rate as an argument when starting the server. PR #3757 --- app/meson.build | 4 ---- app/scrcpy.1 | 2 +- app/src/cli.c | 2 +- app/src/options.c | 2 +- app/src/server.c | 4 +++- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/meson.build b/app/meson.build index b6a772a9..2ea3b317 100644 --- a/app/meson.build +++ b/app/meson.build @@ -195,10 +195,6 @@ conf.set('PORTABLE', get_option('portable')) conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183') conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') -# the default video bitrate, in bits/second -# overridden by option --bit-rate -conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps - # run a server debugger and wait for a client to be attached conf.set('SERVER_DEBUGGER', get_option('server_debugger')) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0f1147da..186d8ad5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -23,7 +23,7 @@ Make scrcpy window always on top (above other windows). .BI "\-b, \-\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). -Default is 8000000. +Default is 8M (8000000). .TP .BI "\-\-codec " name diff --git a/app/src/cli.c b/app/src/cli.c index 4f023da0..d7a0a7ae 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -107,7 +107,7 @@ static const struct sc_option options[] = { .argdesc = "value", .text = "Encode the video at the given bit-rate, expressed in bits/s. " "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" - "Default is " STR(DEFAULT_BIT_RATE) ".", + "Default is 8M (8000000).", }, { .longopt_id = OPT_CODEC, diff --git a/app/src/options.c b/app/src/options.c index 0854067f..64ec5b3b 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -28,7 +28,7 @@ const struct scrcpy_options scrcpy_options_default = { .count = 2, }, .max_size = 0, - .bit_rate = DEFAULT_BIT_RATE, + .bit_rate = 0, .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .rotation = 0, diff --git a/app/src/server.c b/app/src/server.c index 88e3421f..6bf0eb6e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -215,8 +215,10 @@ execute_server(struct sc_server *server, ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); - ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + if (params->bit_rate) { + ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + } if (!params->audio) { ADD_PARAM("audio=false"); } From cee40ca047f02c68e59372ae7305fce6d6ead8f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Feb 2023 21:19:36 +0100 Subject: [PATCH 0724/1133] Rename --codec to --video-codec This prepares the introduction of --audio-codec. PR #3757 --- README.md | 10 +++---- app/data/bash-completion/scrcpy | 4 +-- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 14 +++++----- app/src/cli.c | 27 ++++++++++++++----- app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 2 +- app/src/server.c | 5 ++-- app/src/server.h | 2 +- .../java/com/genymobile/scrcpy/Options.java | 10 +++---- .../com/genymobile/scrcpy/ScreenEncoder.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 16 +++++------ 13 files changed, 56 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index a19aec0f..4b898d26 100644 --- a/README.md +++ b/README.md @@ -258,9 +258,9 @@ The video codec can be selected. The possible values are `h264` (default), `h265` and `av1`: ```bash -scrcpy --codec=h264 # default -scrcpy --codec=h265 -scrcpy --codec=av1 +scrcpy --video-codec=h264 # default +scrcpy --video-codec=h265 +scrcpy --video-codec=av1 ``` @@ -277,8 +277,8 @@ To list the available encoders, you can pass an invalid encoder name; the error will give the available encoders: ```bash -scrcpy --encoder=_ # for the default codec -scrcpy --codec=h265 --encoder=_ # for a specific codec +scrcpy --encoder=_ # for the default codec +scrcpy --video-codec=h265 --encoder=_ # for a specific codec ``` ### Capture diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 22dc4cee..d92cf009 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,7 +3,6 @@ _scrcpy() { local opts=" --always-on-top -b --bit-rate= - --codec= --codec-options= --crop= -d --select-usb @@ -55,6 +54,7 @@ _scrcpy() { --v4l2-sink= -V --verbosity= -v --version + --video-codec= -w --stay-awake --window-borderless --window-title= @@ -66,7 +66,7 @@ _scrcpy() { _init_completion -s || return case "$prev" in - --codec) + --video-codec) COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur")) return ;; diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 17e1de9f..b9c94e1e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,7 +10,6 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' {-b,--bit-rate=}'[Encode the video at the given bit-rate]' - '--codec=[Select the video codec]:codec:(h264 h265 av1)' '--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' @@ -60,6 +59,7 @@ arguments=( '--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]' {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' {-v,--version}'[Print the version of scrcpy]' + '--video-codec=[Select the video codec]:codec:(h264 h265 av1)' {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' '--window-borderless[Disable window decorations \(display borderless window\)]' '--window-title=[Set a custom window title]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 186d8ad5..32bb8464 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,12 +25,6 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 8M (8000000). -.TP -.BI "\-\-codec " name -Select a video codec (h264, h265 or av1). - -Default is h264. - .TP .BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] Set a list of comma-separated key:type=value options for the device encoder. @@ -82,7 +76,7 @@ Also see \fB\-d\fR (\fB\-\-select\-usb\fR). .TP .BI "\-\-encoder " name -Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-codec\fR). +Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-video\-codec\fR). .TP .B \-\-force\-adb\-forward @@ -329,6 +323,12 @@ Default is "info" for release builds, "debug" for debug builds. .B \-v, \-\-version Print the version of scrcpy. +.TP +.BI "\-\-video\-codec " name +Select a video codec (h264, h265 or av1). + +Default is h264. + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index d7a0a7ae..9163ba60 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -59,6 +59,7 @@ enum { OPT_PRINT_FPS, OPT_NO_POWER_ON, OPT_CODEC, + OPT_VIDEO_CODEC, OPT_NO_AUDIO, }; @@ -110,11 +111,13 @@ static const struct sc_option options[] = { "Default is 8M (8000000).", }, { + // Not really deprecated (--codec has never been released), but without + // declaring an explicit --codec option, getopt_long() partial matching + // behavior would consider --codec to be equivalent to --codec-options, + // which would be confusing. .longopt_id = OPT_CODEC, .longopt = "codec", - .argdesc = "name", - .text = "Select a video codec (h264, h265 or av1).\n" - "Default is h264.", + .argdesc = "value", }, { .longopt_id = OPT_CODEC_OPTIONS, @@ -177,7 +180,7 @@ static const struct sc_option options[] = { .longopt = "encoder", .argdesc = "name", .text = "Use a specific MediaCodec encoder (depending on the codec " - "provided by --codec).", + "provided by --video-codec).", }, { .longopt_id = OPT_FORCE_ADB_FORWARD, @@ -519,6 +522,13 @@ static const struct sc_option options[] = { .longopt = "version", .text = "Print the version of scrcpy.", }, + { + .longopt_id = OPT_VIDEO_CODEC, + .longopt = "video-codec", + .argdesc = "name", + .text = "Select a video codec (h264, h265 or av1).\n" + "Default is h264.", + }, { .shortopt = 'w', .longopt = "stay-awake", @@ -1395,7 +1405,7 @@ guess_record_format(const char *filename) { } static bool -parse_codec(const char *optarg, enum sc_codec *codec) { +parse_video_codec(const char *optarg, enum sc_codec *codec) { if (!strcmp(optarg, "h264")) { *codec = SC_CODEC_H264; return true; @@ -1408,7 +1418,7 @@ parse_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_AV1; return true; } - LOGE("Unsupported codec: %s (expected h264, h265 or av1)", optarg); + LOGE("Unsupported video codec: %s (expected h264, h265 or av1)", optarg); return false; } @@ -1649,7 +1659,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->start_fps_counter = true; break; case OPT_CODEC: - if (!parse_codec(optarg, &opts->codec)) { + LOGW("--codec is deprecated, use --video-codec instead."); + // fall through + case OPT_VIDEO_CODEC: + if (!parse_video_codec(optarg, &opts->video_codec)) { return false; } break; diff --git a/app/src/options.c b/app/src/options.c index 64ec5b3b..0368ffcc 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -13,7 +13,7 @@ const struct scrcpy_options scrcpy_options_default = { .v4l2_device = NULL, #endif .log_level = SC_LOG_LEVEL_INFO, - .codec = SC_CODEC_H264, + .video_codec = SC_CODEC_H264, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index 7bf30011..f6ba324b 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -99,7 +99,7 @@ struct scrcpy_options { const char *v4l2_device; #endif enum sc_log_level log_level; - enum sc_codec codec; + enum sc_codec video_codec; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6750f6a1..35b999a8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -314,7 +314,7 @@ scrcpy(struct scrcpy_options *options) { .select_usb = options->select_usb, .select_tcpip = options->select_tcpip, .log_level = options->log_level, - .codec = options->codec, + .video_codec = options->video_codec, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index 6bf0eb6e..20433ea0 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -222,8 +222,9 @@ execute_server(struct sc_server *server, if (!params->audio) { ADD_PARAM("audio=false"); } - if (params->codec != SC_CODEC_H264) { - ADD_PARAM("codec=%s", sc_server_get_codec_name(params->codec)); + if (params->video_codec != SC_CODEC_H264) { + ADD_PARAM("video_codec=%s", + sc_server_get_codec_name(params->video_codec)); } if (params->max_size) { ADD_PARAM("max_size=%" PRIu16, params->max_size); diff --git a/app/src/server.h b/app/src/server.h index 3005ebd2..8914349f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -25,7 +25,7 @@ struct sc_server_params { uint32_t scid; const char *req_serial; enum sc_log_level log_level; - enum sc_codec codec; + enum sc_codec video_codec; const char *crop; const char *codec_options; const char *encoder_name; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 07789974..73a303d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -10,7 +10,7 @@ public class Options { private int scid = -1; // 31-bit non-negative value, or -1 private boolean audio = true; private int maxSize; - private VideoCodec codec = VideoCodec.H264; + private VideoCodec videoCodec = VideoCodec.H264; private int bitRate = 8000000; private int maxFps; private int lockVideoOrientation = -1; @@ -66,12 +66,12 @@ public class Options { this.maxSize = maxSize; } - public VideoCodec getCodec() { - return codec; + public VideoCodec getVideoCodec() { + return videoCodec; } - public void setCodec(VideoCodec codec) { - this.codec = codec; + public void setVideoCodec(VideoCodec videoCodec) { + this.videoCodec = videoCodec; } public int getBitRate() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index fdd23bf3..30e988f0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -231,7 +231,7 @@ public class ScreenEncoder implements Device.RotationListener { if (encoders != null && encoders.length > 0) { msg.append("\nTry to use one of the available encoders:"); for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); + msg.append("\n scrcpy --video-codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); } } return msg.toString(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index eb0c1384..a926f443 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -96,7 +96,6 @@ public final class Server { AudioEncoder audioEncoder = null; try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { - VideoCodec codec = options.getCodec(); if (options.getSendDeviceMeta()) { Size videoSize = device.getScreenInfo().getVideoSize(); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); @@ -116,9 +115,10 @@ public final class Server { audioEncoder.start(); } - Streamer videoStreamer = new Streamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), - codecOptions, options.getEncoderName(), options.getDownsizeOnError()); + Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), + options.getSendFrameMeta()); + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions, + options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous screenEncoder.streamScreen(); @@ -195,12 +195,12 @@ public final class Server { boolean audio = Boolean.parseBoolean(value); options.setAudio(audio); break; - case "codec": - VideoCodec codec = VideoCodec.findByName(value); - if (codec == null) { + case "video_codec": + VideoCodec videoCodec = VideoCodec.findByName(value); + if (videoCodec == null) { throw new IllegalArgumentException("Video codec " + value + " not supported"); } - options.setCodec(codec); + options.setVideoCodec(videoCodec); break; case "max_size": int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 From 9087e85c3f076c54b81a8895a9936bbb49a41812 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 19:56:44 +0100 Subject: [PATCH 0725/1133] Rename --bit-rate to --video-bit-rate This prepares the introduction of --audio-bit-rate. PR #3757 --- README.md | 4 ++-- app/data/bash-completion/scrcpy | 4 ++-- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 14 ++++++++++++-- app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 2 +- app/src/server.c | 4 ++-- app/src/server.h | 2 +- app/tests/test_cli.c | 4 ++-- .../main/java/com/genymobile/scrcpy/Options.java | 10 +++++----- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 8 ++++---- .../main/java/com/genymobile/scrcpy/Server.java | 8 ++++---- 14 files changed, 39 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 4b898d26..81371b92 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. The default bit-rate is 8 Mbps. To change the video bitrate (e.g. to 2 Mbps): ```bash -scrcpy --bit-rate=2M +scrcpy --video-bit-rate=2M scrcpy -b 2M # short version ``` @@ -444,7 +444,7 @@ none found, try running `adb disconnect`, and then run those two commands again. It may be useful to decrease the bit-rate and the resolution: ```bash -scrcpy --bit-rate=2M --max-size=800 +scrcpy --video-bit-rate=2M --max-size=800 scrcpy -b2M -m800 # short version ``` diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index d92cf009..1fe79765 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -2,7 +2,7 @@ _scrcpy() { local cur prev words cword local opts=" --always-on-top - -b --bit-rate= + -b --video-bit-rate= --codec-options= --crop= -d --select-usb @@ -104,7 +104,7 @@ _scrcpy() { COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur})) return ;; - -b|--bit-rate \ + -b|--video-bit-rate \ |--codec-options \ |--crop \ |--display \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index b9c94e1e..ac3ec023 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -9,7 +9,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' - {-b,--bit-rate=}'[Encode the video at the given bit-rate]' + {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 32bb8464..41ce28a4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -20,7 +20,7 @@ provides display and control of Android devices connected on USB (or over TCP/IP Make scrcpy window always on top (above other windows). .TP -.BI "\-b, \-\-bit\-rate " value +.BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Default is 8M (8000000). diff --git a/app/src/cli.c b/app/src/cli.c index 9163ba60..57e85aa4 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -19,6 +19,7 @@ enum { OPT_RENDER_EXPIRED_FRAMES = 1000, + OPT_BIT_RATE, OPT_WINDOW_TITLE, OPT_PUSH_TARGET, OPT_ALWAYS_ON_TOP, @@ -104,12 +105,18 @@ static const struct sc_option options[] = { }, { .shortopt = 'b', - .longopt = "bit-rate", + .longopt = "video-bit-rate", .argdesc = "value", .text = "Encode the video at the given bit-rate, expressed in bits/s. " "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 8M (8000000).", }, + { + // deprecated + .longopt_id = OPT_BIT_RATE, + .longopt = "bit-rate", + .argdesc = "value", + }, { // Not really deprecated (--codec has never been released), but without // declaring an explicit --codec option, getopt_long() partial matching @@ -1432,8 +1439,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], int c; while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) { switch (c) { + case OPT_BIT_RATE: + LOGW("--bit-rate is deprecated, use --video-bit-rate instead."); + // fall through case 'b': - if (!parse_bit_rate(optarg, &opts->bit_rate)) { + if (!parse_bit_rate(optarg, &opts->video_bit_rate)) { return false; } break; diff --git a/app/src/options.c b/app/src/options.c index 0368ffcc..a087f507 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -28,7 +28,7 @@ const struct scrcpy_options scrcpy_options_default = { .count = 2, }, .max_size = 0, - .bit_rate = 0, + .video_bit_rate = 0, .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .rotation = 0, diff --git a/app/src/options.h b/app/src/options.h index f6ba324b..d22078e4 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -108,7 +108,7 @@ struct scrcpy_options { uint16_t tunnel_port; struct sc_shortcut_mods shortcut_mods; uint16_t max_size; - uint32_t bit_rate; + uint32_t video_bit_rate; uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; uint8_t rotation; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 35b999a8..b09de541 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -320,7 +320,7 @@ scrcpy(struct scrcpy_options *options) { .tunnel_host = options->tunnel_host, .tunnel_port = options->tunnel_port, .max_size = options->max_size, - .bit_rate = options->bit_rate, + .video_bit_rate = options->video_bit_rate, .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, diff --git a/app/src/server.c b/app/src/server.c index 20433ea0..7c8fba8d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -216,8 +216,8 @@ execute_server(struct sc_server *server, ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); - if (params->bit_rate) { - ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); + if (params->video_bit_rate) { + ADD_PARAM("video_bit_rate=%" PRIu32, params->video_bit_rate); } if (!params->audio) { ADD_PARAM("audio=false"); diff --git a/app/src/server.h b/app/src/server.h index 8914349f..d3019288 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -33,7 +33,7 @@ struct sc_server_params { uint32_t tunnel_host; uint16_t tunnel_port; uint16_t max_size; - uint32_t bit_rate; + uint32_t video_bit_rate; uint16_t max_fps; int8_t lock_video_orientation; bool control; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 5ea54b7f..3e9a248a 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -46,7 +46,7 @@ static void test_options(void) { char *argv[] = { "scrcpy", "--always-on-top", - "--bit-rate", "5M", + "--video-bit-rate", "5M", "--crop", "100:200:300:400", "--fullscreen", "--max-fps", "30", @@ -75,7 +75,7 @@ static void test_options(void) { const struct scrcpy_options *opts = &args.opts; assert(opts->always_on_top); - assert(opts->bit_rate == 5000000); + assert(opts->video_bit_rate == 5000000); assert(!strcmp(opts->crop, "100:200:300:400")); assert(opts->fullscreen); assert(opts->max_fps == 30); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 73a303d8..53257cf3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,7 +11,7 @@ public class Options { private boolean audio = true; private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; - private int bitRate = 8000000; + private int videoBitRate = 8000000; private int maxFps; private int lockVideoOrientation = -1; private boolean tunnelForward; @@ -74,12 +74,12 @@ public class Options { this.videoCodec = videoCodec; } - public int getBitRate() { - return bitRate; + public int getVideoBitRate() { + return videoBitRate; } - public void setBitRate(int bitRate) { - this.bitRate = bitRate; + public void setVideoBitRate(int videoBitRate) { + this.videoBitRate = videoBitRate; } public int getMaxFps() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 30e988f0..d646995b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -35,18 +35,18 @@ public class ScreenEncoder implements Device.RotationListener { private final Streamer streamer; private final String encoderName; private final List codecOptions; - private final int bitRate; + private final int videoBitRate; private final int maxFps; private final boolean downsizeOnError; private boolean firstFrameSent; private int consecutiveErrors; - public ScreenEncoder(Device device, Streamer streamer, int bitRate, int maxFps, List codecOptions, String encoderName, + public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { this.device = device; this.streamer = streamer; - this.bitRate = bitRate; + this.videoBitRate = videoBitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; this.encoderName = encoderName; @@ -65,7 +65,7 @@ public class ScreenEncoder implements Device.RotationListener { public void streamScreen() throws IOException, ConfigurationException { Codec codec = streamer.getCodec(); MediaCodec mediaCodec = createMediaCodec(codec, encoderName); - MediaFormat format = createFormat(codec.getMimeType(), bitRate, maxFps, codecOptions); + MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a926f443..f764804c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -117,7 +117,7 @@ public final class Server { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions, + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), codecOptions, options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous @@ -206,9 +206,9 @@ public final class Server { int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 options.setMaxSize(maxSize); break; - case "bit_rate": - int bitRate = Integer.parseInt(value); - options.setBitRate(bitRate); + case "video_bit_rate": + int videoBitRate = Integer.parseInt(value); + options.setVideoBitRate(videoBitRate); break; case "max_fps": int maxFps = Integer.parseInt(value); From 31555fa5309b80f6e65a19dc3aa8932fe9265b8d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Feb 2023 21:46:34 +0100 Subject: [PATCH 0726/1133] Rename --codec-options to --video-codec-options This prepares the introduction of --audio-codec-options. PR #3757 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 20 +++++++------- app/src/cli.c | 27 +++++++++++++------ app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 2 +- app/src/server.c | 8 +++--- app/src/server.h | 2 +- .../java/com/genymobile/scrcpy/Options.java | 10 +++---- .../java/com/genymobile/scrcpy/Server.java | 11 ++++---- 11 files changed, 49 insertions(+), 39 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 1fe79765..167f736f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,7 +3,6 @@ _scrcpy() { local opts=" --always-on-top -b --video-bit-rate= - --codec-options= --crop= -d --select-usb --disable-screensaver @@ -55,6 +54,7 @@ _scrcpy() { -V --verbosity= -v --version --video-codec= + --video-codec-options= -w --stay-awake --window-borderless --window-title= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index ac3ec023..29bec42d 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,7 +10,6 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' - '--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' '--disable-screensaver[Disable screensaver while scrcpy is running]' @@ -60,6 +59,7 @@ arguments=( {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' {-v,--version}'[Print the version of scrcpy]' '--video-codec=[Select the video codec]:codec:(h264 h265 av1)' + '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' '--window-borderless[Disable window decorations \(display borderless window\)]' '--window-title=[Set a custom window title]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 41ce28a4..49d05a14 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,16 +25,6 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 8M (8000000). -.TP -.BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] -Set a list of comma-separated key:type=value options for the device encoder. - -The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. - -The list of possible codec options is available in the Android documentation -.UR https://d.android.com/reference/android/media/MediaFormat -.UE . - .TP .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy Crop the device screen on the server. @@ -329,6 +319,16 @@ Select a video codec (h264, h265 or av1). Default is h264. +.TP +.BI "\-\-video\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] +Set a list of comma-separated key:type=value options for the device video encoder. + +The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. + +The list of possible codec options is available in the Android documentation +.UR https://d.android.com/reference/android/media/MediaFormat +.UE . + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index 57e85aa4..cb1af46e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -38,6 +38,7 @@ enum { OPT_RENDER_DRIVER, OPT_NO_MIPMAPS, OPT_CODEC_OPTIONS, + OPT_VIDEO_CODEC_OPTIONS, OPT_FORCE_ADB_FORWARD, OPT_DISABLE_SCREENSAVER, OPT_SHORTCUT_MOD, @@ -127,16 +128,10 @@ static const struct sc_option options[] = { .argdesc = "value", }, { + // deprecated .longopt_id = OPT_CODEC_OPTIONS, .longopt = "codec-options", .argdesc = "key[:type]=value[,...]", - .text = "Set a list of comma-separated key:type=value options for the " - "device encoder.\n" - "The possible values for 'type' are 'int' (default), 'long', " - "'float' and 'string'.\n" - "The list of possible codec options is available in the " - "Android documentation: " - "", }, { .longopt_id = OPT_CROP, @@ -536,6 +531,18 @@ static const struct sc_option options[] = { .text = "Select a video codec (h264, h265 or av1).\n" "Default is h264.", }, + { + .longopt_id = OPT_VIDEO_CODEC_OPTIONS, + .longopt = "video-codec-options", + .argdesc = "key[:type]=value[,...]", + .text = "Set a list of comma-separated key:type=value options for the " + "device video encoder.\n" + "The possible values for 'type' are 'int' (default), 'long', " + "'float' and 'string'.\n" + "The list of possible codec options is available in the " + "Android documentation: " + "", + }, { .shortopt = 'w', .longopt = "stay-awake", @@ -1616,7 +1623,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->forward_key_repeat = false; break; case OPT_CODEC_OPTIONS: - opts->codec_options = optarg; + LOGW("--codec-options is deprecated, use --video-codec-options " + "instead."); + // fall through + case OPT_VIDEO_CODEC_OPTIONS: + opts->video_codec_options = optarg; break; case OPT_ENCODER_NAME: opts->encoder_name = optarg; diff --git a/app/src/options.c b/app/src/options.c index a087f507..0547da1b 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -7,7 +7,7 @@ const struct scrcpy_options scrcpy_options_default = { .window_title = NULL, .push_target = NULL, .render_driver = NULL, - .codec_options = NULL, + .video_codec_options = NULL, .encoder_name = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, diff --git a/app/src/options.h b/app/src/options.h index d22078e4..bde79687 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -93,7 +93,7 @@ struct scrcpy_options { const char *window_title; const char *push_target; const char *render_driver; - const char *codec_options; + const char *video_codec_options; const char *encoder_name; #ifdef HAVE_V4L2 const char *v4l2_device; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b09de541..2bb5794c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -328,7 +328,7 @@ scrcpy(struct scrcpy_options *options) { .audio = options->audio, .show_touches = options->show_touches, .stay_awake = options->stay_awake, - .codec_options = options->codec_options, + .video_codec_options = options->video_codec_options, .encoder_name = options->encoder_name, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, diff --git a/app/src/server.c b/app/src/server.c index 7c8fba8d..eb91b2b2 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -71,7 +71,7 @@ sc_server_params_destroy(struct sc_server_params *params) { // The server stores a copy of the params provided by the user free((char *) params->req_serial); free((char *) params->crop); - free((char *) params->codec_options); + free((char *) params->video_codec_options); free((char *) params->encoder_name); free((char *) params->tcpip_dst); } @@ -95,7 +95,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(req_serial); COPY(crop); - COPY(codec_options); + COPY(video_codec_options); COPY(encoder_name); COPY(tcpip_dst); #undef COPY @@ -255,8 +255,8 @@ execute_server(struct sc_server *server, if (params->stay_awake) { ADD_PARAM("stay_awake=true"); } - if (params->codec_options) { - ADD_PARAM("codec_options=%s", params->codec_options); + if (params->video_codec_options) { + ADD_PARAM("video_codec_options=%s", params->video_codec_options); } if (params->encoder_name) { ADD_PARAM("encoder_name=%s", params->encoder_name); diff --git a/app/src/server.h b/app/src/server.h index d3019288..352d2cae 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -27,7 +27,7 @@ struct sc_server_params { enum sc_log_level log_level; enum sc_codec video_codec; const char *crop; - const char *codec_options; + const char *video_codec_options; const char *encoder_name; struct sc_port_range port_range; uint32_t tunnel_host; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 53257cf3..9cfb7871 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -20,7 +20,7 @@ public class Options { private int displayId; private boolean showTouches; private boolean stayAwake; - private List codecOptions; + private List videoCodecOptions; private String encoderName; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; @@ -146,12 +146,12 @@ public class Options { this.stayAwake = stayAwake; } - public List getCodecOptions() { - return codecOptions; + public List getVideoCodecOptions() { + return videoCodecOptions; } - public void setCodecOptions(List codecOptions) { - this.codecOptions = codecOptions; + public void setVideoCodecOptions(List videoCodecOptions) { + this.videoCodecOptions = videoCodecOptions; } public String getEncoderName() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f764804c..4ff938b4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -61,7 +61,6 @@ public final class Server { private static void scrcpy(Options options) throws IOException, ConfigurationException { Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); - List codecOptions = options.getCodecOptions(); Thread initThread = startInitThread(options); @@ -117,8 +116,8 @@ public final class Server { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName(), options.getDownsizeOnError()); + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), + options.getVideoCodecOptions(), options.getEncoderName(), options.getDownsizeOnError()); try { // synchronous screenEncoder.streamScreen(); @@ -242,9 +241,9 @@ public final class Server { boolean stayAwake = Boolean.parseBoolean(value); options.setStayAwake(stayAwake); break; - case "codec_options": - List codecOptions = CodecOption.parse(value); - options.setCodecOptions(codecOptions); + case "video_codec_options": + List videoCodecOptions = CodecOption.parse(value); + options.setVideoCodecOptions(videoCodecOptions); break; case "encoder_name": if (!value.isEmpty()) { From e694619d53d1f38ed02b269be1649c7a67a11916 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 22:44:01 +0100 Subject: [PATCH 0727/1133] Rename --encoder to --video-encoder This prepares the introduction of --audio-encoder. PR #3757 --- README.md | 6 ++--- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 8 +++---- app/src/cli.c | 22 ++++++++++++++----- app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 2 +- app/src/server.c | 8 +++---- app/src/server.h | 2 +- .../java/com/genymobile/scrcpy/Options.java | 10 ++++----- .../java/com/genymobile/scrcpy/Server.java | 6 ++--- 12 files changed, 41 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 81371b92..8eabafa9 100644 --- a/README.md +++ b/README.md @@ -270,15 +270,15 @@ Some devices have more than one encoder for a specific codec, and some of them may cause issues or crash. It is possible to select a different encoder: ```bash -scrcpy --encoder=OMX.qcom.video.encoder.avc +scrcpy --video-encoder=OMX.qcom.video.encoder.avc ``` To list the available encoders, you can pass an invalid encoder name; the error will give the available encoders: ```bash -scrcpy --encoder=_ # for the default codec -scrcpy --video-codec=h265 --encoder=_ # for a specific codec +scrcpy --video-encoder=_ # for the default codec +scrcpy --video-codec=h265 --video-encoder=_ # for a specific codec ``` ### Capture diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 167f736f..450bd32d 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -9,7 +9,6 @@ _scrcpy() { --display= --display-buffer= -e --select-tcpip - --encoder= --force-adb-forward --forward-all-clicks -f --fullscreen @@ -55,6 +54,7 @@ _scrcpy() { -v --version --video-codec= --video-codec-options= + --video-encoder= -w --stay-awake --window-borderless --window-title= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 29bec42d..86d9ffbf 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -16,7 +16,6 @@ arguments=( '--display=[Specify the display id to mirror]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' {-e,--select-tcpip}'[Use TCP/IP device]' - '--encoder=[Use a specific MediaCodec encoder]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-f,--fullscreen}'[Start in fullscreen]' @@ -60,6 +59,7 @@ arguments=( {-v,--version}'[Print the version of scrcpy]' '--video-codec=[Select the video codec]:codec:(h264 h265 av1)' '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' + '--video-encoder=[Use a specific MediaCodec video encoder]' {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' '--window-borderless[Disable window decorations \(display borderless window\)]' '--window-title=[Set a custom window title]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 49d05a14..34bb750e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -64,10 +64,6 @@ Use TCP/IP device (if there is exactly one, like adb -e). Also see \fB\-d\fR (\fB\-\-select\-usb\fR). -.TP -.BI "\-\-encoder " name -Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-video\-codec\fR). - .TP .B \-\-force\-adb\-forward Do not attempt to use "adb reverse" to connect to the device. @@ -329,6 +325,10 @@ The list of possible codec options is available in the Android documentation .UR https://d.android.com/reference/android/media/MediaFormat .UE . +.TP +.BI "\-\-video\-encoder " name +Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR). + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index cb1af46e..685c2f3d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -45,7 +45,8 @@ enum { OPT_NO_KEY_REPEAT, OPT_FORWARD_ALL_CLICKS, OPT_LEGACY_PASTE, - OPT_ENCODER_NAME, + OPT_ENCODER, + OPT_VIDEO_ENCODER, OPT_POWER_OFF_ON_CLOSE, OPT_V4L2_SINK, OPT_DISPLAY_BUFFER, @@ -178,11 +179,10 @@ static const struct sc_option options[] = { "Also see -d (--select-usb).", }, { - .longopt_id = OPT_ENCODER_NAME, + // deprecated + .longopt_id = OPT_ENCODER, .longopt = "encoder", .argdesc = "name", - .text = "Use a specific MediaCodec encoder (depending on the codec " - "provided by --video-codec).", }, { .longopt_id = OPT_FORCE_ADB_FORWARD, @@ -543,6 +543,13 @@ static const struct sc_option options[] = { "Android documentation: " "", }, + { + .longopt_id = OPT_VIDEO_ENCODER, + .longopt = "video-encoder", + .argdesc = "name", + .text = "Use a specific MediaCodec video encoder (depending on the " + "codec provided by --video-codec).", + }, { .shortopt = 'w', .longopt = "stay-awake", @@ -1629,8 +1636,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_VIDEO_CODEC_OPTIONS: opts->video_codec_options = optarg; break; - case OPT_ENCODER_NAME: - opts->encoder_name = optarg; + case OPT_ENCODER: + LOGW("--encoder is deprecated, use --video-encoder instead."); + // fall through + case OPT_VIDEO_ENCODER: + opts->video_encoder = optarg; break; case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; diff --git a/app/src/options.c b/app/src/options.c index 0547da1b..fa025dea 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -8,7 +8,7 @@ const struct scrcpy_options scrcpy_options_default = { .push_target = NULL, .render_driver = NULL, .video_codec_options = NULL, - .encoder_name = NULL, + .video_encoder = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, #endif diff --git a/app/src/options.h b/app/src/options.h index bde79687..3c602b7e 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -94,7 +94,7 @@ struct scrcpy_options { const char *push_target; const char *render_driver; const char *video_codec_options; - const char *encoder_name; + const char *video_encoder; #ifdef HAVE_V4L2 const char *v4l2_device; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2bb5794c..776f5d13 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -329,7 +329,7 @@ scrcpy(struct scrcpy_options *options) { .show_touches = options->show_touches, .stay_awake = options->stay_awake, .video_codec_options = options->video_codec_options, - .encoder_name = options->encoder_name, + .video_encoder = options->video_encoder, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index eb91b2b2..583c338e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -72,7 +72,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->req_serial); free((char *) params->crop); free((char *) params->video_codec_options); - free((char *) params->encoder_name); + free((char *) params->video_encoder); free((char *) params->tcpip_dst); } @@ -96,7 +96,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(req_serial); COPY(crop); COPY(video_codec_options); - COPY(encoder_name); + COPY(video_encoder); COPY(tcpip_dst); #undef COPY @@ -258,8 +258,8 @@ execute_server(struct sc_server *server, if (params->video_codec_options) { ADD_PARAM("video_codec_options=%s", params->video_codec_options); } - if (params->encoder_name) { - ADD_PARAM("encoder_name=%s", params->encoder_name); + if (params->video_encoder) { + ADD_PARAM("video_encoder=%s", params->video_encoder); } if (params->power_off_on_close) { ADD_PARAM("power_off_on_close=true"); diff --git a/app/src/server.h b/app/src/server.h index 352d2cae..97c9aea2 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -28,7 +28,7 @@ struct sc_server_params { enum sc_codec video_codec; const char *crop; const char *video_codec_options; - const char *encoder_name; + const char *video_encoder; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 9cfb7871..c518bf07 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -21,7 +21,7 @@ public class Options { private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; - private String encoderName; + private String videoEncoder; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; private boolean downsizeOnError = true; @@ -154,12 +154,12 @@ public class Options { this.videoCodecOptions = videoCodecOptions; } - public String getEncoderName() { - return encoderName; + public String getVideoEncoder() { + return videoEncoder; } - public void setEncoderName(String encoderName) { - this.encoderName = encoderName; + public void setVideoEncoder(String videoEncoder) { + this.videoEncoder = videoEncoder; } public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4ff938b4..b809e90f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -117,7 +117,7 @@ public final class Server { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), options.getSendFrameMeta()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), - options.getVideoCodecOptions(), options.getEncoderName(), options.getDownsizeOnError()); + options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); try { // synchronous screenEncoder.streamScreen(); @@ -245,9 +245,9 @@ public final class Server { List videoCodecOptions = CodecOption.parse(value); options.setVideoCodecOptions(videoCodecOptions); break; - case "encoder_name": + case "video_encoder": if (!value.isEmpty()) { - options.setEncoderName(value); + options.setVideoEncoder(value); } break; case "power_off_on_close": From 8e640dc90f66e9b7f6b22feeb7e5bc9d2bb3e6f0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 02:27:39 +0100 Subject: [PATCH 0728/1133] Disable MethodLength checkstyle on createOptions() This method will grow as needed to initialize options. PR #3757 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index b809e90f..800b2fb6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -157,6 +157,7 @@ public final class Server { return thread; } + @SuppressWarnings("MethodLength") private static Options createOptions(String... args) { if (args.length < 1) { throw new IllegalArgumentException("Missing client version"); From 0870b8c8be18261b0930721dfa8e9f5454bd3081 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:32:43 +0100 Subject: [PATCH 0729/1133] Add --audio-bit-rate Add an option to configure the audio bit-rate. PR #3757 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 ++++++ app/src/cli.c | 14 ++++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 2 ++ app/src/server.h | 1 + .../java/com/genymobile/scrcpy/AudioEncoder.java | 11 ++++++----- .../main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../main/java/com/genymobile/scrcpy/Server.java | 6 +++++- 12 files changed, 48 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 450bd32d..02ade8d0 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -2,6 +2,7 @@ _scrcpy() { local cur prev words cword local opts=" --always-on-top + --audio-bit-rate= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 86d9ffbf..28d017e3 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -9,6 +9,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' + '--audio-bit-rate=[Encode the audio at the given bit-rate]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 34bb750e..7c11f6e5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -19,6 +19,12 @@ provides display and control of Android devices connected on USB (or over TCP/IP .B \-\-always\-on\-top Make scrcpy window always on top (above other windows). +.TP +.BI "\-\-audio\-bit\-rate " value +Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). + +Default is 128K (128000). + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). diff --git a/app/src/cli.c b/app/src/cli.c index 685c2f3d..7187b878 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -64,6 +64,7 @@ enum { OPT_CODEC, OPT_VIDEO_CODEC, OPT_NO_AUDIO, + OPT_AUDIO_BIT_RATE, }; struct sc_option { @@ -105,6 +106,14 @@ static const struct sc_option options[] = { .longopt = "always-on-top", .text = "Make scrcpy window always on top (above other windows).", }, + { + .longopt_id = OPT_AUDIO_BIT_RATE, + .longopt = "audio-bit-rate", + .argdesc = "value", + .text = "Encode the audio at the given bit-rate, expressed in bits/s. " + "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" + "Default is 128K (128000).", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1461,6 +1470,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_BIT_RATE: + if (!parse_bit_rate(optarg, &opts->audio_bit_rate)) { + return false; + } + break; case OPT_CROP: opts->crop = optarg; break; diff --git a/app/src/options.c b/app/src/options.c index fa025dea..70d26a6f 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -29,6 +29,7 @@ const struct scrcpy_options scrcpy_options_default = { }, .max_size = 0, .video_bit_rate = 0, + .audio_bit_rate = 0, .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .rotation = 0, diff --git a/app/src/options.h b/app/src/options.h index 3c602b7e..92a53653 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -109,6 +109,7 @@ struct scrcpy_options { struct sc_shortcut_mods shortcut_mods; uint16_t max_size; uint32_t video_bit_rate; + uint32_t audio_bit_rate; uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; uint8_t rotation; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 776f5d13..478f0e87 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -321,6 +321,7 @@ scrcpy(struct scrcpy_options *options) { .tunnel_port = options->tunnel_port, .max_size = options->max_size, .video_bit_rate = options->video_bit_rate, + .audio_bit_rate = options->audio_bit_rate, .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, diff --git a/app/src/server.c b/app/src/server.c index 583c338e..fa8d8300 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -221,6 +221,8 @@ execute_server(struct sc_server *server, } if (!params->audio) { ADD_PARAM("audio=false"); + } else if (params->audio_bit_rate) { + ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate); } if (params->video_codec != SC_CODEC_H264) { ADD_PARAM("video_codec=%s", diff --git a/app/src/server.h b/app/src/server.h index 97c9aea2..805bdaf2 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -34,6 +34,7 @@ struct sc_server_params { uint16_t tunnel_port; uint16_t max_size; uint32_t video_bit_rate; + uint32_t audio_bit_rate; uint16_t max_fps; int8_t lock_video_orientation; bool control; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index d06898d6..5704f768 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -44,12 +44,12 @@ public final class AudioEncoder { private static final int CHANNELS = 2; private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int BYTES_PER_SAMPLE = 2; - private static final int BIT_RATE = 128000; private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; private final Streamer streamer; + private final int bitRate; // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // So many pending tasks would lead to an unacceptable delay anyway. @@ -64,8 +64,9 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer) { + public AudioEncoder(Streamer streamer, int bitRate) { this.streamer = streamer; + this.bitRate = bitRate; } private static AudioFormat createAudioFormat() { @@ -92,10 +93,10 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat() { + private static MediaFormat createFormat(int bitRate) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MIMETYPE); - format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); return format; @@ -220,7 +221,7 @@ public final class AudioEncoder { mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(); + MediaFormat format = createFormat(bitRate); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index c518bf07..44bc73ec 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,6 +12,7 @@ public class Options { private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; private int videoBitRate = 8000000; + private int audioBitRate = 128000; private int maxFps; private int lockVideoOrientation = -1; private boolean tunnelForward; @@ -82,6 +83,14 @@ public class Options { this.videoBitRate = videoBitRate; } + public int getAudioBitRate() { + return audioBitRate; + } + + public void setAudioBitRate(int audioBitRate) { + this.audioBitRate = audioBitRate; + } + public int getMaxFps() { return maxFps; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 800b2fb6..c10e3209 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -110,7 +110,7 @@ public final class Server { if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), AudioCodec.OPUS, options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); audioEncoder.start(); } @@ -210,6 +210,10 @@ public final class Server { int videoBitRate = Integer.parseInt(value); options.setVideoBitRate(videoBitRate); break; + case "audio_bit_rate": + int audioBitRate = Integer.parseInt(value); + options.setAudioBitRate(audioBitRate); + break; case "max_fps": int maxFps = Integer.parseInt(value); options.setMaxFps(maxFps); From 839b842aa71a848510d9407dacc7742c61a17fea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 19:05:43 +0100 Subject: [PATCH 0730/1133] Add --audio-codec Introduce the selection mechanism. Alternative codecs will be added later. PR #3757 --- app/data/bash-completion/scrcpy | 5 ++++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 +++++ app/src/cli.c | 23 +++++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 2 ++ app/src/scrcpy.c | 1 + app/src/server.c | 6 +++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 10 ++++---- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++++ .../java/com/genymobile/scrcpy/Server.java | 10 +++++++- 12 files changed, 69 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 02ade8d0..5a50f6c5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,6 +3,7 @@ _scrcpy() { local opts=" --always-on-top --audio-bit-rate= + --audio-codec= -b --video-bit-rate= --crop= -d --select-usb @@ -71,6 +72,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'h264 h265 av1' -- "$cur")) return ;; + --audio-codec) + COMPREPLY=($(compgen -W 'opus' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 28d017e3..4f7ad5ef 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,6 +10,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' + '--audio-codec=[Select the audio codec]:codec:(opus)' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7c11f6e5..89533a1f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,6 +25,12 @@ Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 128K (128000). +.TP +.BI "\-\-audio\-codec " name +Select an audio codec (opus). + +Default is opus. + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). diff --git a/app/src/cli.c b/app/src/cli.c index 7187b878..5f28164e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -65,6 +65,7 @@ enum { OPT_VIDEO_CODEC, OPT_NO_AUDIO, OPT_AUDIO_BIT_RATE, + OPT_AUDIO_CODEC, }; struct sc_option { @@ -114,6 +115,13 @@ static const struct sc_option options[] = { "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 128K (128000).", }, + { + .longopt_id = OPT_AUDIO_CODEC, + .longopt = "audio-codec", + .argdesc = "name", + .text = "Select an audio codec (opus).\n" + "Default is opus.", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1452,6 +1460,16 @@ parse_video_codec(const char *optarg, enum sc_codec *codec) { return false; } +static bool +parse_audio_codec(const char *optarg, enum sc_codec *codec) { + if (!strcmp(optarg, "opus")) { + *codec = SC_CODEC_OPUS; + return true; + } + LOGE("Unsupported audio codec: %s (expected opus)", optarg); + return false; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1711,6 +1729,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_CODEC: + if (!parse_audio_codec(optarg, &opts->audio_codec)) { + return false; + } + break; case OPT_OTG: #ifdef HAVE_USB opts->otg = true; diff --git a/app/src/options.c b/app/src/options.c index 70d26a6f..72f34e43 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = { #endif .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, + .audio_codec = SC_CODEC_OPUS, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index 92a53653..c698e6e3 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -27,6 +27,7 @@ enum sc_codec { SC_CODEC_H264, SC_CODEC_H265, SC_CODEC_AV1, + SC_CODEC_OPUS, }; enum sc_lock_video_orientation { @@ -100,6 +101,7 @@ struct scrcpy_options { #endif enum sc_log_level log_level; enum sc_codec video_codec; + enum sc_codec audio_codec; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 478f0e87..8b96477c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -315,6 +315,7 @@ scrcpy(struct scrcpy_options *options) { .select_tcpip = options->select_tcpip, .log_level = options->log_level, .video_codec = options->video_codec, + .audio_codec = options->audio_codec, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index fa8d8300..a797f01d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -165,6 +165,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "h265"; case SC_CODEC_AV1: return "av1"; + case SC_CODEC_OPUS: + return "opus"; default: return NULL; } @@ -228,6 +230,10 @@ execute_server(struct sc_server *server, ADD_PARAM("video_codec=%s", sc_server_get_codec_name(params->video_codec)); } + if (params->audio_codec != SC_CODEC_OPUS) { + ADD_PARAM("audio_codec=%s", + sc_server_get_codec_name(params->audio_codec)); + } if (params->max_size) { ADD_PARAM("max_size=%" PRIu16, params->max_size); } diff --git a/app/src/server.h b/app/src/server.h index 805bdaf2..55a86605 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -26,6 +26,7 @@ struct sc_server_params { const char *req_serial; enum sc_log_level log_level; enum sc_codec video_codec; + enum sc_codec audio_codec; const char *crop; const char *video_codec_options; const char *video_encoder; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 5704f768..710e5f7d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -38,7 +38,6 @@ public final class AudioEncoder { } } - private static final String MIMETYPE = MediaFormat.MIMETYPE_AUDIO_OPUS; private static final int SAMPLE_RATE = 48000; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; private static final int CHANNELS = 2; @@ -93,9 +92,9 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat(int bitRate) { + private static MediaFormat createFormat(String mimeType, int bitRate) { MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, MIMETYPE); + format.setString(MediaFormat.KEY_MIME, mimeType); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); @@ -216,12 +215,13 @@ public final class AudioEncoder { boolean mediaCodecStarted = false; boolean recorderStarted = false; try { - mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException + String mimeType = streamer.getCodec().getMimeType(); + mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(bitRate); + MediaFormat format = createFormat(mimeType, bitRate); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 44bc73ec..bdeab851 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,6 +11,7 @@ public class Options { private boolean audio = true; private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; + private AudioCodec audioCodec = AudioCodec.OPUS; private int videoBitRate = 8000000; private int audioBitRate = 128000; private int maxFps; @@ -75,6 +76,14 @@ public class Options { this.videoCodec = videoCodec; } + public AudioCodec getAudioCodec() { + return audioCodec; + } + + public void setAudioCodec(AudioCodec audioCodec) { + this.audioCodec = audioCodec; + } + public int getVideoBitRate() { return videoBitRate; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c10e3209..4c15bd39 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -109,7 +109,8 @@ public final class Server { } if (audio) { - Streamer audioStreamer = new Streamer(connection.getAudioFd(), AudioCodec.OPUS, options.getSendCodecId(), options.getSendFrameMeta()); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), + options.getSendFrameMeta()); audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); audioEncoder.start(); } @@ -202,6 +203,13 @@ public final class Server { } options.setVideoCodec(videoCodec); break; + case "audio_codec": + AudioCodec audioCodec = AudioCodec.findByName(value); + if (audioCodec == null) { + throw new IllegalArgumentException("Audio codec " + value + " not supported"); + } + options.setAudioCodec(audioCodec); + break; case "max_size": int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 options.setMaxSize(maxSize); From 4601735e51b14e85b67fdc557868f022653b4230 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 19:30:36 +0100 Subject: [PATCH 0731/1133] Add support for AAC audio codec Add option --audio-codec=aac. PR #3757 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 8 ++++++-- app/src/demuxer.c | 3 +++ app/src/options.h | 1 + app/src/server.c | 2 ++ .../src/main/java/com/genymobile/scrcpy/AudioCodec.java | 3 ++- 8 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 5a50f6c5..f303ff66 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -73,7 +73,7 @@ _scrcpy() { return ;; --audio-codec) - COMPREPLY=($(compgen -W 'opus' -- "$cur")) + COMPREPLY=($(compgen -W 'opus aac' -- "$cur")) return ;; --lock-video-orientation) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 4f7ad5ef..a0d83a05 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,7 +10,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' - '--audio-codec=[Select the audio codec]:codec:(opus)' + '--audio-codec=[Select the audio codec]:codec:(opus aac)' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 89533a1f..3ccbb111 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -27,7 +27,7 @@ Default is 128K (128000). .TP .BI "\-\-audio\-codec " name -Select an audio codec (opus). +Select an audio codec (opus or aac). Default is opus. diff --git a/app/src/cli.c b/app/src/cli.c index 5f28164e..afd060b8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -119,7 +119,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", .argdesc = "name", - .text = "Select an audio codec (opus).\n" + .text = "Select an audio codec (opus or aac).\n" "Default is opus.", }, { @@ -1466,7 +1466,11 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_OPUS; return true; } - LOGE("Unsupported audio codec: %s (expected opus)", optarg); + if (!strcmp(optarg, "aac")) { + *codec = SC_CODEC_AAC; + return true; + } + LOGE("Unsupported audio codec: %s (expected opus or aac)", optarg); return false; } diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 482f2e04..64bf30a3 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -24,6 +24,7 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { #define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII #define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII #define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII +#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII" switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; @@ -33,6 +34,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { return AV_CODEC_ID_AV1; case SC_CODEC_ID_OPUS: return AV_CODEC_ID_OPUS; + case SC_CODEC_ID_AAC: + return AV_CODEC_ID_AAC; default: LOGE("Unknown codec id 0x%08" PRIx32, codec_id); return AV_CODEC_ID_NONE; diff --git a/app/src/options.h b/app/src/options.h index c698e6e3..3efa2dd6 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -28,6 +28,7 @@ enum sc_codec { SC_CODEC_H265, SC_CODEC_AV1, SC_CODEC_OPUS, + SC_CODEC_AAC, }; enum sc_lock_video_orientation { diff --git a/app/src/server.c b/app/src/server.c index a797f01d..36146d86 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -167,6 +167,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "av1"; case SC_CODEC_OPUS: return "opus"; + case SC_CODEC_AAC: + return "aac"; default: return NULL; } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java index 4d9e3201..dc000e98 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -3,7 +3,8 @@ package com.genymobile.scrcpy; import android.media.MediaFormat; public enum AudioCodec implements Codec { - OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS); + OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), + AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC); private final int id; // 4-byte ASCII representation of the name private final String name; From 58cf8e540199ca5a1a25bb0e9bdac56f8de17a14 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:03:04 +0100 Subject: [PATCH 0732/1133] Extract application of codec options This will allow to reuse the same code for audio codec options. PR #3757 --- .../com/genymobile/scrcpy/CodecUtils.java | 28 +++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 22 +++------------ 2 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CodecUtils.java diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java new file mode 100644 index 00000000..2a808c59 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -0,0 +1,28 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class CodecUtils { + + private CodecUtils() { + // not instantiable + } + + public static void setCodecOption(MediaFormat format, String key, Object value) { + if (value instanceof Integer) { + format.setInteger(key, (Integer) value); + } else if (value instanceof Long) { + format.setLong(key, (Long) value); + } else if (value instanceof Float) { + format.setFloat(key, (Float) value); + } else if (value instanceof String) { + format.setString(key, (String) value); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index d646995b..1c3ccf72 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -237,23 +237,6 @@ public class ScreenEncoder implements Device.RotationListener { return msg.toString(); } - private static void setCodecOption(MediaFormat format, CodecOption codecOption) { - String key = codecOption.getKey(); - Object value = codecOption.getValue(); - - if (value instanceof Integer) { - format.setInteger(key, (Integer) value); - } else if (value instanceof Long) { - format.setLong(key, (Long) value); - } else if (value instanceof Float) { - format.setFloat(key, (Float) value); - } else if (value instanceof String) { - format.setString(key, (String) value); - } - - Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); - } - private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, videoMimeType); @@ -273,7 +256,10 @@ public class ScreenEncoder implements Device.RotationListener { if (codecOptions != null) { for (CodecOption option : codecOptions) { - setCodecOption(format, option); + String key = option.getKey(); + Object value = option.getValue(); + CodecUtils.setCodecOption(format, key, value); + Ln.d("Video codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); } } From b03c864c70c4388ae788c494cfffeed2c00aabb8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 22:48:23 +0100 Subject: [PATCH 0733/1133] Add --audio-codec-options Similar to --video-codec-options, but for audio. PR #3757 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 10 ++++++++++ app/src/cli.c | 16 ++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 5 +++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 19 ++++++++++++++++--- .../java/com/genymobile/scrcpy/Options.java | 10 ++++++++++ .../java/com/genymobile/scrcpy/Server.java | 6 +++++- 12 files changed, 68 insertions(+), 4 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index f303ff66..da245acc 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -4,6 +4,7 @@ _scrcpy() { --always-on-top --audio-bit-rate= --audio-codec= + --audio-codec-options= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index a0d83a05..aa7928c6 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -11,6 +11,7 @@ arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' '--audio-codec=[Select the audio codec]:codec:(opus aac)' + '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 3ccbb111..fd7746c4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -31,6 +31,16 @@ Select an audio codec (opus or aac). Default is opus. +.TP +.BI "\-\-audio\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...] +Set a list of comma-separated key:type=value options for the device audio encoder. + +The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. + +The list of possible codec options is available in the Android documentation +.UR https://d.android.com/reference/android/media/MediaFormat +.UE . + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). diff --git a/app/src/cli.c b/app/src/cli.c index afd060b8..9f61e6cb 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -66,6 +66,7 @@ enum { OPT_NO_AUDIO, OPT_AUDIO_BIT_RATE, OPT_AUDIO_CODEC, + OPT_AUDIO_CODEC_OPTIONS, }; struct sc_option { @@ -122,6 +123,18 @@ static const struct sc_option options[] = { .text = "Select an audio codec (opus or aac).\n" "Default is opus.", }, + { + .longopt_id = OPT_AUDIO_CODEC_OPTIONS, + .longopt = "audio-codec-options", + .argdesc = "key[:type]=value[,...]", + .text = "Set a list of comma-separated key:type=value options for the " + "device audio encoder.\n" + "The possible values for 'type' are 'int' (default), 'long', " + "'float' and 'string'.\n" + "The list of possible codec options is available in the " + "Android documentation: " + "", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1672,6 +1685,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_VIDEO_CODEC_OPTIONS: opts->video_codec_options = optarg; break; + case OPT_AUDIO_CODEC_OPTIONS: + opts->audio_codec_options = optarg; + break; case OPT_ENCODER: LOGW("--encoder is deprecated, use --video-encoder instead."); // fall through diff --git a/app/src/options.c b/app/src/options.c index 72f34e43..a9be5dfa 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -8,6 +8,7 @@ const struct scrcpy_options scrcpy_options_default = { .push_target = NULL, .render_driver = NULL, .video_codec_options = NULL, + .audio_codec_options = NULL, .video_encoder = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, diff --git a/app/src/options.h b/app/src/options.h index 3efa2dd6..bbb52eb7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -96,6 +96,7 @@ struct scrcpy_options { const char *push_target; const char *render_driver; const char *video_codec_options; + const char *audio_codec_options; const char *video_encoder; #ifdef HAVE_V4L2 const char *v4l2_device; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 8b96477c..a43c2687 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -331,6 +331,7 @@ scrcpy(struct scrcpy_options *options) { .show_touches = options->show_touches, .stay_awake = options->stay_awake, .video_codec_options = options->video_codec_options, + .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, diff --git a/app/src/server.c b/app/src/server.c index 36146d86..95e4670d 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -72,6 +72,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->req_serial); free((char *) params->crop); free((char *) params->video_codec_options); + free((char *) params->audio_codec_options); free((char *) params->video_encoder); free((char *) params->tcpip_dst); } @@ -96,6 +97,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(req_serial); COPY(crop); COPY(video_codec_options); + COPY(audio_codec_options); COPY(video_encoder); COPY(tcpip_dst); #undef COPY @@ -268,6 +270,9 @@ execute_server(struct sc_server *server, if (params->video_codec_options) { ADD_PARAM("video_codec_options=%s", params->video_codec_options); } + if (params->audio_codec_options) { + ADD_PARAM("audio_codec_options=%s", params->audio_codec_options); + } if (params->video_encoder) { ADD_PARAM("video_encoder=%s", params->video_encoder); } diff --git a/app/src/server.h b/app/src/server.h index 55a86605..d96f997e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -29,6 +29,7 @@ struct sc_server_params { enum sc_codec audio_codec; const char *crop; const char *video_codec_options; + const char *audio_codec_options; const char *video_encoder; struct sc_port_range port_range; uint32_t tunnel_host; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 710e5f7d..56ff207f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -15,6 +15,7 @@ import android.os.Looper; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -49,6 +50,7 @@ public final class AudioEncoder { private final Streamer streamer; private final int bitRate; + private final List codecOptions; // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // So many pending tasks would lead to an unacceptable delay anyway. @@ -63,9 +65,10 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate) { + public AudioEncoder(Streamer streamer, int bitRate, List codecOptions) { this.streamer = streamer; this.bitRate = bitRate; + this.codecOptions = codecOptions; } private static AudioFormat createAudioFormat() { @@ -92,12 +95,22 @@ public final class AudioEncoder { return builder.build(); } - private static MediaFormat createFormat(String mimeType, int bitRate) { + private static MediaFormat createFormat(String mimeType, int bitRate, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, mimeType); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); + + if (codecOptions != null) { + for (CodecOption option : codecOptions) { + String key = option.getKey(); + Object value = option.getValue(); + CodecUtils.setCodecOption(format, key, value); + Ln.d("Audio codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value); + } + } + return format; } @@ -221,7 +234,7 @@ public final class AudioEncoder { mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(mimeType, bitRate); + MediaFormat format = createFormat(mimeType, bitRate, codecOptions); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index bdeab851..4cb21e28 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -23,6 +23,8 @@ public class Options { private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; + private List audioCodecOptions; + private String videoEncoder; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; @@ -172,6 +174,14 @@ public class Options { this.videoCodecOptions = videoCodecOptions; } + public List getAudioCodecOptions() { + return audioCodecOptions; + } + + public void setAudioCodecOptions(List audioCodecOptions) { + this.audioCodecOptions = audioCodecOptions; + } + public String getVideoEncoder() { return videoEncoder; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4c15bd39..f4e36bff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,7 +111,7 @@ public final class Server { if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate()); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions()); audioEncoder.start(); } @@ -258,6 +258,10 @@ public final class Server { List videoCodecOptions = CodecOption.parse(value); options.setVideoCodecOptions(videoCodecOptions); break; + case "audio_codec_options": + List audioCodecOptions = CodecOption.parse(value); + options.setAudioCodecOptions(audioCodecOptions); + break; case "video_encoder": if (!value.isEmpty()) { options.setVideoEncoder(value); From 6f332a2bc73853884c894ee2400ad9c92aabc34c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:03:04 +0100 Subject: [PATCH 0734/1133] Extract unknown encoder error message This will allow to reuse the same code for audio encoder selection. PR #3757 --- .../com/genymobile/scrcpy/CodecUtils.java | 25 +++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 28 +------------------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java index 2a808c59..96887c14 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -25,4 +25,29 @@ public final class CodecUtils { format.setString(key, (String) value); } } + + public static String buildUnknownEncoderMessage(Codec codec, String encoderName) { + StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); + MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); + if (encoders != null && encoders.length > 0) { + msg.append("\nTry to use one of the available encoders:"); + String codecOption = codec.getType() == Codec.Type.VIDEO ? "video-codec" : "audio-codec"; + for (MediaCodecInfo encoder : encoders) { + msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName()); + msg.append(" --encoder='").append(encoder.getName()).append("'"); + } + } + return msg.toString(); + } + + private static MediaCodecInfo[] listEncoders(String mimeType) { + List result = new ArrayList<>(); + MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + for (MediaCodecInfo codecInfo : list.getCodecInfos()) { + if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { + result.add(codecInfo); + } + } + return result.toArray(new MediaCodecInfo[result.size()]); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 1c3ccf72..77cd1de4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -5,7 +5,6 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Rect; import android.media.MediaCodec; import android.media.MediaCodecInfo; -import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.Build; import android.os.IBinder; @@ -14,8 +13,6 @@ import android.view.Surface; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -199,24 +196,13 @@ public class ScreenEncoder implements Device.RotationListener { return !eof; } - private static MediaCodecInfo[] listEncoders(String videoMimeType) { - List result = new ArrayList<>(); - MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo codecInfo : list.getCodecInfos()) { - if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(videoMimeType)) { - result.add(codecInfo); - } - } - return result.toArray(new MediaCodecInfo[result.size()]); - } - private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { if (encoderName != null) { Ln.d("Creating encoder by name: '" + encoderName + "'"); try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e(buildUnknownEncoderMessage(codec, encoderName)); + Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); throw new ConfigurationException("Unknown encoder: " + encoderName); } } @@ -225,18 +211,6 @@ public class ScreenEncoder implements Device.RotationListener { return mediaCodec; } - private static String buildUnknownEncoderMessage(Codec codec, String encoderName) { - StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); - MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); - if (encoders != null && encoders.length > 0) { - msg.append("\nTry to use one of the available encoders:"); - for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --video-codec=").append(codec.getName()).append(" --encoder='").append(encoder.getName()).append("'"); - } - } - return msg.toString(); - } - private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, videoMimeType); From f9960e959fa7b46cc1ed6b15115fe2958724766d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Feb 2023 20:20:29 +0100 Subject: [PATCH 0735/1133] Add --audio-encoder Similar to --video-encoder, but for audio. PR #3757 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 +++ app/src/cli.c | 11 +++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 5 ++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 29 +++++++++++++++---- .../java/com/genymobile/scrcpy/Options.java | 9 ++++++ .../java/com/genymobile/scrcpy/Server.java | 6 +++- 12 files changed, 64 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index da245acc..c860707f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -5,6 +5,7 @@ _scrcpy() { --audio-bit-rate= --audio-codec= --audio-codec-options= + --audio-encoder= -b --video-bit-rate= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index aa7928c6..b122587f 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -12,6 +12,7 @@ arguments=( '--audio-bit-rate=[Encode the audio at the given bit-rate]' '--audio-codec=[Select the audio codec]:codec:(opus aac)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' + '--audio-encoder=[Use a specific MediaCodec audio encoder]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index fd7746c4..ef17465a 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -41,6 +41,10 @@ The list of possible codec options is available in the Android documentation .UR https://d.android.com/reference/android/media/MediaFormat .UE . +.TP +.BI "\-\-audio\-encoder " name +Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). diff --git a/app/src/cli.c b/app/src/cli.c index 9f61e6cb..68629fd2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -67,6 +67,7 @@ enum { OPT_AUDIO_BIT_RATE, OPT_AUDIO_CODEC, OPT_AUDIO_CODEC_OPTIONS, + OPT_AUDIO_ENCODER, }; struct sc_option { @@ -135,6 +136,13 @@ static const struct sc_option options[] = { "Android documentation: " "", }, + { + .longopt_id = OPT_AUDIO_ENCODER, + .longopt = "audio-encoder", + .argdesc = "name", + .text = "Use a specific MediaCodec audio encoder (depending on the " + "codec provided by --audio-codec).", + }, { .shortopt = 'b', .longopt = "video-bit-rate", @@ -1694,6 +1702,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_VIDEO_ENCODER: opts->video_encoder = optarg; break; + case OPT_AUDIO_ENCODER: + opts->audio_encoder = optarg; + break; case OPT_FORCE_ADB_FORWARD: opts->force_adb_forward = true; break; diff --git a/app/src/options.c b/app/src/options.c index a9be5dfa..40f84fdd 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -10,6 +10,7 @@ const struct scrcpy_options scrcpy_options_default = { .video_codec_options = NULL, .audio_codec_options = NULL, .video_encoder = NULL, + .audio_encoder = NULL, #ifdef HAVE_V4L2 .v4l2_device = NULL, #endif diff --git a/app/src/options.h b/app/src/options.h index bbb52eb7..804fba93 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -98,6 +98,7 @@ struct scrcpy_options { const char *video_codec_options; const char *audio_codec_options; const char *video_encoder; + const char *audio_encoder; #ifdef HAVE_V4L2 const char *v4l2_device; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a43c2687..6bfed295 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -333,6 +333,7 @@ scrcpy(struct scrcpy_options *options) { .video_codec_options = options->video_codec_options, .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, + .audio_encoder = options->audio_encoder, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index 95e4670d..b50003c9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -74,6 +74,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->video_codec_options); free((char *) params->audio_codec_options); free((char *) params->video_encoder); + free((char *) params->audio_encoder); free((char *) params->tcpip_dst); } @@ -99,6 +100,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(video_codec_options); COPY(audio_codec_options); COPY(video_encoder); + COPY(audio_encoder); COPY(tcpip_dst); #undef COPY @@ -276,6 +278,9 @@ execute_server(struct sc_server *server, if (params->video_encoder) { ADD_PARAM("video_encoder=%s", params->video_encoder); } + if (params->audio_encoder) { + ADD_PARAM("audio_encoder=%s", params->audio_encoder); + } if (params->power_off_on_close) { ADD_PARAM("power_off_on_close=true"); } diff --git a/app/src/server.h b/app/src/server.h index d96f997e..c20508e0 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -31,6 +31,7 @@ struct sc_server_params { const char *video_codec_options; const char *audio_codec_options; const char *video_encoder; + const char *audio_encoder; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 56ff207f..a70a475b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -51,6 +51,7 @@ public final class AudioEncoder { private final Streamer streamer; private final int bitRate; private final List codecOptions; + private final String encoderName; // Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4). // So many pending tasks would lead to an unacceptable delay anyway. @@ -65,10 +66,11 @@ public final class AudioEncoder { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate, List codecOptions) { + public AudioEncoder(Streamer streamer, int bitRate, List codecOptions, String encoderName) { this.streamer = streamer; this.bitRate = bitRate; this.codecOptions = codecOptions; + this.encoderName = encoderName; } private static AudioFormat createAudioFormat() { @@ -177,6 +179,8 @@ public final class AudioEncoder { thread = new Thread(() -> { try { encode(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { Ln.e("Audio encoding error", e); } finally { @@ -215,7 +219,7 @@ public final class AudioEncoder { } @TargetApi(Build.VERSION_CODES.M) - public void encode() throws IOException { + public void encode() throws IOException, ConfigurationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { Ln.w("Audio disabled: it is not supported before Android 11"); streamer.writeDisableStream(); @@ -228,13 +232,13 @@ public final class AudioEncoder { boolean mediaCodecStarted = false; boolean recorderStarted = false; try { - String mimeType = streamer.getCodec().getMimeType(); - mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException + Codec codec = streamer.getCodec(); + mediaCodec = createMediaCodec(codec, encoderName); mediaCodecThread = new HandlerThread("AudioEncoder"); mediaCodecThread.start(); - MediaFormat format = createFormat(mimeType, bitRate, codecOptions); + MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions); mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); @@ -324,6 +328,21 @@ public final class AudioEncoder { } } + private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { + if (encoderName != null) { + Ln.d("Creating audio encoder by name: '" + encoderName + "'"); + try { + return MediaCodec.createByCodecName(encoderName); + } catch (IllegalArgumentException e) { + Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); + throw new ConfigurationException("Unknown encoder: " + encoderName); + } + } + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } + private class EncoderCallback extends MediaCodec.Callback { @TargetApi(Build.VERSION_CODES.N) @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 4cb21e28..86838022 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -26,6 +26,7 @@ public class Options { private List audioCodecOptions; private String videoEncoder; + private String audioEncoder; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; private boolean downsizeOnError = true; @@ -190,6 +191,14 @@ public class Options { this.videoEncoder = videoEncoder; } + public String getAudioEncoder() { + return audioEncoder; + } + + public void setAudioEncoder(String audioEncoder) { + this.audioEncoder = audioEncoder; + } + public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { this.powerOffScreenOnClose = powerOffScreenOnClose; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f4e36bff..f30d65f6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -111,7 +111,7 @@ public final class Server { if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions()); + audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); audioEncoder.start(); } @@ -267,6 +267,10 @@ public final class Server { options.setVideoEncoder(value); } break; + case "audio_encoder": + if (!value.isEmpty()) { + options.setAudioEncoder(value); + } case "power_off_on_close": boolean powerOffScreenOnClose = Boolean.parseBoolean(value); options.setPowerOffScreenOnClose(powerOffScreenOnClose); From b7e5284adf1074d6d851a0a7d164461325ccaa58 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 23:12:21 +0100 Subject: [PATCH 0736/1133] Move await_for_server() logs Print the logs on the caller side. This will allow to call the function in another context without printing the logs. PR #3757 --- app/src/scrcpy.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6bfed295..81affe47 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -183,14 +183,11 @@ await_for_server(bool *connected) { while (SDL_WaitEvent(&event)) { switch (event.type) { case SDL_QUIT: - LOGD("User requested to quit"); *connected = false; return true; case SC_EVENT_SERVER_CONNECTION_FAILED: - LOGE("Server connection failed"); return false; case SC_EVENT_SERVER_CONNECTED: - LOGD("Server connected"); *connected = true; return true; default: @@ -374,15 +371,19 @@ scrcpy(struct scrcpy_options *options) { // Await for server without blocking Ctrl+C handling bool connected; if (!await_for_server(&connected)) { + LOGE("Server connection failed"); goto end; } if (!connected) { // This is not an error, user requested to quit + LOGD("User requested to quit"); ret = SCRCPY_EXIT_SUCCESS; goto end; } + LOGD("Server connected"); + // It is necessarily initialized here, since the device is connected struct sc_server_info *info = &s->server.info; From 9196dc156376760eb637024d5c0448ceeaa2a8ff Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 22 Feb 2023 23:15:15 +0100 Subject: [PATCH 0737/1133] Add --list-encoders Add an option to list the device encoders properly. PR #3757 --- README.md | 6 +- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 8 ++ app/src/cli.c | 15 +++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 15 +++- app/src/server.c | 27 +++++-- app/src/server.h | 1 + .../com/genymobile/scrcpy/AudioEncoder.java | 2 +- .../java/com/genymobile/scrcpy/CleanUp.java | 2 +- .../com/genymobile/scrcpy/CodecUtils.java | 79 ++++++++++++++++--- .../java/com/genymobile/scrcpy/Options.java | 10 +++ .../com/genymobile/scrcpy/ScreenEncoder.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 15 ++++ 16 files changed, 157 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8eabafa9..5575fc4d 100644 --- a/README.md +++ b/README.md @@ -273,12 +273,10 @@ may cause issues or crash. It is possible to select a different encoder: scrcpy --video-encoder=OMX.qcom.video.encoder.avc ``` -To list the available encoders, you can pass an invalid encoder name; the -error will give the available encoders: +To list the available encoders: ```bash -scrcpy --video-encoder=_ # for the default codec -scrcpy --video-codec=h265 --video-encoder=_ # for a specific codec +scrcpy --list-encoders ``` ### Capture diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index c860707f..70695019 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -19,6 +19,7 @@ _scrcpy() { -K --hid-keyboard -h --help --legacy-paste + --list-encoders --lock-video-orientation --lock-video-orientation= --max-fps= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index b122587f..268aa626 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -26,6 +26,7 @@ arguments=( {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-h,--help}'[Print the help]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' + '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' '--max-fps=[Limit the frame rate of screen capture]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ef17465a..add263c2 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -45,6 +45,8 @@ The list of possible codec options is available in the Android documentation .BI "\-\-audio\-encoder " name Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). +The available encoders can be listed by \-\-list\-encoders. + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). @@ -128,6 +130,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. +.TP +.B \-\-list\-encoders +List video and audio encoders available on the device. + .TP \fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR] Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise. @@ -355,6 +361,8 @@ The list of possible codec options is available in the Android documentation .BI "\-\-video\-encoder " name Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR). +The available encoders can be listed by \-\-list\-encoders. + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index 68629fd2..edb694a6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -68,6 +68,7 @@ enum { OPT_AUDIO_CODEC, OPT_AUDIO_CODEC_OPTIONS, OPT_AUDIO_ENCODER, + OPT_LIST_ENCODERS, }; struct sc_option { @@ -141,7 +142,8 @@ static const struct sc_option options[] = { .longopt = "audio-encoder", .argdesc = "name", .text = "Use a specific MediaCodec audio encoder (depending on the " - "codec provided by --audio-codec).", + "codec provided by --audio-codec).\n" + "The available encoders can be listed by --list-encoders.", }, { .shortopt = 'b', @@ -270,6 +272,11 @@ static const struct sc_option options[] = { "This is a workaround for some devices not behaving as " "expected when setting the device clipboard programmatically.", }, + { + .longopt_id = OPT_LIST_ENCODERS, + .longopt = "list-encoders", + .text = "List video and audio encoders available on the device.", + }, { .longopt_id = OPT_LOCK_VIDEO_ORIENTATION, .longopt = "lock-video-orientation", @@ -586,7 +593,8 @@ static const struct sc_option options[] = { .longopt = "video-encoder", .argdesc = "name", .text = "Use a specific MediaCodec video encoder (depending on the " - "codec provided by --video-codec).", + "codec provided by --video-codec).\n" + "The available encoders can be listed by --list-encoders.", }, { .shortopt = 'w', @@ -1792,6 +1800,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("V4L2 (--v4l2-buffer) is only available on Linux."); return false; #endif + case OPT_LIST_ENCODERS: + opts->list_encoders = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 40f84fdd..1839df6e 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -72,4 +72,5 @@ const struct scrcpy_options scrcpy_options_default = { .start_fps_counter = false, .power_on = true, .audio = true, + .list_encoders = false, }; diff --git a/app/src/options.h b/app/src/options.h index 804fba93..568b8155 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -154,6 +154,7 @@ struct scrcpy_options { bool start_fps_counter; bool power_on; bool audio; + bool list_encoders; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 81affe47..6d0fac9e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -183,12 +183,16 @@ await_for_server(bool *connected) { while (SDL_WaitEvent(&event)) { switch (event.type) { case SDL_QUIT: - *connected = false; + if (connected) { + *connected = false; + } return true; case SC_EVENT_SERVER_CONNECTION_FAILED: return false; case SC_EVENT_SERVER_CONNECTED: - *connected = true; + if (connected) { + *connected = true; + } return true; default: break; @@ -339,6 +343,7 @@ scrcpy(struct scrcpy_options *options) { .tcpip_dst = options->tcpip_dst, .cleanup = options->cleanup, .power_on = options->power_on, + .list_encoders = options->list_encoders, }; static const struct sc_server_callbacks cbs = { @@ -356,6 +361,12 @@ scrcpy(struct scrcpy_options *options) { server_started = true; + if (options->list_encoders) { + bool ok = await_for_server(NULL); + ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE; + goto end; + } + if (options->display) { sdl_set_hints(options->render_driver); } diff --git a/app/src/server.c b/app/src/server.c index b50003c9..077614a8 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -300,6 +300,9 @@ execute_server(struct sc_server *server, // By default, power_on is true ADD_PARAM("power_on=false"); } + if (params->list_encoders) { + ADD_PARAM("list_encoders=true"); + } #undef ADD_PARAM @@ -848,6 +851,25 @@ run_server(void *data) { assert(serial); LOGD("Device serial: %s", serial); + ok = push_server(&server->intr, serial); + if (!ok) { + goto error_connection_failed; + } + + // If --list-encoders is passed, then the server just prints the encoders + // then exits. + if (params->list_encoders) { + sc_pid pid = execute_server(server, params); + if (pid == SC_PROCESS_NONE) { + goto error_connection_failed; + } + sc_process_wait(pid, NULL); // ignore exit code + sc_process_close(pid); + // Wake up await_for_server() + server->cbs->on_connected(server, server->cbs_userdata); + return 0; + } + int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x", params->scid); if (r == -1) { @@ -857,11 +879,6 @@ run_server(void *data) { assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8); assert(server->device_socket_name); - ok = push_server(&server->intr, serial); - if (!ok) { - goto error_connection_failed; - } - ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial, server->device_socket_name, params->port_range, params->force_adb_forward); diff --git a/app/src/server.h b/app/src/server.h index c20508e0..ada04baa 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -55,6 +55,7 @@ struct sc_server_params { bool select_tcpip; bool cleanup; bool power_on; + bool list_encoders; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index a70a475b..540d8306 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -334,7 +334,7 @@ public final class AudioEncoder { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); + Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildAudioEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 831dc994..0bcd1a54 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -139,7 +139,7 @@ public final class CleanUp { builder.start(); } - private static void unlinkSelf() { + public static void unlinkSelf() { try { new File(SERVER_PATH).delete(); } catch (Exception e) { diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java index 96887c14..aca54d20 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -10,6 +10,24 @@ import java.util.List; public final class CodecUtils { + public static final class DeviceEncoder { + private final Codec codec; + private final MediaCodecInfo info; + + DeviceEncoder(Codec codec, MediaCodecInfo info) { + this.codec = codec; + this.info = info; + } + + public Codec getCodec() { + return codec; + } + + public MediaCodecInfo getInfo() { + return info; + } + } + private CodecUtils() { // not instantiable } @@ -26,28 +44,63 @@ public final class CodecUtils { } } - public static String buildUnknownEncoderMessage(Codec codec, String encoderName) { - StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found"); - MediaCodecInfo[] encoders = listEncoders(codec.getMimeType()); - if (encoders != null && encoders.length > 0) { - msg.append("\nTry to use one of the available encoders:"); - String codecOption = codec.getType() == Codec.Type.VIDEO ? "video-codec" : "audio-codec"; - for (MediaCodecInfo encoder : encoders) { - msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName()); - msg.append(" --encoder='").append(encoder.getName()).append("'"); + public static String buildVideoEncoderListMessage() { + StringBuilder builder = new StringBuilder("List of video encoders:"); + List videoEncoders = CodecUtils.listVideoEncoders(); + if (videoEncoders.isEmpty()) { + builder.append("\n (none)"); + } else { + for (CodecUtils.DeviceEncoder encoder : videoEncoders) { + builder.append("\n --video-codec=").append(encoder.getCodec().getName()); + builder.append(" --video-encoder='").append(encoder.getInfo().getName()).append("'"); + } + } + return builder.toString(); + } + + public static String buildAudioEncoderListMessage() { + StringBuilder builder = new StringBuilder("List of audio encoders:"); + List audioEncoders = CodecUtils.listAudioEncoders(); + if (audioEncoders.isEmpty()) { + builder.append("\n (none)"); + } else { + for (CodecUtils.DeviceEncoder encoder : audioEncoders) { + builder.append("\n --audio-codec=").append(encoder.getCodec().getName()); + builder.append(" --audio-encoder='").append(encoder.getInfo().getName()).append("'"); } } - return msg.toString(); + return builder.toString(); } - private static MediaCodecInfo[] listEncoders(String mimeType) { + private static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) { List result = new ArrayList<>(); - MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo codecInfo : list.getCodecInfos()) { + for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) { if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { result.add(codecInfo); } } return result.toArray(new MediaCodecInfo[result.size()]); } + + public static List listVideoEncoders() { + List encoders = new ArrayList<>(); + MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + for (VideoCodec codec : VideoCodec.values()) { + for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) { + encoders.add(new DeviceEncoder(codec, info)); + } + } + return encoders; + } + + public static List listAudioEncoders() { + List encoders = new ArrayList<>(); + MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); + for (AudioCodec codec : AudioCodec.values()) { + for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) { + encoders.add(new DeviceEncoder(codec, info)); + } + } + return encoders; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 86838022..8cac5e2c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -33,6 +33,8 @@ public class Options { private boolean cleanup = true; private boolean powerOn = true; + private boolean listEncoders; + // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size private boolean sendFrameMeta = true; // send PTS so that the client may record properly @@ -239,6 +241,14 @@ public class Options { this.powerOn = powerOn; } + public boolean getListEncoders() { + return listEncoders; + } + + public void setListEncoders(boolean listEncoders) { + this.listEncoders = listEncoders; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 77cd1de4..668a4ed0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -202,7 +202,7 @@ public class ScreenEncoder implements Device.RotationListener { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName)); + Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildVideoEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f30d65f6..adfbef2a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -291,6 +291,10 @@ public final class Server { boolean powerOn = Boolean.parseBoolean(value); options.setPowerOn(powerOn); break; + case "list_encoders": + boolean listEncoders = Boolean.parseBoolean(value); + options.setListEncoders(listEncoders); + break; case "send_device_meta": boolean sendDeviceMeta = Boolean.parseBoolean(value); options.setSendDeviceMeta(sendDeviceMeta); @@ -350,6 +354,17 @@ public final class Server { Ln.initLogLevel(options.getLogLevel()); + if (options.getListEncoders()) { + if (options.getCleanup()) { + CleanUp.unlinkSelf(); + } + + Ln.i(CodecUtils.buildVideoEncoderListMessage()); + Ln.i(CodecUtils.buildAudioEncoderListMessage()); + // Just print the available encoders, do not mirror + return; + } + try { scrcpy(options); } catch (ConfigurationException e) { From 50d56a9a2bb75398c562e71b131de5c3ed43e95e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 21:27:11 +0100 Subject: [PATCH 0738/1133] Quit on audio configuration failure When audio capture fails on the device, scrcpy continues mirroring the video stream. This allows to enable audio by default only when supported. However, if an audio configuration occurs (for example the user explicitly selected an unknown audio encoder), this must be treated as an error and scrcpy must exit. PR #3757 --- app/src/demuxer.c | 6 ++++++ app/src/scrcpy.c | 13 +++++++++++-- .../java/com/genymobile/scrcpy/AudioEncoder.java | 8 ++++++-- .../main/java/com/genymobile/scrcpy/Streamer.java | 13 +++++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 64bf30a3..d80a5dda 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -195,6 +195,12 @@ run_demuxer(void *data) { goto end; } + if (raw_codec_id == 1) { + LOGE("Demuxer '%s': stream configuration error on the device", + demuxer->name); + goto end; + } + enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id); if (codec_id == AV_CODEC_ID_NONE) { LOGE("Demuxer '%s': stream disabled due to unsupported codec", diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6d0fac9e..5739c3b2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -231,10 +231,19 @@ static void sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, void *userdata) { (void) demuxer; - (void) eos; (void) userdata; - // Contrary to the video demuxer, keep mirroring if only the audio fails + // Contrary to the video demuxer, keep mirroring if only the audio fails. + // 'eos' is true on end-of-stream, including when audio capture is not + // possible on the device (so that scrcpy continue to mirror video without + // failing). + // However, if an audio configuration failure occurs (for example the user + // explicitly selected an unknown audio encoder), 'eos' is false and scrcpy + // must exit. + + if (!eos) { + PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); + } } static void diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 540d8306..66950004 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -222,7 +222,7 @@ public final class AudioEncoder { public void encode() throws IOException, ConfigurationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { Ln.w("Audio disabled: it is not supported before Android 11"); - streamer.writeDisableStream(); + streamer.writeDisableStream(false); return; } @@ -279,9 +279,13 @@ public final class AudioEncoder { outputThread.start(); waitEnded(); + } catch (ConfigurationException e) { + // Notify the error to make scrcpy exit + streamer.writeDisableStream(true); + throw e; } catch (Throwable e) { // Notify the client that the audio could not be captured - streamer.writeDisableStream(); + streamer.writeDisableStream(false); throw e; } finally { // Cleanup everything (either at the end or on error at any step of the initialization) diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index 7cc065eb..9bfe7e91 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -40,10 +40,15 @@ public final class Streamer { } } - public void writeDisableStream() throws IOException { - // Writing 0 (32-bit) as codec-id means that the device disables the stream (because it could not capture) - byte[] zeros = new byte[4]; - IO.writeFully(fd, zeros, 0, zeros.length); + public void writeDisableStream(boolean error) throws IOException { + // Writing a specific code as codec-id means that the device disables the stream + // code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only + // code 1: a configuration error occurred, scrcpy must be stopped + byte[] code = new byte[4]; + if (error) { + code[3] = 1; + } + IO.writeFully(fd, code, 0, code.length); } public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { From 2596ca02f0d9b53ddf322873ad141197216f00f4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 23:09:25 +0100 Subject: [PATCH 0739/1133] Move log message helpers to LogUtils This class will also contain other log helpers. --- .../com/genymobile/scrcpy/AudioEncoder.java | 2 +- .../com/genymobile/scrcpy/CodecUtils.java | 28 -------------- .../java/com/genymobile/scrcpy/LogUtils.java | 38 +++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 4 +- 5 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/LogUtils.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 66950004..1ce4107f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -338,7 +338,7 @@ public final class AudioEncoder { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildAudioEncoderListMessage()); + Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java index aca54d20..afb6f904 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecUtils.java @@ -44,34 +44,6 @@ public final class CodecUtils { } } - public static String buildVideoEncoderListMessage() { - StringBuilder builder = new StringBuilder("List of video encoders:"); - List videoEncoders = CodecUtils.listVideoEncoders(); - if (videoEncoders.isEmpty()) { - builder.append("\n (none)"); - } else { - for (CodecUtils.DeviceEncoder encoder : videoEncoders) { - builder.append("\n --video-codec=").append(encoder.getCodec().getName()); - builder.append(" --video-encoder='").append(encoder.getInfo().getName()).append("'"); - } - } - return builder.toString(); - } - - public static String buildAudioEncoderListMessage() { - StringBuilder builder = new StringBuilder("List of audio encoders:"); - List audioEncoders = CodecUtils.listAudioEncoders(); - if (audioEncoders.isEmpty()) { - builder.append("\n (none)"); - } else { - for (CodecUtils.DeviceEncoder encoder : audioEncoders) { - builder.append("\n --audio-codec=").append(encoder.getCodec().getName()); - builder.append(" --audio-encoder='").append(encoder.getInfo().getName()).append("'"); - } - } - return builder.toString(); - } - private static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) { List result = new ArrayList<>(); for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) { diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java new file mode 100644 index 00000000..e74b7e97 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -0,0 +1,38 @@ +package com.genymobile.scrcpy; + +import java.util.List; + +public final class LogUtils { + + private LogUtils() { + // not instantiable + } + + public static String buildVideoEncoderListMessage() { + StringBuilder builder = new StringBuilder("List of video encoders:"); + List videoEncoders = CodecUtils.listVideoEncoders(); + if (videoEncoders.isEmpty()) { + builder.append("\n (none)"); + } else { + for (CodecUtils.DeviceEncoder encoder : videoEncoders) { + builder.append("\n --video-codec=").append(encoder.getCodec().getName()); + builder.append(" --video-encoder='").append(encoder.getInfo().getName()).append("'"); + } + } + return builder.toString(); + } + + public static String buildAudioEncoderListMessage() { + StringBuilder builder = new StringBuilder("List of audio encoders:"); + List audioEncoders = CodecUtils.listAudioEncoders(); + if (audioEncoders.isEmpty()) { + builder.append("\n (none)"); + } else { + for (CodecUtils.DeviceEncoder encoder : audioEncoders) { + builder.append("\n --audio-codec=").append(encoder.getCodec().getName()); + builder.append(" --audio-encoder='").append(encoder.getInfo().getName()).append("'"); + } + } + return builder.toString(); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 668a4ed0..f5f996ba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -202,7 +202,7 @@ public class ScreenEncoder implements Device.RotationListener { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + CodecUtils.buildVideoEncoderListMessage()); + Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index adfbef2a..f46cf308 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -359,8 +359,8 @@ public final class Server { CleanUp.unlinkSelf(); } - Ln.i(CodecUtils.buildVideoEncoderListMessage()); - Ln.i(CodecUtils.buildAudioEncoderListMessage()); + Ln.i(LogUtils.buildVideoEncoderListMessage()); + Ln.i(LogUtils.buildAudioEncoderListMessage()); // Just print the available encoders, do not mirror return; } From b65301f672e852fe2f3fded1fac2091c85679c35 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 23:10:15 +0100 Subject: [PATCH 0740/1133] Add --list-displays Add an option to list the device displays properly. --- README.md | 2 +- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 9 ++++++--- app/src/cli.c | 16 ++++++++++++---- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 3 ++- app/src/server.c | 7 +++++-- app/src/server.h | 1 + .../main/java/com/genymobile/scrcpy/Device.java | 14 +------------- .../java/com/genymobile/scrcpy/LogUtils.java | 15 +++++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../main/java/com/genymobile/scrcpy/Server.java | 17 +++++++++++++---- 14 files changed, 69 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 5575fc4d..a2e275f9 100644 --- a/README.md +++ b/README.md @@ -718,7 +718,7 @@ scrcpy --display=1 The list of display ids can be retrieved by: ```bash -adb shell dumpsys display # search "mDisplayId=" in the output +scrcpy --list-displays ``` The secondary display may only be controlled if the device runs at least Android diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 70695019..fa95ce6e 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -19,6 +19,7 @@ _scrcpy() { -K --hid-keyboard -h --help --legacy-paste + --list-displays --list-encoders --lock-video-orientation --lock-video-orientation= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 268aa626..231405ce 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -26,6 +26,7 @@ arguments=( {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-h,--help}'[Print the help]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' + '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' '--max-fps=[Limit the frame rate of screen capture]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index add263c2..40b8158c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -73,10 +73,9 @@ Disable screensaver while scrcpy is running. .TP .BI "\-\-display " id -Specify the display id to mirror. +Specify the device display id to mirror. -The list of possible display ids can be listed by "adb shell dumpsys display" -(search "mDisplayId=" in the output). +The available display ids can be listed by \-\-list\-displays. Default is 0. @@ -134,6 +133,10 @@ This is a workaround for some devices not behaving as expected when setting the .B \-\-list\-encoders List video and audio encoders available on the device. +.TP +.B \-\-list\-displays +List displays available on the device. + .TP \fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR] Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise. diff --git a/app/src/cli.c b/app/src/cli.c index edb694a6..8dfcdc79 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -69,6 +69,7 @@ enum { OPT_AUDIO_CODEC_OPTIONS, OPT_AUDIO_ENCODER, OPT_LIST_ENCODERS, + OPT_LIST_DISPLAYS, }; struct sc_option { @@ -198,10 +199,9 @@ static const struct sc_option options[] = { .longopt_id = OPT_DISPLAY_ID, .longopt = "display", .argdesc = "id", - .text = "Specify the display id to mirror.\n" - "The list of possible display ids can be listed by:\n" - " adb shell dumpsys display\n" - "(search \"mDisplayId=\" in the output)\n" + .text = "Specify the device display id to mirror.\n" + "The available display ids can be listed by:\n" + " scrcpy --list-displays\n" "Default is 0.", }, { @@ -272,6 +272,11 @@ static const struct sc_option options[] = { "This is a workaround for some devices not behaving as " "expected when setting the device clipboard programmatically.", }, + { + .longopt_id = OPT_LIST_DISPLAYS, + .longopt = "list-displays", + .text = "List device displays.", + }, { .longopt_id = OPT_LIST_ENCODERS, .longopt = "list-encoders", @@ -1803,6 +1808,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_LIST_ENCODERS: opts->list_encoders = true; break; + case OPT_LIST_DISPLAYS: + opts->list_displays = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 1839df6e..8560b37b 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -73,4 +73,5 @@ const struct scrcpy_options scrcpy_options_default = { .power_on = true, .audio = true, .list_encoders = false, + .list_displays = false, }; diff --git a/app/src/options.h b/app/src/options.h index 568b8155..a15d51f8 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -155,6 +155,7 @@ struct scrcpy_options { bool power_on; bool audio; bool list_encoders; + bool list_displays; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5739c3b2..4d68fb29 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -353,6 +353,7 @@ scrcpy(struct scrcpy_options *options) { .cleanup = options->cleanup, .power_on = options->power_on, .list_encoders = options->list_encoders, + .list_displays = options->list_displays, }; static const struct sc_server_callbacks cbs = { @@ -370,7 +371,7 @@ scrcpy(struct scrcpy_options *options) { server_started = true; - if (options->list_encoders) { + if (options->list_encoders || options->list_displays) { bool ok = await_for_server(NULL); ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE; goto end; diff --git a/app/src/server.c b/app/src/server.c index 077614a8..9d4fb098 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -303,6 +303,9 @@ execute_server(struct sc_server *server, if (params->list_encoders) { ADD_PARAM("list_encoders=true"); } + if (params->list_displays) { + ADD_PARAM("list_displays=true"); + } #undef ADD_PARAM @@ -856,9 +859,9 @@ run_server(void *data) { goto error_connection_failed; } - // If --list-encoders is passed, then the server just prints the encoders + // If --list-* is passed, then the server just prints the requested data // then exits. - if (params->list_encoders) { + if (params->list_encoders || params->list_displays) { sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { goto error_connection_failed; diff --git a/app/src/server.h b/app/src/server.h index ada04baa..8edf2666 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -56,6 +56,7 @@ struct sc_server_params { bool cleanup; bool power_on; bool list_encoders; + bool list_displays; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index c7f7c1f8..b66474b7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -65,7 +65,7 @@ public final class Device { displayId = options.getDisplayId(); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { - Ln.e(buildUnknownDisplayIdMessage(displayId)); + Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); throw new ConfigurationException("Unknown display id: " + displayId); } @@ -130,18 +130,6 @@ public final class Device { } } - private static String buildUnknownDisplayIdMessage(int displayId) { - StringBuilder msg = new StringBuilder("Display ").append(displayId).append(" not found"); - int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); - if (displayIds != null && displayIds.length > 0) { - msg.append("\nTry to use one of the available display ids:"); - for (int id : displayIds) { - msg.append("\n scrcpy --display=").append(id); - } - } - return msg.toString(); - } - public synchronized void setMaxSize(int newMaxSize) { maxSize = newMaxSize; screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index e74b7e97..c073336d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -1,5 +1,7 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ServiceManager; + import java.util.List; public final class LogUtils { @@ -35,4 +37,17 @@ public final class LogUtils { } return builder.toString(); } + + public static String buildDisplayListMessage() { + StringBuilder builder = new StringBuilder("List of displays:"); + int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); + if (displayIds == null || displayIds.length == 0) { + builder.append("\n (none)"); + } else { + for (int id : displayIds) { + builder.append("\n --display=").append(id); + } + } + return builder.toString(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 8cac5e2c..bcf235ed 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -34,6 +34,7 @@ public class Options { private boolean powerOn = true; private boolean listEncoders; + private boolean listDisplays; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -249,6 +250,14 @@ public class Options { this.listEncoders = listEncoders; } + public boolean getListDisplays() { + return listDisplays; + } + + public void setListDisplays(boolean listDisplays) { + this.listDisplays = listDisplays; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index f46cf308..35da6965 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -295,6 +295,10 @@ public final class Server { boolean listEncoders = Boolean.parseBoolean(value); options.setListEncoders(listEncoders); break; + case "list_displays": + boolean listDisplays = Boolean.parseBoolean(value); + options.setListDisplays(listDisplays); + break; case "send_device_meta": boolean sendDeviceMeta = Boolean.parseBoolean(value); options.setSendDeviceMeta(sendDeviceMeta); @@ -354,14 +358,19 @@ public final class Server { Ln.initLogLevel(options.getLogLevel()); - if (options.getListEncoders()) { + if (options.getListEncoders() || options.getListDisplays()) { if (options.getCleanup()) { CleanUp.unlinkSelf(); } - Ln.i(LogUtils.buildVideoEncoderListMessage()); - Ln.i(LogUtils.buildAudioEncoderListMessage()); - // Just print the available encoders, do not mirror + if (options.getListEncoders()) { + Ln.i(LogUtils.buildVideoEncoderListMessage()); + Ln.i(LogUtils.buildAudioEncoderListMessage()); + } + if (options.getListDisplays()) { + Ln.i(LogUtils.buildDisplayListMessage()); + } + // Just print the requested data, do not mirror return; } From a205ff6c8b5ac1cae5a5b2763742247da84cd4b5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Feb 2023 23:12:24 +0100 Subject: [PATCH 0741/1133] Log display sizes in display list This is more convenient than just the display id alone. --- .../main/java/com/genymobile/scrcpy/LogUtils.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index c073336d..243a156b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import java.util.List; @@ -40,12 +41,21 @@ public final class LogUtils { public static String buildDisplayListMessage() { StringBuilder builder = new StringBuilder("List of displays:"); - int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); + DisplayManager displayManager = ServiceManager.getDisplayManager(); + int[] displayIds = displayManager.getDisplayIds(); if (displayIds == null || displayIds.length == 0) { builder.append("\n (none)"); } else { for (int id : displayIds) { - builder.append("\n --display=").append(id); + builder.append("\n --display=").append(id).append(" ("); + DisplayInfo displayInfo = displayManager.getDisplayInfo(id); + if (displayInfo != null) { + Size size = displayInfo.getSize(); + builder.append(size.getWidth()).append("x").append(size.getHeight()); + } else { + builder.append("size unknown"); + } + builder.append(")"); } } return builder.toString(); From 99837fa600af9f128fa0830b9b007a6944b9808f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Feb 2023 21:13:56 +0100 Subject: [PATCH 0742/1133] Rename decoder to video_decoder This prepares the introduction of audio_decoder. PR #3757 --- app/src/scrcpy.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4d68fb29..578943f9 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -42,7 +42,7 @@ struct scrcpy { struct sc_screen screen; struct sc_demuxer video_demuxer; struct sc_demuxer audio_demuxer; - struct sc_decoder decoder; + struct sc_decoder video_decoder; struct sc_recorder recorder; #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; @@ -436,13 +436,13 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, NULL); } - bool needs_decoder = options->display; + bool needs_video_decoder = options->display; #ifdef HAVE_V4L2 - needs_decoder |= !!options->v4l2_device; + needs_video_decoder |= !!options->v4l2_device; #endif - if (needs_decoder) { - sc_decoder_init(&s->decoder); - sc_demuxer_add_sink(&s->video_demuxer, &s->decoder.packet_sink); + if (needs_video_decoder) { + sc_decoder_init(&s->video_decoder); + sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink); } if (options->record_filename) { @@ -656,7 +656,7 @@ aoa_hid_end: } screen_initialized = true; - sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink); + sc_decoder_add_sink(&s->video_decoder, &s->screen.frame_sink); } #ifdef HAVE_V4L2 @@ -666,7 +666,7 @@ aoa_hid_end: goto end; } - sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); + sc_decoder_add_sink(&s->video_decoder, &s->v4l2_sink.frame_sink); v4l2_sink_initialized = true; } From 05f0e35d2a9074ecb214d70303f3f271631af645 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Feb 2023 21:22:35 +0100 Subject: [PATCH 0743/1133] Give a name to decoder instances This will be useful in logs. PR #3757 --- app/src/decoder.c | 11 +++++++---- app/src/decoder.h | 5 ++++- app/src/scrcpy.c | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 337aa329..d750253c 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -48,7 +48,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { - LOGE("Could not open codec"); + LOGE("Decoder '%s': could not open codec", decoder->name); avcodec_free_context(&decoder->codec_ctx); return false; } @@ -101,7 +101,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { int ret = avcodec_send_packet(decoder->codec_ctx, packet); if (ret < 0 && ret != AVERROR(EAGAIN)) { - LOGE("Could not send video packet: %d", ret); + LOGE("Decoder '%s': could not send video packet: %d", + decoder->name, ret); return false; } ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); @@ -114,7 +115,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { av_frame_unref(decoder->frame); } else if (ret != AVERROR(EAGAIN)) { - LOGE("Could not receive video frame: %d", ret); + LOGE("Decoder '%s', could not receive video frame: %d", + decoder->name, ret); return false; } return true; @@ -140,7 +142,8 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink, } void -sc_decoder_init(struct sc_decoder *decoder) { +sc_decoder_init(struct sc_decoder *decoder, const char *name) { + decoder->name = name; // statically allocated decoder->sink_count = 0; static const struct sc_packet_sink_ops ops = { diff --git a/app/src/decoder.h b/app/src/decoder.h index 16adc5ec..aace1af6 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -14,6 +14,8 @@ struct sc_decoder { struct sc_packet_sink packet_sink; // packet sink trait + const char *name; // must be statically allocated (e.g. a string literal) + struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS]; unsigned sink_count; @@ -21,8 +23,9 @@ struct sc_decoder { AVFrame *frame; }; +// The name must be statically allocated (e.g. a string literal) void -sc_decoder_init(struct sc_decoder *decoder); +sc_decoder_init(struct sc_decoder *decoder, const char *name); void sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 578943f9..944d5f05 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -441,7 +441,7 @@ scrcpy(struct scrcpy_options *options) { needs_video_decoder |= !!options->v4l2_device; #endif if (needs_video_decoder) { - sc_decoder_init(&s->video_decoder); + sc_decoder_init(&s->video_decoder, "video"); sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink); } From e22660d698900f949cfc6afdb41d49cb371f7cff Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Feb 2023 21:31:39 +0100 Subject: [PATCH 0744/1133] Add an audio decoder PR #3757 --- app/src/scrcpy.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 944d5f05..eb70749a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -43,6 +43,7 @@ struct scrcpy { struct sc_demuxer video_demuxer; struct sc_demuxer audio_demuxer; struct sc_decoder video_decoder; + struct sc_decoder audio_decoder; struct sc_recorder recorder; #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; @@ -437,6 +438,7 @@ scrcpy(struct scrcpy_options *options) { } bool needs_video_decoder = options->display; + bool needs_audio_decoder = options->audio && options->display; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; #endif @@ -444,6 +446,10 @@ scrcpy(struct scrcpy_options *options) { sc_decoder_init(&s->video_decoder, "video"); sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink); } + if (needs_audio_decoder) { + sc_decoder_init(&s->audio_decoder, "audio"); + sc_demuxer_add_sink(&s->audio_demuxer, &s->audio_decoder.packet_sink); + } if (options->record_filename) { static const struct sc_recorder_callbacks recorder_cbs = { From 619730edafe7ab2fd2fb022f9eb6e350bfe1bb52 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Feb 2023 16:19:58 +0100 Subject: [PATCH 0745/1133] Pass AVCodecContext to frame sinks Frame consumers may need details about the frame format. PR #3757 --- app/src/decoder.c | 11 ++++++++--- app/src/screen.c | 6 +++++- app/src/trait/frame_sink.h | 3 ++- app/src/v4l2_sink.c | 9 ++++++--- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index d750253c..96d4a010 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -25,10 +25,10 @@ sc_decoder_close_sinks(struct sc_decoder *decoder) { } static bool -sc_decoder_open_sinks(struct sc_decoder *decoder) { +sc_decoder_open_sinks(struct sc_decoder *decoder, const AVCodecContext *ctx) { for (unsigned i = 0; i < decoder->sink_count; ++i) { struct sc_frame_sink *sink = decoder->sinks[i]; - if (!sink->ops->open(sink)) { + if (!sink->ops->open(sink, ctx)) { sc_decoder_close_first_sinks(decoder, i); return false; } @@ -47,6 +47,11 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; + if (codec->type == AVMEDIA_TYPE_VIDEO) { + // Hardcoded video properties + decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + } + if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { LOGE("Decoder '%s': could not open codec", decoder->name); avcodec_free_context(&decoder->codec_ctx); @@ -61,7 +66,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { return false; } - if (!sc_decoder_open_sinks(decoder)) { + if (!sc_decoder_open_sinks(decoder, decoder->codec_ctx)) { av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); diff --git a/app/src/screen.c b/app/src/screen.c index 425ba2c3..a9a48eae 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -330,7 +330,11 @@ event_watcher(void *data, SDL_Event *event) { #endif static bool -sc_screen_frame_sink_open(struct sc_frame_sink *sink) { +sc_screen_frame_sink_open(struct sc_frame_sink *sink, + const AVCodecContext *ctx) { + assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P); + (void) ctx; + struct sc_screen *screen = DOWNCAST(sink); (void) screen; #ifndef NDEBUG diff --git a/app/src/trait/frame_sink.h b/app/src/trait/frame_sink.h index 0214ab3e..30bf0d37 100644 --- a/app/src/trait/frame_sink.h +++ b/app/src/trait/frame_sink.h @@ -5,6 +5,7 @@ #include #include +#include typedef struct AVFrame AVFrame; @@ -18,7 +19,7 @@ struct sc_frame_sink { }; struct sc_frame_sink_ops { - bool (*open)(struct sc_frame_sink *sink); + bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx); void (*close)(struct sc_frame_sink *sink); bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame); }; diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 9a0011f2..ba876b2b 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -156,7 +156,10 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, } static bool -sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { +sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { + assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P); + (void) ctx; + static const struct sc_video_buffer_callbacks cbs = { .on_new_frame = sc_video_buffer_on_new_frame, }; @@ -336,9 +339,9 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { } static bool -sc_v4l2_frame_sink_open(struct sc_frame_sink *sink) { +sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) { struct sc_v4l2_sink *vs = DOWNCAST(sink); - return sc_v4l2_sink_open(vs); + return sc_v4l2_sink_open(vs, ctx); } static void From 20d41fdd7e236b33fc70a12758dacdf47099c83e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Feb 2023 14:32:02 +0100 Subject: [PATCH 0746/1133] Introduce bytebuf util Add a ring-buffer for bytes. It will be useful for audio buffering. PR #3757 --- app/meson.build | 5 +++ app/src/util/bytebuf.c | 75 +++++++++++++++++++++++++++++++++ app/src/util/bytebuf.h | 90 ++++++++++++++++++++++++++++++++++++++++ app/tests/test_bytebuf.c | 82 ++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 app/src/util/bytebuf.c create mode 100644 app/src/util/bytebuf.h create mode 100644 app/tests/test_bytebuf.c diff --git a/app/meson.build b/app/meson.build index 2ea3b317..8be917e9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -30,6 +30,7 @@ src = [ 'src/version.c', 'src/video_buffer.c', 'src/util/acksync.c', + 'src/util/bytebuf.c', 'src/util/file.c', 'src/util/intmap.c', 'src/util/intr.c', @@ -254,6 +255,10 @@ if get_option('buildtype') == 'debug' ['test_binary', [ 'tests/test_binary.c', ]], + ['test_bytebuf', [ + 'tests/test_bytebuf.c', + 'src/util/bytebuf.c', + ]], ['test_cbuf', [ 'tests/test_cbuf.c', ]], diff --git a/app/src/util/bytebuf.c b/app/src/util/bytebuf.c new file mode 100644 index 00000000..61814376 --- /dev/null +++ b/app/src/util/bytebuf.c @@ -0,0 +1,75 @@ +#include "bytebuf.h" + +#include +#include +#include + +#include "util/log.h" + +bool +sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) { + assert(alloc_size); + buf->data = malloc(alloc_size); + if (!buf->data) { + LOG_OOM(); + return false; + } + + buf->alloc_size = alloc_size; + buf->head = 0; + buf->tail = 0; + + return true; +} + +void +sc_bytebuf_destroy(struct sc_bytebuf *buf) { + free(buf->data); +} + +void +sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) { + assert(len); + assert(len <= sc_bytebuf_read_available(buf)); + assert(buf->tail != buf->head); // the buffer could not be empty + + size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size; + size_t right_len = right_limit - buf->tail; + if (len < right_len) { + right_len = len; + } + memcpy(to, buf->data + buf->tail, right_len); + + if (len > right_len) { + memcpy(to + right_len, buf->data, len - right_len); + } + + buf->tail = (buf->tail + len) % buf->alloc_size; +} + +void +sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) { + assert(len); + assert(len <= sc_bytebuf_read_available(buf)); + assert(buf->tail != buf->head); // the buffer could not be empty + + buf->tail = (buf->tail + len) % buf->alloc_size; +} + +void +sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { + assert(len); + assert(len <= sc_bytebuf_write_available(buf)); + + size_t right_len = buf->alloc_size - buf->head; + if (len < right_len) { + right_len = len; + } + memcpy(buf->data + buf->head, from, right_len); + + if (len > right_len) { + memcpy(buf->data, from + right_len, len - right_len); + } + + buf->head = (buf->head + len) % buf->alloc_size; +} diff --git a/app/src/util/bytebuf.h b/app/src/util/bytebuf.h new file mode 100644 index 00000000..fcebc2d3 --- /dev/null +++ b/app/src/util/bytebuf.h @@ -0,0 +1,90 @@ +#ifndef SC_BYTEBUF_H +#define SC_BYTEBUF_H + +#include "common.h" + +#include +#include + +struct sc_bytebuf { + uint8_t *data; + // The actual capacity is (allocated - 1) so that head == tail is + // non-ambiguous + size_t alloc_size; + size_t head; // writter cursor + size_t tail; // reader cursor + // empty: tail == head + // full: ((tail + 1) % alloc_size) == head +}; + +bool +sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size); + +/** + * Copy from the bytebuf to a user-provided array + * + * The caller must check that len <= sc_bytebuf_read_available() (it is an + * error to attempt to read more bytes than available). + * + * This function is guaranteed not to write to buf->head. + */ +void +sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len); + +/** + * Drop len bytes from the buffer + * + * The caller must check that len <= sc_bytebuf_read_available() (it is an + * error to attempt to skip more bytes than available). + * + * This function is guaranteed not to write to buf->head. + * + * It is equivalent to call sc_bytebuf_read() to some array and discard the + * array (but this function is more efficient since there is no copy). + */ +void +sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len); + +/** + * Copy the user-provided array to the bytebuf + * + * The caller must check that len <= sc_bytebuf_write_available() (it is an + * error to write more bytes than the remaining available space). + * + * This function is guaranteed not to write to buf->tail. + */ +void +sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len); + +/** + * Return the number of bytes which can be read + * + * It is an error to read more bytes than available. + */ +static inline size_t +sc_bytebuf_read_available(struct sc_bytebuf *buf) { + return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size; +} + +/** + * Return the number of bytes which can be written + * + * It is an error to write more bytes than available. + */ +static inline size_t +sc_bytebuf_write_available(struct sc_bytebuf *buf) { + return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size; +} + +/** + * Return the actual capacity of the buffer (read available + write available) + */ +static inline size_t +sc_bytebuf_capacity(struct sc_bytebuf *buf) { + return buf->alloc_size - 1; +} + +void +sc_bytebuf_destroy(struct sc_bytebuf *buf); + +#endif diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c new file mode 100644 index 00000000..fbb33765 --- /dev/null +++ b/app/tests/test_bytebuf.c @@ -0,0 +1,82 @@ +#include "common.h" + +#include +#include + +#include "util/bytebuf.h" + +void test_bytebuf_simple(void) { + struct sc_bytebuf buf; + uint8_t data[20]; + + bool ok = sc_bytebuf_init(&buf, 20); + assert(ok); + + sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1); + assert(sc_bytebuf_read_available(&buf) == 5); + + sc_bytebuf_read(&buf, data, 4); + assert(!strncmp((char *) data, "hell", 4)); + + sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1); + assert(sc_bytebuf_read_available(&buf) == 7); + + sc_bytebuf_write(&buf, (uint8_t *) "!", 1); + assert(sc_bytebuf_read_available(&buf) == 8); + + sc_bytebuf_read(&buf, &data[4], 8); + assert(sc_bytebuf_read_available(&buf) == 0); + + data[12] = '\0'; + assert(!strcmp((char *) data, "hello world!")); + assert(sc_bytebuf_read_available(&buf) == 0); + + sc_bytebuf_destroy(&buf); +} + +void test_bytebuf_boundaries(void) { + struct sc_bytebuf buf; + uint8_t data[20]; + + bool ok = sc_bytebuf_init(&buf, 20); + assert(ok); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 6); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 18); + + sc_bytebuf_read(&buf, data, 9); + assert(!strncmp((char *) data, "hello hel", 9)); + assert(sc_bytebuf_read_available(&buf) == 9); + + sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1); + assert(sc_bytebuf_read_available(&buf) == 14); + + sc_bytebuf_write(&buf, (uint8_t *) "!", 1); + assert(sc_bytebuf_read_available(&buf) == 15); + + sc_bytebuf_skip(&buf, 3); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_read(&buf, data, 12); + data[12] = '\0'; + assert(!strcmp((char *) data, "hello world!")); + assert(sc_bytebuf_read_available(&buf) == 0); + + sc_bytebuf_destroy(&buf); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_bytebuf_simple(); + test_bytebuf_boundaries(); + + return 0; +} From b60a8aa657f62a596e528f244f1c101dc2f054b7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Feb 2023 18:45:05 +0100 Subject: [PATCH 0747/1133] Add two-step write feature to bytebuf If there is exactly one producer, then it can assume that the remaining space in the buffer will only increase until it writes something. This assumption may allow the producer to write to the buffer (up to a known safe size) without any synchronization mechanism, thus allowing to read and write different parts of the buffer in parallel. The producer can then commit the write with a lock held, and update its knowledge of the safe empty remaining space. PR #3757 --- app/src/util/bytebuf.c | 39 ++++++++++++++++++++++++++++++----- app/src/util/bytebuf.h | 24 ++++++++++++++++++++++ app/tests/test_bytebuf.c | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/app/src/util/bytebuf.c b/app/src/util/bytebuf.c index 61814376..eac69e9c 100644 --- a/app/src/util/bytebuf.c +++ b/app/src/util/bytebuf.c @@ -56,11 +56,9 @@ sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) { buf->tail = (buf->tail + len) % buf->alloc_size; } -void -sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { - assert(len); - assert(len <= sc_bytebuf_write_available(buf)); - +static inline void +sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from, + size_t len) { size_t right_len = buf->alloc_size - buf->head; if (len < right_len) { right_len = len; @@ -70,6 +68,37 @@ sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { if (len > right_len) { memcpy(buf->data, from + right_len, len - right_len); } +} +static inline void +sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) { buf->head = (buf->head + len) % buf->alloc_size; } + +void +sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { + assert(len); + assert(len <= sc_bytebuf_write_available(buf)); + + sc_bytebuf_write_step0(buf, from, len); + sc_bytebuf_write_step1(buf, len); +} + +void +sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from, + size_t len) { + // *This function MUST NOT access buf->tail (even in assert()).* + // The purpose of this function is to allow a reader and a writer to access + // different parts of the buffer in parallel simultaneously. It is intended + // to be called without lock (only sc_bytebuf_commit_write() is intended to + // be called with lock held). + + assert(len < buf->alloc_size - 1); + sc_bytebuf_write_step0(buf, from, len); +} + +void +sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) { + assert(len <= sc_bytebuf_write_available(buf)); + sc_bytebuf_write_step1(buf, len); +} diff --git a/app/src/util/bytebuf.h b/app/src/util/bytebuf.h index fcebc2d3..e8279ef8 100644 --- a/app/src/util/bytebuf.h +++ b/app/src/util/bytebuf.h @@ -56,6 +56,30 @@ sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len); void sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len); +/** + * Copy the user-provided array to the bytebuf, but do not advance the cursor + * + * The caller must check that len <= sc_bytebuf_write_available() (it is an + * error to write more bytes than the remaining available space). + * + * After this function is called, the write must be committed with + * sc_bytebuf_commit_write(). + * + * The purpose of this mechanism is to acquire a lock only to commit the write, + * but not to perform the actual copy. + * + * This function is guaranteed not to access buf->tail. + */ +void +sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from, + size_t len); + +/** + * Commit a prepared write + */ +void +sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len); + /** * Return the number of bytes which can be read * diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c index fbb33765..75af3073 100644 --- a/app/tests/test_bytebuf.c +++ b/app/tests/test_bytebuf.c @@ -71,12 +71,56 @@ void test_bytebuf_boundaries(void) { sc_bytebuf_destroy(&buf); } +void test_bytebuf_two_steps_write(void) { + struct sc_bytebuf buf; + uint8_t data[20]; + + bool ok = sc_bytebuf_init(&buf, 20); + assert(ok); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 6); + + sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 12); // write not committed yet + + sc_bytebuf_read(&buf, data, 9); + assert(!strncmp((char *) data, "hello hel", 3)); + assert(sc_bytebuf_read_available(&buf) == 3); + + sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1); + assert(sc_bytebuf_read_available(&buf) == 9); + + sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1); + assert(sc_bytebuf_read_available(&buf) == 9); // write not committed yet + + sc_bytebuf_commit_write(&buf, sizeof("world") - 1); + assert(sc_bytebuf_read_available(&buf) == 14); + + sc_bytebuf_write(&buf, (uint8_t *) "!", 1); + assert(sc_bytebuf_read_available(&buf) == 15); + + sc_bytebuf_skip(&buf, 3); + assert(sc_bytebuf_read_available(&buf) == 12); + + sc_bytebuf_read(&buf, data, 12); + data[12] = '\0'; + assert(!strcmp((char *) data, "hello world!")); + assert(sc_bytebuf_read_available(&buf) == 0); + + sc_bytebuf_destroy(&buf); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; test_bytebuf_simple(); test_bytebuf_boundaries(); + test_bytebuf_two_steps_write(); return 0; } From de40cac6ad929f1d150b332f0e987c0d70ba5d38 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Mon, 30 Jan 2023 00:42:09 +0800 Subject: [PATCH 0748/1133] Add workaround to capture audio on Android 11 On Android 11, it is possible to start the capture only when the running app is in foreground. But scrcpy is not an app, it's a Java application started from shell. As a workaround, start an existing Android shell existing activity just to start the capture, then close it immediately. PR #3757 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- .../AudioCaptureForegroundException.java | 7 +++ .../com/genymobile/scrcpy/AudioEncoder.java | 51 ++++++++++++++-- .../scrcpy/wrappers/ActivityManager.java | 60 +++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioCaptureForegroundException.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCaptureForegroundException.java b/server/src/main/java/com/genymobile/scrcpy/AudioCaptureForegroundException.java new file mode 100644 index 00000000..baa7d846 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCaptureForegroundException.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy; + +/** + * Exception thrown if audio capture failed on Android 11 specifically because the running App (shell) was not in foreground. + */ +public class AudioCaptureForegroundException extends Exception { +} diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 1ce4107f..cc786bdb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -1,7 +1,11 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ServiceManager; + import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Intent; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTimestamp; @@ -12,6 +16,7 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; +import android.os.SystemClock; import java.io.IOException; import java.nio.ByteBuffer; @@ -179,7 +184,7 @@ public final class AudioEncoder { thread = new Thread(() -> { try { encode(); - } catch (ConfigurationException e) { + } catch (ConfigurationException | AudioCaptureForegroundException e) { // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { Ln.e("Audio encoding error", e); @@ -218,8 +223,34 @@ public final class AudioEncoder { } } + private static void startWorkaroundAndroid11() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + // Android 11 requires Apps to be at foreground to record audio. + // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. + // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android + // shell ("com.android.shell"). + // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the + // foreground. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); + ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); + // Wait for activity to start + SystemClock.sleep(150); + } + } + } + + private static void stopWorkaroundAndroid11() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); + } + } + @TargetApi(Build.VERSION_CODES.M) - public void encode() throws IOException, ConfigurationException { + public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { Ln.w("Audio disabled: it is not supported before Android 11"); streamer.writeDisableStream(false); @@ -242,8 +273,20 @@ public final class AudioEncoder { mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); - recorder = createAudioRecord(); - recorder.startRecording(); + startWorkaroundAndroid11(); + try { + recorder = createAudioRecord(); + recorder.startRecording(); + } catch (UnsupportedOperationException e) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Ln.e("Failed to start audio capture"); + Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy."); + throw new AudioCaptureForegroundException(); + } + throw e; + } finally { + stopWorkaroundAndroid11(); + } recorderStarted = true; final MediaCodec mediaCodecRef = mediaCodec; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 76aab5f1..aaf83d66 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -3,7 +3,12 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Ln; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Intent; import android.os.Binder; +import android.os.Build; +import android.os.Bundle; import android.os.IBinder; import android.os.IInterface; @@ -11,12 +16,15 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +@SuppressLint("PrivateApi,DiscouragedPrivateApi") public class ActivityManager { private final IInterface manager; private Method getContentProviderExternalMethod; private boolean getContentProviderExternalMethodNewVersion = true; private Method removeContentProviderExternalMethod; + private Method startActivityAsUserWithFeatureMethod; + private Method forceStopPackageMethod; public ActivityManager(IInterface manager) { this.manager = manager; @@ -43,6 +51,7 @@ public class ActivityManager { return removeContentProviderExternalMethod; } + @TargetApi(Build.VERSION_CODES.Q) private ContentProvider getContentProviderExternal(String name, IBinder token) { try { Method method = getGetContentProviderExternalMethod(); @@ -85,4 +94,55 @@ public class ActivityManager { public ContentProvider createSettingsProvider() { return getContentProviderExternal("settings", new Binder()); } + + private Method getStartActivityAsUserWithFeatureMethod() throws NoSuchMethodException, ClassNotFoundException { + if (startActivityAsUserWithFeatureMethod == null) { + Class iApplicationThreadClass = Class.forName("android.app.IApplicationThread"); + Class profilerInfo = Class.forName("android.app.ProfilerInfo"); + startActivityAsUserWithFeatureMethod = manager.getClass() + .getMethod("startActivityAsUserWithFeature", iApplicationThreadClass, String.class, String.class, Intent.class, String.class, + IBinder.class, String.class, int.class, int.class, profilerInfo, Bundle.class, int.class); + } + return startActivityAsUserWithFeatureMethod; + } + + @SuppressWarnings("ConstantConditions") + public int startActivityAsUserWithFeature(Intent intent) { + try { + Method method = getStartActivityAsUserWithFeatureMethod(); + return (int) method.invoke( + /* this */ manager, + /* caller */ null, + /* callingPackage */ FakeContext.PACKAGE_NAME, + /* callingFeatureId */ null, + /* intent */ intent, + /* resolvedType */ null, + /* resultTo */ null, + /* resultWho */ null, + /* requestCode */ 0, + /* startFlags */ 0, + /* profilerInfo */ null, + /* bOptions */ null, + /* userId */ /* UserHandle.USER_CURRENT */ -2); + } catch (Throwable e) { + Ln.e("Could not invoke method", e); + return 0; + } + } + + private Method getForceStopPackageMethod() throws NoSuchMethodException { + if (forceStopPackageMethod == null) { + forceStopPackageMethod = manager.getClass().getMethod("forceStopPackage", String.class, int.class); + } + return forceStopPackageMethod; + } + + public void forceStopPackage(String packageName) { + try { + Method method = getForceStopPackageMethod(); + method.invoke(manager, packageName, /* userId */ /* UserHandle.USER_CURRENT */ -2); + } catch (Throwable e) { + Ln.e("Could not invoke method", e); + } + } } From c1528cdca92d72d98f8f0d1b5cd088a1dd79467c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 21:19:43 +0100 Subject: [PATCH 0749/1133] Add --require-audio By default, scrcpy mirrors only the video when audio capture fails on the device. Add an option to force scrcpy to fail if audio is enabled but does not work. PR #3757 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 11 +++++++++++ app/src/demuxer.c | 9 ++++----- app/src/demuxer.h | 9 ++++++++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 30 +++++++++++++++--------------- 9 files changed, 46 insertions(+), 21 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index fa95ce6e..74c3ee57 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -45,6 +45,7 @@ _scrcpy() { -r --record= --record-format= --render-driver= + --require-audio --rotation= -s --serial= --shortcut-mod= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 231405ce..b28201a4 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -51,6 +51,7 @@ arguments=( {-r,--record=}'[Record screen to file]:record file:_files' '--record-format=[Force recording format]:format:(mp4 mkv)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' + '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 40b8158c..91258414 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -272,6 +272,10 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UE +.TP +.B \-\-require\-audio +By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work. + .TP .BI "\-\-rotation " value Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise. diff --git a/app/src/cli.c b/app/src/cli.c index 8dfcdc79..18f3b83b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -70,6 +70,7 @@ enum { OPT_AUDIO_ENCODER, OPT_LIST_ENCODERS, OPT_LIST_DISPLAYS, + OPT_REQUIRE_AUDIO, }; struct sc_option { @@ -465,6 +466,13 @@ static const struct sc_option options[] = { .longopt_id = OPT_RENDER_EXPIRED_FRAMES, .longopt = "render-expired-frames", }, + { + .longopt_id = OPT_REQUIRE_AUDIO, + .longopt = "require-audio", + .text = "By default, scrcpy mirrors only the video when audio capture " + "fails on the device. This option makes scrcpy fail if audio " + "is enabled but does not work." + }, { .longopt_id = OPT_ROTATION, .longopt = "rotation", @@ -1811,6 +1819,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_LIST_DISPLAYS: opts->list_displays = true; break; + case OPT_REQUIRE_AUDIO: + opts->require_audio = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/demuxer.c b/app/src/demuxer.c index d80a5dda..5977a28a 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -176,14 +176,13 @@ run_demuxer(void *data) { struct sc_demuxer *demuxer = data; // Flag to report end-of-stream (i.e. device disconnected) - bool eos = false; + enum sc_demuxer_status status = SC_DEMUXER_STATUS_ERROR; uint32_t raw_codec_id; bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); if (!ok) { LOGE("Demuxer '%s': stream disabled due to connection error", demuxer->name); - eos = true; goto end; } @@ -191,7 +190,7 @@ run_demuxer(void *data) { LOGW("Demuxer '%s': stream explicitly disabled by the device", demuxer->name); sc_demuxer_disable_sinks(demuxer); - eos = true; + status = SC_DEMUXER_STATUS_DISABLED; goto end; } @@ -241,7 +240,7 @@ run_demuxer(void *data) { bool ok = sc_demuxer_recv_packet(demuxer, packet); if (!ok) { // end of stream - eos = true; + status = SC_DEMUXER_STATUS_EOS; break; } @@ -272,7 +271,7 @@ run_demuxer(void *data) { finally_close_sinks: sc_demuxer_close_sinks(demuxer); end: - demuxer->cbs->on_ended(demuxer, eos, demuxer->cbs_userdata); + demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata); return 0; } diff --git a/app/src/demuxer.h b/app/src/demuxer.h index 73166b41..d0e41add 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -27,8 +27,15 @@ struct sc_demuxer { void *cbs_userdata; }; +enum sc_demuxer_status { + SC_DEMUXER_STATUS_EOS, + SC_DEMUXER_STATUS_DISABLED, + SC_DEMUXER_STATUS_ERROR, +}; + struct sc_demuxer_callbacks { - void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata); + void (*on_ended)(struct sc_demuxer *demuxer, enum sc_demuxer_status, + void *userdata); }; // The name must be statically allocated (e.g. a string literal) diff --git a/app/src/options.c b/app/src/options.c index 8560b37b..5dd655ce 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -72,6 +72,7 @@ const struct scrcpy_options scrcpy_options_default = { .start_fps_counter = false, .power_on = true, .audio = true, + .require_audio = false, .list_encoders = false, .list_displays = false, }; diff --git a/app/src/options.h b/app/src/options.h index a15d51f8..5fcaf016 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -154,6 +154,7 @@ struct scrcpy_options { bool start_fps_counter; bool power_on; bool audio; + bool require_audio; bool list_encoders; bool list_displays; }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index eb70749a..4355d71b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -216,12 +216,15 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success, } static void -sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, - void *userdata) { +sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, + enum sc_demuxer_status status, void *userdata) { (void) demuxer; (void) userdata; - if (eos) { + // The device may not decide to disable the video + assert(status != SC_DEMUXER_STATUS_DISABLED); + + if (status == SC_DEMUXER_STATUS_EOS) { PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); } else { PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); @@ -229,20 +232,17 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, } static void -sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos, - void *userdata) { +sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, + enum sc_demuxer_status status, void *userdata) { (void) demuxer; - (void) userdata; - // Contrary to the video demuxer, keep mirroring if only the audio fails. - // 'eos' is true on end-of-stream, including when audio capture is not - // possible on the device (so that scrcpy continue to mirror video without - // failing). - // However, if an audio configuration failure occurs (for example the user - // explicitly selected an unknown audio encoder), 'eos' is false and scrcpy - // must exit. + const struct scrcpy_options *options = userdata; - if (!eos) { + // Contrary to the video demuxer, keep mirroring if only the audio fails + // (unless --require-audio is set). + if (status == SC_DEMUXER_STATUS_ERROR + || (status == SC_DEMUXER_STATUS_DISABLED + && options->require_audio)) { PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); } } @@ -434,7 +434,7 @@ scrcpy(struct scrcpy_options *options) { .on_ended = sc_audio_demuxer_on_ended, }; sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket, - &audio_demuxer_cbs, NULL); + &audio_demuxer_cbs, options); } bool needs_video_decoder = options->display; From 6e05d7047a9914bdba0bd833e909bc781d5b1a81 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 21:20:28 +0100 Subject: [PATCH 0750/1133] Call avcodec_receive_frame() in a loop Since in scrcpy a video packet passed to avcodec_send_packet() is always a complete video frame, it is sufficient to call avcodec_receive_frame() exactly once. In practice, it also works for audio packets: the decoder produces exactly 1 frame for 1 input packet. In theory, it is an implementation detail though, so avcodec_receive_frame() should be called in a loop. --- app/src/decoder.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index 96d4a010..e4d59628 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -110,8 +110,19 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { decoder->name, ret); return false; } - ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); - if (!ret) { + + for (;;) { + ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + + if (ret) { + LOGE("Decoder '%s', could not receive video frame: %d", + decoder->name, ret); + return false; + } + // a frame was received bool ok = push_frame_to_sinks(decoder, decoder->frame); // A frame lost should not make the whole pipeline fail. The error, if @@ -119,11 +130,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { (void) ok; av_frame_unref(decoder->frame); - } else if (ret != AVERROR(EAGAIN)) { - LOGE("Decoder '%s', could not receive video frame: %d", - decoder->name, ret); - return false; } + return true; } From 6dceb328173ed9dc9c7fcdfc250621b4c57ae415 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 21:43:19 +0100 Subject: [PATCH 0751/1133] Add compat for reallocarray() This function fails safely in the case where the multiplication would overflow. --- app/meson.build | 1 + app/src/compat.c | 13 +++++++++++++ app/src/compat.h | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/app/meson.build b/app/meson.build index 8be917e9..7bdd288d 100644 --- a/app/meson.build +++ b/app/meson.build @@ -169,6 +169,7 @@ check_functions = [ 'vasprintf', 'nrand48', 'jrand48', + 'reallocarray', ] foreach f : check_functions diff --git a/app/src/compat.c b/app/src/compat.c index bb0152aa..785f843c 100644 --- a/app/src/compat.c +++ b/app/src/compat.c @@ -3,6 +3,9 @@ #include "config.h" #include +#ifndef HAVE_REALLOCARRAY +# include +#endif #include #include #include @@ -93,5 +96,15 @@ long jrand48(unsigned short xsubi[3]) { return v.i; } #endif +#endif +#ifndef HAVE_REALLOCARRAY +void *reallocarray(void *ptr, size_t nmemb, size_t size) { + size_t bytes; + if (__builtin_mul_overflow(nmemb, size, &bytes)) { + errno = ENOMEM; + return NULL; + } + return realloc(ptr, bytes); +} #endif diff --git a/app/src/compat.h b/app/src/compat.h index 857623e6..ea44437d 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -67,4 +67,8 @@ long nrand48(unsigned short xsubi[3]); long jrand48(unsigned short xsubi[3]); #endif +#ifndef HAVE_REALLOCARRAY +void *reallocarray(void *ptr, size_t nmemb, size_t size); +#endif + #endif From c735b8c127bba489df8d45e397fc47089ae9f00b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 21:49:44 +0100 Subject: [PATCH 0752/1133] Use reallocarray() in sc_vector This fails safely in case of overflow. --- app/src/util/vector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/util/vector.h b/app/src/util/vector.h index 0c6cab98..97d7c389 100644 --- a/app/src/util/vector.h +++ b/app/src/util/vector.h @@ -118,7 +118,7 @@ static inline void * sc_vector_reallocdata_(void *ptr, size_t count, size_t size, size_t *restrict pcap, size_t *restrict psize) { - void *p = realloc(ptr, count * size); + void *p = reallocarray(ptr, count, size); if (!p) { return NULL; } From 457385d5f468f87c31b44ad8d0fec1cd743c627c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 21:48:18 +0100 Subject: [PATCH 0753/1133] Add sc_allocarray() util Add a function to allocate an array, which fails safely in the case where the multiplication would overflow. --- app/meson.build | 1 + app/src/util/memory.c | 14 ++++++++++++++ app/src/util/memory.h | 15 +++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 app/src/util/memory.c create mode 100644 app/src/util/memory.h diff --git a/app/meson.build b/app/meson.build index 7bdd288d..c24a17de 100644 --- a/app/meson.build +++ b/app/meson.build @@ -35,6 +35,7 @@ src = [ 'src/util/intmap.c', 'src/util/intr.c', 'src/util/log.c', + 'src/util/memory.c', 'src/util/net.c', 'src/util/net_intr.c', 'src/util/process.c', diff --git a/app/src/util/memory.c b/app/src/util/memory.c new file mode 100644 index 00000000..64ee616e --- /dev/null +++ b/app/src/util/memory.c @@ -0,0 +1,14 @@ +#include "memory.h" + +#include +#include + +void * +sc_allocarray(size_t nmemb, size_t size) { + size_t bytes; + if (__builtin_mul_overflow(nmemb, size, &bytes)) { + errno = ENOMEM; + return NULL; + } + return malloc(bytes); +} diff --git a/app/src/util/memory.h b/app/src/util/memory.h new file mode 100644 index 00000000..0fb6bc64 --- /dev/null +++ b/app/src/util/memory.h @@ -0,0 +1,15 @@ +#ifndef SC_MEMORY_H +#define SC_MEMORY_H + +#include + +/** + * Allocate an array of `nmemb` items of `size` bytes each + * + * Like calloc(), but without initialization. + * Like reallocarray(), but without reallocation. + */ +void * +sc_allocarray(size_t nmemb, size_t size); + +#endif From 33df484912f8ac4af759a1cf865a2cebbdecbf34 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Feb 2023 22:56:37 +0100 Subject: [PATCH 0754/1133] Introduce VecDeque Introduce a double-ended queue implemented with a growable ring buffer. Inspired from the Rust VecDeque type: --- app/meson.build | 4 + app/src/util/vecdeque.h | 379 ++++++++++++++++++++++++++++++++++++++ app/tests/test_vecdeque.c | 197 ++++++++++++++++++++ 3 files changed, 580 insertions(+) create mode 100644 app/src/util/vecdeque.h create mode 100644 app/tests/test_vecdeque.c diff --git a/app/meson.build b/app/meson.build index c24a17de..a238eb8f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -300,6 +300,10 @@ if get_option('buildtype') == 'debug' 'src/util/str.c', 'src/util/strbuf.c', ]], + ['test_vecdeque', [ + 'tests/test_vecdeque.c', + 'src/util/memory.c', + ]], ['test_vector', [ 'tests/test_vector.c', ]], diff --git a/app/src/util/vecdeque.h b/app/src/util/vecdeque.h new file mode 100644 index 00000000..e5372e02 --- /dev/null +++ b/app/src/util/vecdeque.h @@ -0,0 +1,379 @@ +#ifndef SC_VECDEQUE_H +#define SC_VECDEQUE_H + +#include "common.h" + +#include +#include +#include +#include +#include + +#include "util/memory.h" + +/** + * A double-ended queue implemented with a growable ring buffer. + * + * Inspired from the Rust VecDeque type: + * + */ + +/** + * VecDeque struct body + * + * A VecDeque is a dynamic ring-buffer, managed by the sc_vecdeque_* helpers. + * + * It is generic over the type of its items, so it is implemented via macros. + * + * To use a VecDeque, a new type must be defined: + * + * struct vecdeque_int SC_VECDEQUE(int); + * + * The struct may be anonymous: + * + * struct SC_VECDEQUE(const char *) names; + * + * Functions and macros having name ending with '_' are private. + */ +#define SC_VECDEQUE(type) { \ + size_t cap; \ + size_t origin; \ + size_t size; \ + type *data; \ +} + +/** + * Static initializer for a VecDeque + */ +#define SC_VECDEQUE_INITIALIZER { 0, 0, 0, NULL } + +/** + * Initialize an empty VecDeque + */ +#define sc_vecdeque_init(pv) \ +({ \ + (pv)->cap = 0; \ + (pv)->origin = 0; \ + (pv)->size = 0; \ + (pv)->data = NULL; \ +}) + +/** + * Destroy a VecDeque + */ +#define sc_vecdeque_destroy(pv) \ + free((pv)->data) + +/** + * Clear a VecDeque + * + * Remove all items. + */ +#define sc_vecdeque_clear(pv) \ +(void) ({ \ + sc_vecdeque_destroy(pv); \ + sc_vecdeque_init(pv); \ +}) + +/** + * Returns the content size + */ +#define sc_vecdeque_size(pv) \ + (pv)->size + +/** + * Return whether the VecDeque is empty (i.e. its size is 0) + */ +#define sc_vecdeque_is_empty(pv) \ + ((pv)->size == 0) + +/** + * Return whether the VecDeque is full + * + * A VecDeque is full when its size equals its current capacity. However, it + * does not prevent to push a new item (with sc_vecdeque_push()), since this + * will increase its capacity. + */ +#define sc_vecdeque_is_full(pv) \ + ((pv)->size == (pv)->cap) + +/** + * The minimal allocation size, in number of items + * + * Private. + */ +#define SC_VECDEQUE_MINCAP_ ((size_t) 10) + +/** + * The maximal allocation size, in number of items + * + * Use SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow. + * + * Private. + */ +#define sc_vecdeque_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data)) + +/** + * Realloc the internal array to a specific capacity + * + * On reallocation success, update the VecDeque capacity (`*pcap`) and origin + * (`*porigin`), and return the reallocated data. + * + * On reallocation failure, return NULL without any change. + * + * Private. + * + * \param ptr the current `data` field of the SC_VECDEQUE to realloc + * \param newcap the requested capacity, in number of items + * \param item_size the size of one item (the generic type is unknown from this + * function) + * \param pcap a pointer to the `cap` field of the SC_VECDEQUE [IN/OUT] + * \param porigin a pointer to pv->origin [IN/OUT] + * \param size the `size` field of the SC_VECDEQUE + * \return the new array to assign to the `data` field of the SC_VECDEQUE (if + * not NULL) + */ +static inline void * +sc_vecdeque_reallocdata_(void *ptr, size_t newcap, size_t item_size, + size_t *pcap, size_t *porigin, size_t size) { + + size_t oldcap = *pcap; + size_t oldorigin = *porigin; + + assert(newcap > oldcap); // Could only grow + + if (oldorigin + size <= oldcap) { + // The current content will stay in place, just realloc + // + // As an example, here is the content of a ring-buffer (oldcap=10) + // before the realloc: + // + // _ _ 2 3 4 5 6 7 _ _ + // ^ + // origin + // + // It is resized (newcap=15), e.g. with sc_vecdeque_reserve(): + // + // _ _ 2 3 4 5 6 7 _ _ _ _ _ _ _ + // ^ + // origin + + void *newptr = reallocarray(ptr, newcap, item_size); + if (!newptr) { + return NULL; + } + + *pcap = newcap; + return newptr; + } + + // Copy the current content to the new array + // + // As an example, here is the content of a ring-buffer (oldcap=10) before + // the realloc: + // + // 5 6 7 _ _ 0 1 2 3 4 + // ^ + // origin + // + // It is resized (newcap=15), e.g. with sc_vecdeque_reserve(): + // + // 0 1 2 3 4 5 6 7 _ _ _ _ _ _ _ + // ^ + // origin + + assert(size); + void *newptr = sc_allocarray(newcap, item_size); + if (!newptr) { + return NULL; + } + + size_t right_len = MIN(size, oldcap - oldorigin); + assert(right_len); + memcpy(newptr, ptr + (oldorigin * item_size), right_len * item_size); + + if (size > right_len) { + memcpy(newptr + (right_len * item_size), ptr, + (size - right_len) * item_size); + } + + free(ptr); + + *pcap = newcap; + *porigin = 0; + return newptr; +} + +/** + * Macro to realloc the internal data to a new capacity + * + * Private. + * + * \retval true on success + * \retval false on allocation failure (the VecDeque is left untouched) + */ +#define sc_vecdeque_realloc_(pv, newcap) \ +({ \ + void *p = sc_vecdeque_reallocdata_((pv)->data, newcap, \ + sizeof(*(pv)->data), &(pv)->cap, \ + &(pv)->origin, (pv)->size); \ + if (p) { \ + (pv)->data = p; \ + } \ + (bool) p; \ +}); + +static inline size_t +sc_vecdeque_growsize_(size_t value) +{ + /* integer multiplication by 1.5 */ + return value + (value >> 1); +} + +/** + * Increase the capacity of the VecDeque to at least `mincap` + * + * \param pv a pointer to the VecDeque + * \param mincap (`size_t`) the requested capacity + * \retval true on success + * \retval false on allocation failure (the VecDeque is left untouched) + */ +#define sc_vecdeque_reserve(pv, mincap) \ +({ \ + assert(mincap <= sc_vecdeque_max_cap_(pv)); \ + bool ok; \ + /* avoid to allocate tiny arrays (< SC_VECDEQUE_MINCAP_) */ \ + size_t mincap_ = MAX(mincap, SC_VECDEQUE_MINCAP_); \ + if (mincap_ <= (pv)->cap) { \ + /* nothing to do */ \ + ok = true; \ + } else if (mincap_ <= sc_vecdeque_max_cap_(pv)) { \ + /* not too big */ \ + size_t newsize = sc_vecdeque_growsize_((pv)->cap); \ + newsize = CLAMP(newsize, mincap_, sc_vecdeque_max_cap_(pv)); \ + ok = sc_vecdeque_realloc_(pv, newsize); \ + } else { \ + ok = false; \ + } \ + ok; \ +}) + +/** + * Automatically grow the VecDeque capacity + * + * Private. + * + * \retval true on success + * \retval false on allocation failure (the VecDeque is left untouched) + */ +#define sc_vecdeque_grow_(pv) \ +({ \ + bool ok; \ + if ((pv)->cap < sc_vecdeque_max_cap_(pv)) { \ + size_t newsize = sc_vecdeque_growsize_((pv)->cap); \ + newsize = CLAMP(newsize, SC_VECDEQUE_MINCAP_, \ + sc_vecdeque_max_cap_(pv)); \ + ok = sc_vecdeque_realloc_(pv, newsize); \ + } else { \ + ok = false; \ + } \ + ok; \ +}) + +/** + * Grow the VecDeque capacity if it is full + * + * Private. + * + * \retval true on success + * \retval false on allocation failure (the VecDeque is left untouched) + */ +#define sc_vecdeque_grow_if_needed_(pv) \ + (!sc_vecdeque_is_full(pv) || sc_vecdeque_grow_(pv)) + +/** + * Push an uninitialized item, and return a pointer to it + * + * It does not attempt to resize the VecDeque. It is an error to this function + * if the VecDeque is full. + * + * This function may not fail. It returns a valid non-NULL pointer to the + * uninitialized item just pushed. + */ +#define sc_vecdeque_push_hole_noresize(pv) \ +({ \ + assert(!sc_vecdeque_is_full(pv)); \ + ++(pv)->size; \ + &(pv)->data[((pv)->origin + (pv)->size - 1) % (pv)->cap]; \ +}) + +/** + * Push an uninitialized item, and return a pointer to it + * + * If the VecDeque is full, it is resized. + * + * This function returns either a valid non-NULL pointer to the uninitialized + * item just pushed, or NULL on reallocation failure. + */ +#define sc_vecdeque_push_hole(pv) \ + (sc_vecdeque_grow_if_needed_(pv) ? \ + sc_vecdeque_push_hole_noresize(pv) : NULL) + +/** + * Push an item + * + * It does not attempt to resize the VecDeque. It is an error to this function + * if the VecDeque is full. + * + * This function may not fail. + */ +#define sc_vecdeque_push_noresize(pv, item) \ +(void) ({ \ + assert(!sc_vecdeque_is_full(pv)); \ + ++(pv)->size; \ + (pv)->data[((pv)->origin + (pv)->size - 1) % (pv)->cap] = item; \ +}) + +/** + * Push an item + * + * If the VecDeque is full, it is resized. + * + * \retval true on success + * \retval false on allocation failure (the VecDeque is left untouched) + */ +#define sc_vecdeque_push(pv, item) \ +({ \ + bool ok = sc_vecdeque_grow_if_needed_(pv); \ + if (ok) { \ + sc_vecdeque_push_noresize(pv, item); \ + } \ + ok; \ +}) + +/** + * Pop an item and return a pointer to it (still in the VecDeque) + * + * Returning a pointer allows the caller to destroy it in place without copy + * (especially if the item type is big). + * + * It is an error to call this function if the VecDeque is empty. + */ +#define sc_vecdeque_popref(pv) \ +({ \ + assert(!sc_vecdeque_is_empty(pv)); \ + size_t pos = (pv)->origin; \ + (pv)->origin = ((pv)->origin + 1) % (pv)->cap; \ + --(pv)->size; \ + &(pv)->data[pos]; \ +}) + +/** + * Pop an item and return it + * + * It is an error to call this function if the VecDeque is empty. + */ +#define sc_vecdeque_pop(pv) \ + (*sc_vecdeque_popref(pv)) + +#endif diff --git a/app/tests/test_vecdeque.c b/app/tests/test_vecdeque.c new file mode 100644 index 00000000..fa3ba963 --- /dev/null +++ b/app/tests/test_vecdeque.c @@ -0,0 +1,197 @@ +#include "common.h" + +#include + +#include "util/vecdeque.h" + +#define pr(pv) \ +({ \ + fprintf(stderr, "cap=%lu origin=%lu size=%lu\n", (pv)->cap, (pv)->origin, (pv)->size); \ + for (size_t i = 0; i < (pv)->cap; ++i) \ + fprintf(stderr, "%d ", (pv)->data[i]); \ + fprintf(stderr, "\n"); \ +}) + +static void test_vecdeque_push_pop(void) { + struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; + + assert(sc_vecdeque_is_empty(&vdq)); + assert(sc_vecdeque_size(&vdq) == 0); + + bool ok = sc_vecdeque_push(&vdq, 5); + assert(ok); + assert(sc_vecdeque_size(&vdq) == 1); + + ok = sc_vecdeque_push(&vdq, 12); + assert(ok); + assert(sc_vecdeque_size(&vdq) == 2); + + int v = sc_vecdeque_pop(&vdq); + assert(v == 5); + assert(sc_vecdeque_size(&vdq) == 1); + + ok = sc_vecdeque_push(&vdq, 7); + assert(ok); + assert(sc_vecdeque_size(&vdq) == 2); + + int *p = sc_vecdeque_popref(&vdq); + assert(p); + assert(*p == 12); + assert(sc_vecdeque_size(&vdq) == 1); + + v = sc_vecdeque_pop(&vdq); + assert(v == 7); + assert(sc_vecdeque_size(&vdq) == 0); + assert(sc_vecdeque_is_empty(&vdq)); + + sc_vecdeque_destroy(&vdq); +} + +static void test_vecdeque_reserve(void) { + struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; + + bool ok = sc_vecdeque_reserve(&vdq, 20); + assert(ok); + assert(vdq.cap == 20); + + assert(sc_vecdeque_size(&vdq) == 0); + + for (size_t i = 0; i < 20; ++i) { + ok = sc_vecdeque_push(&vdq, i); + assert(ok); + } + + assert(sc_vecdeque_size(&vdq) == 20); + + // It is now full + + for (int i = 0; i < 5; ++i) { + int v = sc_vecdeque_pop(&vdq); + assert(v == i); + } + assert(sc_vecdeque_size(&vdq) == 15); + + for (int i = 20; i < 25; ++i) { + ok = sc_vecdeque_push(&vdq, i); + assert(ok); + } + + assert(sc_vecdeque_size(&vdq) == 20); + assert(vdq.cap == 20); + + // Now, the content wraps around the ring buffer: + // 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + // ^ + // origin + + // It is now full, let's reserve some space + ok = sc_vecdeque_reserve(&vdq, 30); + assert(ok); + assert(vdq.cap == 30); + + assert(sc_vecdeque_size(&vdq) == 20); + + for (int i = 0; i < 20; ++i) { + // We should retrieve the items we inserted in order + int v = sc_vecdeque_pop(&vdq); + assert(v == i + 5); + } + + assert(sc_vecdeque_size(&vdq) == 0); + + sc_vecdeque_destroy(&vdq); +} + +static void test_vecdeque_grow() { + struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; + + bool ok = sc_vecdeque_reserve(&vdq, 20); + assert(ok); + assert(vdq.cap == 20); + + assert(sc_vecdeque_size(&vdq) == 0); + + for (int i = 0; i < 500; ++i) { + ok = sc_vecdeque_push(&vdq, i); + assert(ok); + } + + assert(sc_vecdeque_size(&vdq) == 500); + + for (int i = 0; i < 100; ++i) { + int v = sc_vecdeque_pop(&vdq); + assert(v == i); + } + + assert(sc_vecdeque_size(&vdq) == 400); + + for (int i = 500; i < 1000; ++i) { + ok = sc_vecdeque_push(&vdq, i); + assert(ok); + } + + assert(sc_vecdeque_size(&vdq) == 900); + + for (int i = 100; i < 1000; ++i) { + int v = sc_vecdeque_pop(&vdq); + assert(v == i); + } + + assert(sc_vecdeque_size(&vdq) == 0); + + sc_vecdeque_destroy(&vdq); +} + +static void test_vecdeque_push_hole() { + struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; + + bool ok = sc_vecdeque_reserve(&vdq, 20); + assert(ok); + assert(vdq.cap == 20); + + assert(sc_vecdeque_size(&vdq) == 0); + + for (int i = 0; i < 20; ++i) { + int *p = sc_vecdeque_push_hole(&vdq); + assert(p); + *p = i * 10; + } + + assert(sc_vecdeque_size(&vdq) == 20); + + for (int i = 0; i < 10; ++i) { + int v = sc_vecdeque_pop(&vdq); + assert(v == i * 10); + } + + assert(sc_vecdeque_size(&vdq) == 10); + + for (int i = 20; i < 30; ++i) { + int *p = sc_vecdeque_push_hole(&vdq); + assert(p); + *p = i * 10; + } + + assert(sc_vecdeque_size(&vdq) == 20); + + for (int i = 10; i < 30; ++i) { + int v = sc_vecdeque_pop(&vdq); + assert(v == i * 10); + } + + assert(sc_vecdeque_size(&vdq) == 0); + + sc_vecdeque_destroy(&vdq); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_vecdeque_push_pop(); + test_vecdeque_reserve(); + test_vecdeque_grow(); + test_vecdeque_push_hole(); + + return 0; +} From efc15744da576206f407099927bceea8421b79bb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 21:42:51 +0100 Subject: [PATCH 0755/1133] Use VecDeque in recorder The packets queued for recording were wrapped in a dynamically allocated structure with a "next" field. To avoid this additional layer of allocation and indirection, use a VecDeque. --- app/src/recorder.c | 169 ++++++++++++++++++++++----------------------- app/src/recorder.h | 9 +-- 2 files changed, 84 insertions(+), 94 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 9fc15dac..bd7c50f2 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -33,41 +33,27 @@ find_muxer(const char *name) { return oformat; } -static struct sc_record_packet * -sc_record_packet_new(const AVPacket *packet) { - struct sc_record_packet *rec = malloc(sizeof(*rec)); - if (!rec) { - LOG_OOM(); - return NULL; - } - - rec->packet = av_packet_alloc(); - if (!rec->packet) { +static AVPacket * +sc_recorder_packet_ref(const AVPacket *packet) { + AVPacket *p = av_packet_alloc(); + if (!p) { LOG_OOM(); - free(rec); return NULL; } - if (av_packet_ref(rec->packet, packet)) { - av_packet_free(&rec->packet); - free(rec); + if (av_packet_ref(p, packet)) { + av_packet_free(&p); return NULL; } - return rec; -} -static void -sc_record_packet_delete(struct sc_record_packet *rec) { - av_packet_free(&rec->packet); - free(rec); + return p; } static void sc_recorder_queue_clear(struct sc_recorder_queue *queue) { - while (!sc_queue_is_empty(queue)) { - struct sc_record_packet *rec; - sc_queue_take(queue, next, &rec); - sc_record_packet_delete(rec); + while (!sc_vecdeque_is_empty(queue)) { + AVPacket *p = sc_vecdeque_pop(queue); + av_packet_free(&p); } } @@ -227,12 +213,12 @@ sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { static inline bool sc_recorder_has_empty_queues(struct sc_recorder *recorder) { - if (sc_queue_is_empty(&recorder->video_queue)) { + if (sc_vecdeque_is_empty(&recorder->video_queue)) { // The video queue is empty return true; } - if (recorder->audio && sc_queue_is_empty(&recorder->audio_queue)) { + if (recorder->audio && sc_vecdeque_is_empty(&recorder->audio_queue)) { // The audio queue is empty (when audio is enabled) return true; } @@ -249,28 +235,27 @@ sc_recorder_process_header(struct sc_recorder *recorder) { sc_cond_wait(&recorder->queue_cond, &recorder->mutex); } - if (sc_queue_is_empty(&recorder->video_queue)) { + if (sc_vecdeque_is_empty(&recorder->video_queue)) { assert(recorder->stopped); - // Don't process anything if there are not at least video packets (when - // the recorder is stopped) + // If the recorder is stopped, don't process anything if there are not + // at least video packets sc_mutex_unlock(&recorder->mutex); return false; } - struct sc_record_packet *video_pkt; - sc_queue_take(&recorder->video_queue, next, &video_pkt); + AVPacket *video_pkt = sc_vecdeque_pop(&recorder->video_queue); - struct sc_record_packet *audio_pkt = NULL; - if (!sc_queue_is_empty(&recorder->audio_queue)) { + AVPacket *audio_pkt = NULL; + if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { assert(recorder->audio); - sc_queue_take(&recorder->audio_queue, next, &audio_pkt); + audio_pkt = sc_vecdeque_pop(&recorder->audio_queue); } sc_mutex_unlock(&recorder->mutex); int ret = false; - if (video_pkt->packet->pts != AV_NOPTS_VALUE) { + if (video_pkt->pts != AV_NOPTS_VALUE) { LOGE("The first video packet is not a config packet"); goto end; } @@ -278,13 +263,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) { assert(recorder->video_stream_index >= 0); AVStream *video_stream = recorder->ctx->streams[recorder->video_stream_index]; - bool ok = sc_recorder_set_extradata(video_stream, video_pkt->packet); + bool ok = sc_recorder_set_extradata(video_stream, video_pkt); if (!ok) { goto end; } if (audio_pkt) { - if (audio_pkt->packet->pts != AV_NOPTS_VALUE) { + if (audio_pkt->pts != AV_NOPTS_VALUE) { LOGE("The first audio packet is not a config packet"); goto end; } @@ -292,7 +277,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { assert(recorder->audio_stream_index >= 0); AVStream *audio_stream = recorder->ctx->streams[recorder->audio_stream_index]; - ok = sc_recorder_set_extradata(audio_stream, audio_pkt->packet); + ok = sc_recorder_set_extradata(audio_stream, audio_pkt); if (!ok) { goto end; } @@ -307,9 +292,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { ret = true; end: - sc_record_packet_delete(video_pkt); + av_packet_free(&video_pkt); if (audio_pkt) { - sc_record_packet_delete(audio_pkt); + av_packet_free(&audio_pkt); } return ret; @@ -324,12 +309,12 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { return false; } - struct sc_record_packet *video_pkt = NULL; - struct sc_record_packet *audio_pkt = NULL; + AVPacket *video_pkt = NULL; + AVPacket *audio_pkt = NULL; // We can write a video packet only once we received the next one so that // we can set its duration (next_pts - current_pts) - struct sc_record_packet *video_pkt_previous = NULL; + AVPacket *video_pkt_previous = NULL; bool error = false; @@ -337,12 +322,12 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); while (!recorder->stopped) { - if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) { + if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) { // A new packet may be assigned to video_pkt and be processed break; } if (recorder->audio && !audio_pkt - && !sc_queue_is_empty(&recorder->audio_queue)) { + && !sc_vecdeque_is_empty(&recorder->audio_queue)) { // A new packet may be assigned to audio_pkt and be processed break; } @@ -354,20 +339,20 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // If there is no audio, then the audio_queue will remain empty forever // and audio_pkt will always be NULL. - assert(recorder->audio - || (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue))); + assert(recorder->audio || (!audio_pkt + && sc_vecdeque_is_empty(&recorder->audio_queue))); - if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) { - sc_queue_take(&recorder->video_queue, next, &video_pkt); + if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) { + video_pkt = sc_vecdeque_pop(&recorder->video_queue); } - if (!audio_pkt && !sc_queue_is_empty(&recorder->audio_queue)) { - sc_queue_take(&recorder->audio_queue, next, &audio_pkt); + if (!audio_pkt && !sc_vecdeque_is_empty(&recorder->audio_queue)) { + audio_pkt = sc_vecdeque_pop(&recorder->audio_queue); } if (recorder->stopped && !video_pkt && !audio_pkt) { - assert(sc_queue_is_empty(&recorder->video_queue)); - assert(sc_queue_is_empty(&recorder->audio_queue)); + assert(sc_vecdeque_is_empty(&recorder->video_queue)); + assert(sc_vecdeque_is_empty(&recorder->audio_queue)); sc_mutex_unlock(&recorder->mutex); break; } @@ -379,28 +364,27 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // Ignore further config packets (e.g. on device orientation // change). The next non-config packet will have the config packet // data prepended. - if (video_pkt && video_pkt->packet->pts == AV_NOPTS_VALUE) { - sc_record_packet_delete(video_pkt); + if (video_pkt && video_pkt->pts == AV_NOPTS_VALUE) { + av_packet_free(&video_pkt); video_pkt = NULL; } - if (audio_pkt && audio_pkt->packet->pts == AV_NOPTS_VALUE) { - sc_record_packet_delete(audio_pkt); - audio_pkt= NULL; + if (audio_pkt && audio_pkt->pts == AV_NOPTS_VALUE) { + av_packet_free(&audio_pkt); + audio_pkt = NULL; } if (pts_origin == AV_NOPTS_VALUE) { if (!recorder->audio) { assert(video_pkt); - pts_origin = video_pkt->packet->pts; + pts_origin = video_pkt->pts; } else if (video_pkt && audio_pkt) { - pts_origin = - MIN(video_pkt->packet->pts, audio_pkt->packet->pts); + pts_origin = MIN(video_pkt->pts, audio_pkt->pts); } else if (recorder->stopped) { if (video_pkt) { // The recorder is stopped without audio, record the video // packets - pts_origin = video_pkt->packet->pts; + pts_origin = video_pkt->pts; } else { // Fail if there is no video error = true; @@ -415,17 +399,16 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { assert(pts_origin != AV_NOPTS_VALUE); if (video_pkt) { - video_pkt->packet->pts -= pts_origin; - video_pkt->packet->dts = video_pkt->packet->pts; + video_pkt->pts -= pts_origin; + video_pkt->dts = video_pkt->pts; if (video_pkt_previous) { // we now know the duration of the previous packet - video_pkt_previous->packet->duration = - video_pkt->packet->pts - video_pkt_previous->packet->pts; + video_pkt_previous->duration = video_pkt->pts + - video_pkt_previous->pts; - bool ok = sc_recorder_write_video(recorder, - video_pkt_previous->packet); - sc_record_packet_delete(video_pkt_previous); + bool ok = sc_recorder_write_video(recorder, video_pkt_previous); + av_packet_free(&video_pkt_previous); if (!ok) { LOGE("Could not record video packet"); error = true; @@ -438,34 +421,34 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { } if (audio_pkt) { - audio_pkt->packet->pts -= pts_origin; - audio_pkt->packet->dts = audio_pkt->packet->pts; + audio_pkt->pts -= pts_origin; + audio_pkt->dts = audio_pkt->pts; - bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet); + bool ok = sc_recorder_write_audio(recorder, audio_pkt); if (!ok) { LOGE("Could not record audio packet"); error = true; goto end; } - sc_record_packet_delete(audio_pkt); + av_packet_free(&audio_pkt); audio_pkt = NULL; } } // Write the last video packet - struct sc_record_packet *last = video_pkt_previous; + AVPacket *last = video_pkt_previous; if (last) { // assign an arbitrary duration to the last packet - last->packet->duration = 100000; - bool ok = sc_recorder_write_video(recorder, last->packet); + last->duration = 100000; + bool ok = sc_recorder_write_video(recorder, last); if (!ok) { // failing to write the last frame is not very serious, no // future frame may depend on it, so the resulting file // will still be valid LOGW("Could not record last packet"); } - sc_record_packet_delete(last); + av_packet_free(&last); } int ret = av_write_trailer(recorder->ctx); @@ -476,10 +459,10 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { end: if (video_pkt) { - sc_record_packet_delete(video_pkt); + av_packet_free(&video_pkt); } if (audio_pkt) { - sc_record_packet_delete(audio_pkt); + av_packet_free(&audio_pkt); } return !error; @@ -585,16 +568,22 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, return false; } - struct sc_record_packet *rec = sc_record_packet_new(packet); + AVPacket *rec = sc_recorder_packet_ref(packet); if (!rec) { LOG_OOM(); sc_mutex_unlock(&recorder->mutex); return false; } - rec->packet->stream_index = recorder->video_stream_index; + rec->stream_index = recorder->video_stream_index; + + bool ok = sc_vecdeque_push(&recorder->video_queue, rec); + if (!ok) { + LOG_OOM(); + sc_mutex_unlock(&recorder->mutex); + return false; + } - sc_queue_push(&recorder->video_queue, next, rec); sc_cond_signal(&recorder->queue_cond); sc_mutex_unlock(&recorder->mutex); @@ -648,16 +637,22 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, return false; } - struct sc_record_packet *rec = sc_record_packet_new(packet); + AVPacket *rec = sc_recorder_packet_ref(packet); if (!rec) { LOG_OOM(); sc_mutex_unlock(&recorder->mutex); return false; } - rec->packet->stream_index = recorder->audio_stream_index; + rec->stream_index = recorder->audio_stream_index; + + bool ok = sc_vecdeque_push(&recorder->audio_queue, rec); + if (!ok) { + LOG_OOM(); + sc_mutex_unlock(&recorder->mutex); + return false; + } - sc_queue_push(&recorder->audio_queue, next, rec); sc_cond_signal(&recorder->queue_cond); sc_mutex_unlock(&recorder->mutex); @@ -708,8 +703,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->audio = audio; - sc_queue_init(&recorder->video_queue); - sc_queue_init(&recorder->audio_queue); + sc_vecdeque_init(&recorder->video_queue); + sc_vecdeque_init(&recorder->audio_queue); recorder->stopped = false; recorder->video_codec = NULL; diff --git a/app/src/recorder.h b/app/src/recorder.h index 6fe72401..e3d5f018 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -9,15 +9,10 @@ #include "coords.h" #include "options.h" #include "trait/packet_sink.h" -#include "util/queue.h" #include "util/thread.h" +#include "util/vecdeque.h" -struct sc_record_packet { - AVPacket *packet; - struct sc_record_packet *next; -}; - -struct sc_recorder_queue SC_QUEUE(struct sc_record_packet); +struct sc_recorder_queue SC_VECDEQUE(AVPacket *); struct sc_recorder { struct sc_packet_sink video_packet_sink; From f25a67f3424406c9cbf33b7b11956ad7dcf2ddb3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 22:21:43 +0100 Subject: [PATCH 0756/1133] Use VecDeque in video_buffer The packets queued for buffering were wrapped in a dynamically allocated structure with a "next" field. To avoid this additional layer of allocation and indirection, use a VecDeque. --- app/src/video_buffer.c | 63 ++++++++++++++++++++---------------------- app/src/video_buffer.h | 5 ++-- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 11f76479..b3b29098 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -10,35 +10,26 @@ #define SC_BUFFERING_NDEBUG // comment to debug -static struct sc_video_buffer_frame * -sc_video_buffer_frame_new(const AVFrame *frame) { - struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame)); - if (!vb_frame) { - LOG_OOM(); - return NULL; - } - +static bool +sc_video_buffer_frame_init(struct sc_video_buffer_frame *vb_frame, + const AVFrame *frame) { vb_frame->frame = av_frame_alloc(); if (!vb_frame->frame) { - LOG_OOM(); - free(vb_frame); - return NULL; + return false; } if (av_frame_ref(vb_frame->frame, frame)) { av_frame_free(&vb_frame->frame); - free(vb_frame); - return NULL; + return false; } - return vb_frame; + return true; } static void -sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) { +sc_video_buffer_frame_destroy(struct sc_video_buffer_frame *vb_frame) { av_frame_unref(vb_frame->frame); av_frame_free(&vb_frame->frame); - free(vb_frame); } static bool @@ -62,7 +53,7 @@ run_buffering(void *data) { for (;;) { sc_mutex_lock(&vb->b.mutex); - while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) { + while (!vb->b.stopped && sc_vecdeque_is_empty(&vb->b.queue)) { sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex); } @@ -71,12 +62,11 @@ run_buffering(void *data) { goto stopped; } - struct sc_video_buffer_frame *vb_frame; - sc_queue_take(&vb->b.queue, next, &vb_frame); + struct sc_video_buffer_frame vb_frame = sc_vecdeque_pop(&vb->b.queue); sc_tick max_deadline = sc_tick_now() + vb->buffering_time; // PTS (written by the server) are expressed in microseconds - sc_tick pts = SC_TICK_TO_US(vb_frame->frame->pts); + sc_tick pts = SC_TICK_TO_US(vb_frame.frame->pts); bool timed_out = false; while (!vb->b.stopped && !timed_out) { @@ -91,7 +81,7 @@ run_buffering(void *data) { } if (vb->b.stopped) { - sc_video_buffer_frame_delete(vb_frame); + sc_video_buffer_frame_destroy(&vb_frame); sc_mutex_unlock(&vb->b.mutex); goto stopped; } @@ -100,20 +90,19 @@ run_buffering(void *data) { #ifndef SC_BUFFERING_NDEBUG LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick, - pts, vb_frame->push_date, sc_tick_now()); + pts, vb_frame.push_date, sc_tick_now()); #endif - sc_video_buffer_offer(vb, vb_frame->frame); + sc_video_buffer_offer(vb, vb_frame.frame); - sc_video_buffer_frame_delete(vb_frame); + sc_video_buffer_frame_destroy(&vb_frame); } stopped: // Flush queue - while (!sc_queue_is_empty(&vb->b.queue)) { - struct sc_video_buffer_frame *vb_frame; - sc_queue_take(&vb->b.queue, next, &vb_frame); - sc_video_buffer_frame_delete(vb_frame); + while (!sc_vecdeque_is_empty(&vb->b.queue)) { + struct sc_video_buffer_frame *p = sc_vecdeque_popref(&vb->b.queue); + sc_video_buffer_frame_destroy(p); } LOGD("Buffering thread ended"); @@ -154,7 +143,7 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, } sc_clock_init(&vb->b.clock); - sc_queue_init(&vb->b.queue); + sc_vecdeque_init(&vb->b.queue); } assert(cbs); @@ -230,17 +219,25 @@ sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { return sc_video_buffer_offer(vb, frame); } - struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame); - if (!vb_frame) { + struct sc_video_buffer_frame vb_frame; + bool ok = sc_video_buffer_frame_init(&vb_frame, frame); + if (!ok) { sc_mutex_unlock(&vb->b.mutex); LOG_OOM(); return false; } #ifndef SC_BUFFERING_NDEBUG - vb_frame->push_date = sc_tick_now(); + vb_frame.push_date = sc_tick_now(); #endif - sc_queue_push(&vb->b.queue, next, vb_frame); + + ok = sc_vecdeque_push(&vb->b.queue, vb_frame); + if (!ok) { + sc_mutex_unlock(&vb->b.mutex); + LOG_OOM(); + return false; + } + sc_cond_signal(&vb->b.queue_cond); sc_mutex_unlock(&vb->b.mutex); diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 48777703..41b09434 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -7,22 +7,21 @@ #include "clock.h" #include "frame_buffer.h" -#include "util/queue.h" #include "util/thread.h" #include "util/tick.h" +#include "util/vecdeque.h" // forward declarations typedef struct AVFrame AVFrame; struct sc_video_buffer_frame { AVFrame *frame; - struct sc_video_buffer_frame *next; #ifndef NDEBUG sc_tick push_date; #endif }; -struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame); +struct sc_video_buffer_frame_queue SC_VECDEQUE(struct sc_video_buffer_frame); struct sc_video_buffer { struct sc_frame_buffer fb; From 4d989de9ae27a88b08677d2cb44c0cce86380ee4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 22:39:11 +0100 Subject: [PATCH 0757/1133] Use VecDeque in controller Replace cbuf by VecDeque in controller. --- app/src/controller.c | 48 ++++++++++++++++++++++++++++++-------------- app/src/controller.h | 4 ++-- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 4a1d2b1d..0139e42c 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -4,19 +4,28 @@ #include "util/log.h" +#define SC_CONTROL_MSG_QUEUE_MAX 64 + bool sc_controller_init(struct sc_controller *controller, sc_socket control_socket, struct sc_acksync *acksync) { - cbuf_init(&controller->queue); + sc_vecdeque_init(&controller->queue); + + bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); + if (!ok) { + return false; + } - bool ok = sc_receiver_init(&controller->receiver, control_socket, acksync); + ok = sc_receiver_init(&controller->receiver, control_socket, acksync); if (!ok) { + sc_vecdeque_destroy(&controller->queue); return false; } ok = sc_mutex_init(&controller->mutex); if (!ok) { sc_receiver_destroy(&controller->receiver); + sc_vecdeque_destroy(&controller->queue); return false; } @@ -24,6 +33,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket, if (!ok) { sc_receiver_destroy(&controller->receiver); sc_mutex_destroy(&controller->mutex); + sc_vecdeque_destroy(&controller->queue); return false; } @@ -38,10 +48,12 @@ sc_controller_destroy(struct sc_controller *controller) { sc_cond_destroy(&controller->msg_cond); sc_mutex_destroy(&controller->mutex); - struct sc_control_msg msg; - while (cbuf_take(&controller->queue, &msg)) { - sc_control_msg_destroy(&msg); + while (!sc_vecdeque_is_empty(&controller->queue)) { + struct sc_control_msg *msg = sc_vecdeque_popref(&controller->queue); + assert(msg); + sc_control_msg_destroy(msg); } + sc_vecdeque_destroy(&controller->queue); sc_receiver_destroy(&controller->receiver); } @@ -54,13 +66,19 @@ sc_controller_push_msg(struct sc_controller *controller, } sc_mutex_lock(&controller->mutex); - bool was_empty = cbuf_is_empty(&controller->queue); - bool res = cbuf_push(&controller->queue, *msg); - if (was_empty) { - sc_cond_signal(&controller->msg_cond); + bool full = sc_vecdeque_is_full(&controller->queue); + if (!full) { + bool was_empty = sc_vecdeque_is_empty(&controller->queue); + sc_vecdeque_push_noresize(&controller->queue, *msg); + if (was_empty) { + sc_cond_signal(&controller->msg_cond); + } } + // Otherwise (if the queue is full), the msg is discarded + sc_mutex_unlock(&controller->mutex); - return res; + + return !full; } static bool @@ -82,7 +100,8 @@ run_controller(void *data) { for (;;) { sc_mutex_lock(&controller->mutex); - while (!controller->stopped && cbuf_is_empty(&controller->queue)) { + while (!controller->stopped + && sc_vecdeque_is_empty(&controller->queue)) { sc_cond_wait(&controller->msg_cond, &controller->mutex); } if (controller->stopped) { @@ -90,10 +109,9 @@ run_controller(void *data) { sc_mutex_unlock(&controller->mutex); break; } - struct sc_control_msg msg; - bool non_empty = cbuf_take(&controller->queue, &msg); - assert(non_empty); - (void) non_empty; + + assert(!sc_vecdeque_is_empty(&controller->queue)); + struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue); sc_mutex_unlock(&controller->mutex); bool ok = process_msg(controller, &msg); diff --git a/app/src/controller.h b/app/src/controller.h index 67c3c58d..a044b2bf 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -8,11 +8,11 @@ #include "control_msg.h" #include "receiver.h" #include "util/acksync.h" -#include "util/cbuf.h" #include "util/net.h" #include "util/thread.h" +#include "util/vecdeque.h" -struct sc_control_msg_queue CBUF(struct sc_control_msg, 64); +struct sc_control_msg_queue SC_VECDEQUE(struct sc_control_msg); struct sc_controller { sc_socket control_socket; From a0a65b3c4da6b56ca320959451fdf2979a34738d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 22:46:10 +0100 Subject: [PATCH 0758/1133] Use VecDeque in file_pusher Replace cbuf by VecDeque in file_pusher. As a side-effect, the new implementation does not limit the queue to an arbitrary value. --- app/src/file_pusher.c | 31 +++++++++++++++++++------------ app/src/file_pusher.h | 6 +++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c index f6757870..b49e93e5 100644 --- a/app/src/file_pusher.c +++ b/app/src/file_pusher.c @@ -19,7 +19,7 @@ sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial, const char *push_target) { assert(serial); - cbuf_init(&fp->queue); + sc_vecdeque_init(&fp->queue); bool ok = sc_mutex_init(&fp->mutex); if (!ok) { @@ -65,9 +65,10 @@ sc_file_pusher_destroy(struct sc_file_pusher *fp) { sc_intr_destroy(&fp->intr); free(fp->serial); - struct sc_file_pusher_request req; - while (cbuf_take(&fp->queue, &req)) { - sc_file_pusher_request_destroy(&req); + while (!sc_vecdeque_is_empty(&fp->queue)) { + struct sc_file_pusher_request *req = sc_vecdeque_popref(&fp->queue); + assert(req); + sc_file_pusher_request_destroy(req); } } @@ -91,13 +92,20 @@ sc_file_pusher_request(struct sc_file_pusher *fp, }; sc_mutex_lock(&fp->mutex); - bool was_empty = cbuf_is_empty(&fp->queue); - bool res = cbuf_push(&fp->queue, req); + bool was_empty = sc_vecdeque_is_empty(&fp->queue); + bool res = sc_vecdeque_push(&fp->queue, req); + if (!res) { + LOG_OOM(); + sc_mutex_unlock(&fp->mutex); + return false; + } + if (was_empty) { sc_cond_signal(&fp->event_cond); } sc_mutex_unlock(&fp->mutex); - return res; + + return true; } static int @@ -113,7 +121,7 @@ run_file_pusher(void *data) { for (;;) { sc_mutex_lock(&fp->mutex); - while (!fp->stopped && cbuf_is_empty(&fp->queue)) { + while (!fp->stopped && sc_vecdeque_is_empty(&fp->queue)) { sc_cond_wait(&fp->event_cond, &fp->mutex); } if (fp->stopped) { @@ -121,10 +129,9 @@ run_file_pusher(void *data) { sc_mutex_unlock(&fp->mutex); break; } - struct sc_file_pusher_request req; - bool non_empty = cbuf_take(&fp->queue, &req); - assert(non_empty); - (void) non_empty; + + assert(!sc_vecdeque_is_empty(&fp->queue)); + struct sc_file_pusher_request req = sc_vecdeque_pop(&fp->queue); sc_mutex_unlock(&fp->mutex); if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) { diff --git a/app/src/file_pusher.h b/app/src/file_pusher.h index 0d934d6c..0ffb3721 100644 --- a/app/src/file_pusher.h +++ b/app/src/file_pusher.h @@ -5,9 +5,9 @@ #include -#include "util/cbuf.h" -#include "util/thread.h" #include "util/intr.h" +#include "util/thread.h" +#include "util/vecdeque.h" enum sc_file_pusher_action { SC_FILE_PUSHER_ACTION_INSTALL_APK, @@ -19,7 +19,7 @@ struct sc_file_pusher_request { char *file; }; -struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16); +struct sc_file_pusher_request_queue SC_VECDEQUE(struct sc_file_pusher_request); struct sc_file_pusher { char *serial; From f978e4d6dea2958b449906e33fdb965ef5bc26d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 22:50:56 +0100 Subject: [PATCH 0759/1133] Use VecDeque in aoa_hid Replace cbuf by VecDeque in aoa_hid --- app/src/usb/aoa_hid.c | 41 +++++++++++++++++++++++++++-------------- app/src/usb/aoa_hid.h | 4 ++-- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 0007169d..fb64e57c 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -14,6 +14,8 @@ #define DEFAULT_TIMEOUT 1000 +#define SC_HID_EVENT_QUEUE_MAX 64 + static void sc_hid_event_log(const struct sc_hid_event *event) { // HID Event: [00] FF FF FF FF... @@ -48,14 +50,20 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) { bool sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { - cbuf_init(&aoa->queue); + sc_vecdeque_init(&aoa->queue); + + if (!sc_vecdeque_reserve(&aoa->queue, SC_HID_EVENT_QUEUE_MAX)) { + return false; + } if (!sc_mutex_init(&aoa->mutex)) { + sc_vecdeque_destroy(&aoa->queue); return false; } if (!sc_cond_init(&aoa->event_cond)) { sc_mutex_destroy(&aoa->mutex); + sc_vecdeque_destroy(&aoa->queue); return false; } @@ -69,9 +77,10 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, void sc_aoa_destroy(struct sc_aoa *aoa) { // Destroy remaining events - struct sc_hid_event event; - while (cbuf_take(&aoa->queue, &event)) { - sc_hid_event_destroy(&event); + while (!sc_vecdeque_is_empty(&aoa->queue)) { + struct sc_hid_event *event = sc_vecdeque_popref(&aoa->queue); + assert(event); + sc_hid_event_destroy(event); } sc_cond_destroy(&aoa->event_cond); @@ -212,13 +221,19 @@ sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { } sc_mutex_lock(&aoa->mutex); - bool was_empty = cbuf_is_empty(&aoa->queue); - bool res = cbuf_push(&aoa->queue, *event); - if (was_empty) { - sc_cond_signal(&aoa->event_cond); + bool full = sc_vecdeque_is_full(&aoa->queue); + if (!full) { + bool was_empty = sc_vecdeque_is_empty(&aoa->queue); + sc_vecdeque_push_noresize(&aoa->queue, *event); + if (was_empty) { + sc_cond_signal(&aoa->event_cond); + } } + // Otherwise (if the queue is full), the event is discarded + sc_mutex_unlock(&aoa->mutex); - return res; + + return !full; } static int @@ -227,7 +242,7 @@ run_aoa_thread(void *data) { for (;;) { sc_mutex_lock(&aoa->mutex); - while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) { + while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) { sc_cond_wait(&aoa->event_cond, &aoa->mutex); } if (aoa->stopped) { @@ -235,11 +250,9 @@ run_aoa_thread(void *data) { sc_mutex_unlock(&aoa->mutex); break; } - struct sc_hid_event event; - bool non_empty = cbuf_take(&aoa->queue, &event); - assert(non_empty); - (void) non_empty; + assert(!sc_vecdeque_is_empty(&aoa->queue)); + struct sc_hid_event event = sc_vecdeque_pop(&aoa->queue); uint64_t ack_to_wait = event.ack_to_wait; sc_mutex_unlock(&aoa->mutex); diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index d785a0e9..8803c1d9 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -8,9 +8,9 @@ #include "usb.h" #include "util/acksync.h" -#include "util/cbuf.h" #include "util/thread.h" #include "util/tick.h" +#include "util/vecdeque.h" struct sc_hid_event { uint16_t accessory_id; @@ -27,7 +27,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, void sc_hid_event_destroy(struct sc_hid_event *hid_event); -struct sc_hid_event_queue CBUF(struct sc_hid_event, 64); +struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event); struct sc_aoa { struct sc_usb *usb; From 338310677e2ef441d09048e19a30c94ecf4fbca4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 22:54:08 +0100 Subject: [PATCH 0760/1133] Remove cbuf All uses have been replaced by VecDeque. --- app/meson.build | 3 -- app/src/util/cbuf.h | 52 ----------------------------- app/tests/test_cbuf.c | 78 ------------------------------------------- 3 files changed, 133 deletions(-) delete mode 100644 app/src/util/cbuf.h delete mode 100644 app/tests/test_cbuf.c diff --git a/app/meson.build b/app/meson.build index a238eb8f..35934131 100644 --- a/app/meson.build +++ b/app/meson.build @@ -261,9 +261,6 @@ if get_option('buildtype') == 'debug' 'tests/test_bytebuf.c', 'src/util/bytebuf.c', ]], - ['test_cbuf', [ - 'tests/test_cbuf.c', - ]], ['test_cli', [ 'tests/test_cli.c', 'src/cli.c', diff --git a/app/src/util/cbuf.h b/app/src/util/cbuf.h deleted file mode 100644 index 2a756171..00000000 --- a/app/src/util/cbuf.h +++ /dev/null @@ -1,52 +0,0 @@ -// generic circular buffer (bounded queue) implementation -#ifndef SC_CBUF_H -#define SC_CBUF_H - -#include "common.h" - -#include -#include - -// To define a circular buffer type of 20 ints: -// struct cbuf_int CBUF(int, 20); -// -// data has length CAP + 1 to distinguish empty vs full. -#define CBUF(TYPE, CAP) { \ - TYPE data[(CAP) + 1]; \ - size_t head; \ - size_t tail; \ -} - -#define cbuf_size_(PCBUF) \ - (sizeof((PCBUF)->data) / sizeof(*(PCBUF)->data)) - -#define cbuf_is_empty(PCBUF) \ - ((PCBUF)->head == (PCBUF)->tail) - -#define cbuf_is_full(PCBUF) \ - (((PCBUF)->head + 1) % cbuf_size_(PCBUF) == (PCBUF)->tail) - -#define cbuf_init(PCBUF) \ - (void) ((PCBUF)->head = (PCBUF)->tail = 0) - -#define cbuf_push(PCBUF, ITEM) \ - ({ \ - bool ok = !cbuf_is_full(PCBUF); \ - if (ok) { \ - (PCBUF)->data[(PCBUF)->head] = (ITEM); \ - (PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \ - } \ - ok; \ - }) - -#define cbuf_take(PCBUF, PITEM) \ - ({ \ - bool ok = !cbuf_is_empty(PCBUF); \ - if (ok) { \ - *(PITEM) = (PCBUF)->data[(PCBUF)->tail]; \ - (PCBUF)->tail = ((PCBUF)->tail + 1) % cbuf_size_(PCBUF); \ - } \ - ok; \ - }) - -#endif diff --git a/app/tests/test_cbuf.c b/app/tests/test_cbuf.c deleted file mode 100644 index 16674e92..00000000 --- a/app/tests/test_cbuf.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "common.h" - -#include -#include - -#include "util/cbuf.h" - -struct int_queue CBUF(int, 32); - -static void test_cbuf_empty(void) { - struct int_queue queue; - cbuf_init(&queue); - - assert(cbuf_is_empty(&queue)); - - bool push_ok = cbuf_push(&queue, 42); - assert(push_ok); - assert(!cbuf_is_empty(&queue)); - - int item; - bool take_ok = cbuf_take(&queue, &item); - assert(take_ok); - assert(cbuf_is_empty(&queue)); - - bool take_empty_ok = cbuf_take(&queue, &item); - assert(!take_empty_ok); // the queue is empty -} - -static void test_cbuf_full(void) { - struct int_queue queue; - cbuf_init(&queue); - - assert(!cbuf_is_full(&queue)); - - // fill the queue - for (int i = 0; i < 32; ++i) { - bool ok = cbuf_push(&queue, i); - assert(ok); - } - bool ok = cbuf_push(&queue, 42); - assert(!ok); // the queue if full - - int item; - bool take_ok = cbuf_take(&queue, &item); - assert(take_ok); - assert(!cbuf_is_full(&queue)); -} - -static void test_cbuf_push_take(void) { - struct int_queue queue; - cbuf_init(&queue); - - bool push1_ok = cbuf_push(&queue, 42); - assert(push1_ok); - - bool push2_ok = cbuf_push(&queue, 35); - assert(push2_ok); - - int item; - - bool take1_ok = cbuf_take(&queue, &item); - assert(take1_ok); - assert(item == 42); - - bool take2_ok = cbuf_take(&queue, &item); - assert(take2_ok); - assert(item == 35); -} - -int main(int argc, char *argv[]) { - (void) argc; - (void) argv; - - test_cbuf_empty(); - test_cbuf_full(); - test_cbuf_push_take(); - return 0; -} From 6f38c6311b4db17858e221f66cbe9be1c7da12ca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 23:05:17 +0100 Subject: [PATCH 0761/1133] Remove sc_queue All uses have been replaced by VecDeque. --- app/meson.build | 3 -- app/src/util/queue.h | 77 ------------------------------------------ app/tests/test_queue.c | 43 ----------------------- 3 files changed, 123 deletions(-) delete mode 100644 app/src/util/queue.h delete mode 100644 app/tests/test_queue.c diff --git a/app/meson.build b/app/meson.build index 35934131..e34f7cc1 100644 --- a/app/meson.build +++ b/app/meson.build @@ -285,9 +285,6 @@ if get_option('buildtype') == 'debug' 'tests/test_device_msg_deserialize.c', 'src/device_msg.c', ]], - ['test_queue', [ - 'tests/test_queue.c', - ]], ['test_strbuf', [ 'tests/test_strbuf.c', 'src/util/strbuf.c', diff --git a/app/src/util/queue.h b/app/src/util/queue.h deleted file mode 100644 index 2233eca0..00000000 --- a/app/src/util/queue.h +++ /dev/null @@ -1,77 +0,0 @@ -// generic intrusive FIFO queue -#ifndef SC_QUEUE_H -#define SC_QUEUE_H - -#include "common.h" - -#include -#include -#include - -// To define a queue type of "struct foo": -// struct queue_foo QUEUE(struct foo); -#define SC_QUEUE(TYPE) { \ - TYPE *first; \ - TYPE *last; \ -} - -#define sc_queue_init(PQ) \ - (void) ((PQ)->first = (PQ)->last = NULL) - -#define sc_queue_is_empty(PQ) \ - !(PQ)->first - -// NEXTFIELD is the field in the ITEM type used for intrusive linked-list -// -// For example: -// struct foo { -// int value; -// struct foo *next; -// }; -// -// // define the type "struct my_queue" -// struct my_queue SC_QUEUE(struct foo); -// -// struct my_queue queue; -// sc_queue_init(&queue); -// -// struct foo v1 = { .value = 42 }; -// struct foo v2 = { .value = 27 }; -// -// sc_queue_push(&queue, next, v1); -// sc_queue_push(&queue, next, v2); -// -// struct foo *foo; -// sc_queue_take(&queue, next, &foo); -// assert(foo->value == 42); -// sc_queue_take(&queue, next, &foo); -// assert(foo->value == 27); -// assert(sc_queue_is_empty(&queue)); -// - -// push a new item into the queue -#define sc_queue_push(PQ, NEXTFIELD, ITEM) \ - (void) ({ \ - (ITEM)->NEXTFIELD = NULL; \ - if (sc_queue_is_empty(PQ)) { \ - (PQ)->first = (PQ)->last = (ITEM); \ - } else { \ - (PQ)->last->NEXTFIELD = (ITEM); \ - (PQ)->last = (ITEM); \ - } \ - }) - -// take the next item and remove it from the queue (the queue must not be empty) -// the result is stored in *(PITEM) -// (without typeof(), we could not store a local variable having the correct -// type so that we can "return" it) -#define sc_queue_take(PQ, NEXTFIELD, PITEM) \ - (void) ({ \ - assert(!sc_queue_is_empty(PQ)); \ - *(PITEM) = (PQ)->first; \ - (PQ)->first = (PQ)->first->NEXTFIELD; \ - }) - // no need to update (PQ)->last if the queue is left empty: - // (PQ)->last is undefined if !(PQ)->first anyway - -#endif diff --git a/app/tests/test_queue.c b/app/tests/test_queue.c deleted file mode 100644 index d8b2b4ec..00000000 --- a/app/tests/test_queue.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "common.h" - -#include - -#include "util/queue.h" - -struct foo { - int value; - struct foo *next; -}; - -static void test_queue(void) { - struct my_queue SC_QUEUE(struct foo) queue; - sc_queue_init(&queue); - - assert(sc_queue_is_empty(&queue)); - - struct foo v1 = { .value = 42 }; - struct foo v2 = { .value = 27 }; - - sc_queue_push(&queue, next, &v1); - sc_queue_push(&queue, next, &v2); - - struct foo *foo; - - assert(!sc_queue_is_empty(&queue)); - sc_queue_take(&queue, next, &foo); - assert(foo->value == 42); - - assert(!sc_queue_is_empty(&queue)); - sc_queue_take(&queue, next, &foo); - assert(foo->value == 27); - - assert(sc_queue_is_empty(&queue)); -} - -int main(int argc, char *argv[]) { - (void) argc; - (void) argv; - - test_queue(); - return 0; -} From a3703340fc5f2d10d8b8d1a248200f302831c432 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 18:24:31 +0100 Subject: [PATCH 0762/1133] Fix possible race condition on video_buffer end The video_buffer thread clears the queue once it is stopped, but new frames might still be pushed asynchronously. To avoid the problem, do not push any frame once the video_buffer is stopped. --- app/src/video_buffer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index b3b29098..a8f7f20a 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -99,6 +99,8 @@ run_buffering(void *data) { } stopped: + assert(vb->b.stopped); + // Flush queue while (!sc_vecdeque_is_empty(&vb->b.queue)) { struct sc_video_buffer_frame *p = sc_vecdeque_popref(&vb->b.queue); @@ -206,6 +208,11 @@ sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { sc_mutex_lock(&vb->b.mutex); + if (vb->b.stopped) { + sc_mutex_unlock(&vb->b.mutex); + return false; + } + sc_tick pts = SC_TICK_FROM_US(frame->pts); sc_clock_update(&vb->b.clock, sc_tick_now(), pts); sc_cond_signal(&vb->b.wait_cond); From ad94ccca0bbed9cd8ddaaa931da2c7ed18bf060c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 18:33:03 +0100 Subject: [PATCH 0763/1133] Stop the video buffer on error If an error occurs from the video buffer thread (typically an out-of-memory error), then stop. --- app/src/video_buffer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index a8f7f20a..49c01839 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -93,9 +93,16 @@ run_buffering(void *data) { pts, vb_frame.push_date, sc_tick_now()); #endif - sc_video_buffer_offer(vb, vb_frame.frame); - + bool ok = sc_video_buffer_offer(vb, vb_frame.frame); sc_video_buffer_frame_destroy(&vb_frame); + if (!ok) { + LOGE("Delayed frame could not be pushed, stopping"); + sc_mutex_lock(&vb->b.mutex); + // Prevent to push any new packet + vb->b.stopped = true; + sc_mutex_unlock(&vb->b.mutex); + goto stopped; + } } stopped: From 4540f1d69e7435b78af2c968c3b07b06729a714f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Mar 2023 18:45:22 +0100 Subject: [PATCH 0764/1133] Report video buffer downstream errors Make the video buffer stop if its consumer could not receive a frame. --- app/src/screen.c | 21 +++++---------------- app/src/screen.h | 2 -- app/src/v4l2_sink.c | 4 +++- app/src/video_buffer.c | 3 +-- app/src/video_buffer.h | 2 +- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index a9a48eae..ce2e74bb 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -362,27 +362,17 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { return sc_video_buffer_push(&screen->vb, frame); } -static void +static bool sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, void *userdata) { (void) vb; struct sc_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) { sc_fps_counter_add_skipped_frame(&screen->fps_counter); // The SC_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 = SC_EVENT_NEW_FRAME, }; @@ -391,11 +381,11 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, 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; + return false; } } + + return true; } bool @@ -405,7 +395,6 @@ sc_screen_init(struct sc_screen *screen, screen->has_frame = false; screen->fullscreen = false; screen->maximized = false; - screen->event_failed = false; screen->mouse_capture_key_pressed = 0; screen->req.x = params->window_x; diff --git a/app/src/screen.h b/app/src/screen.h index 222e418f..0952c79c 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -59,8 +59,6 @@ struct sc_screen { bool maximized; bool mipmaps; - bool event_failed; // in case SDL_PushEvent() returned an error - // To enable/disable mouse capture, a mouse capture key (LALT, LGUI or // RGUI) must be pressed. This variable tracks the pressed capture key. SDL_Keycode mouse_capture_key_pressed; diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index ba876b2b..5dfe37bc 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -141,7 +141,7 @@ run_v4l2_sink(void *data) { return 0; } -static void +static bool sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, void *userdata) { (void) vb; @@ -153,6 +153,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, sc_cond_signal(&vs->cond); sc_mutex_unlock(&vs->mutex); } + + return true; } static bool diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 49c01839..7f771179 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -40,8 +40,7 @@ sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) { return false; } - vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); - return true; + return vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); } static int diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 41b09434..d183a484 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -45,7 +45,7 @@ struct sc_video_buffer { }; struct sc_video_buffer_callbacks { - void (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped, + bool (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped, void *userdata); }; From 6379c08012454300e399006e665a5e4f7d436202 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Mar 2023 09:05:46 +0100 Subject: [PATCH 0765/1133] Fix buffering pts conversion The mistake had no effect, because tick is also internally expressed in microseconds. --- app/src/video_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 7f771179..74a4b042 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -65,7 +65,7 @@ run_buffering(void *data) { sc_tick max_deadline = sc_tick_now() + vb->buffering_time; // PTS (written by the server) are expressed in microseconds - sc_tick pts = SC_TICK_TO_US(vb_frame.frame->pts); + sc_tick pts = SC_TICK_FROM_US(vb_frame.frame->pts); bool timed_out = false; while (!vb->b.stopped && !timed_out) { From f410f2bdc468cd32dd099679471af08ca6a76a54 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 00:31:43 +0100 Subject: [PATCH 0766/1133] Extract sc_delay_buffer A video buffer had 2 responsibilities: - handle the frame delaying mechanism (queuing packets and pushing them after the expected delay); - keep only the most recent frame (using a sc_frame_buffer). In order to be able to reuse only the frame delaying mechanism, extract it to a separate component, sc_delay_buffer. --- app/meson.build | 1 + app/src/delay_buffer.c | 246 +++++++++++++++++++++++++++++++++++++++++ app/src/delay_buffer.h | 69 ++++++++++++ app/src/video_buffer.c | 216 +++--------------------------------- app/src/video_buffer.h | 34 +----- 5 files changed, 337 insertions(+), 229 deletions(-) create mode 100644 app/src/delay_buffer.c create mode 100644 app/src/delay_buffer.h diff --git a/app/meson.build b/app/meson.build index e34f7cc1..7749d664 100644 --- a/app/meson.build +++ b/app/meson.build @@ -10,6 +10,7 @@ src = [ 'src/control_msg.c', 'src/controller.c', 'src/decoder.c', + 'src/delay_buffer.c', 'src/demuxer.c', 'src/device_msg.c', 'src/icon.c', diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c new file mode 100644 index 00000000..95d47c9c --- /dev/null +++ b/app/src/delay_buffer.c @@ -0,0 +1,246 @@ +#include "delay_buffer.h" + +#include +#include + +#include +#include + +#include "util/log.h" + +#define SC_BUFFERING_NDEBUG // comment to debug + +static bool +sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) { + dframe->frame = av_frame_alloc(); + if (!dframe->frame) { + LOG_OOM(); + return false; + } + + if (av_frame_ref(dframe->frame, frame)) { + LOG_OOM(); + av_frame_free(&dframe->frame); + return false; + } + + return true; +} + +static void +sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) { + av_frame_unref(dframe->frame); + av_frame_free(&dframe->frame); +} + +static bool +sc_delay_buffer_offer(struct sc_delay_buffer *db, const AVFrame *frame) { + return db->cbs->on_new_frame(db, frame, db->cbs_userdata); +} + +static int +run_buffering(void *data) { + struct sc_delay_buffer *db = data; + + assert(db->delay > 0); + + for (;;) { + sc_mutex_lock(&db->b.mutex); + + while (!db->b.stopped && sc_vecdeque_is_empty(&db->b.queue)) { + sc_cond_wait(&db->b.queue_cond, &db->b.mutex); + } + + if (db->b.stopped) { + sc_mutex_unlock(&db->b.mutex); + goto stopped; + } + + struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->b.queue); + + sc_tick max_deadline = sc_tick_now() + db->delay; + // PTS (written by the server) are expressed in microseconds + sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts); + + bool timed_out = false; + while (!db->b.stopped && !timed_out) { + sc_tick deadline = sc_clock_to_system_time(&db->b.clock, pts) + + db->delay; + if (deadline > max_deadline) { + deadline = max_deadline; + } + + timed_out = + !sc_cond_timedwait(&db->b.wait_cond, &db->b.mutex, deadline); + } + + bool stopped = db->b.stopped; + sc_mutex_unlock(&db->b.mutex); + + if (stopped) { + sc_delayed_frame_destroy(&dframe); + goto stopped; + } + +#ifndef SC_BUFFERING_NDEBUG + LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick, + pts, dframe.push_date, sc_tick_now()); +#endif + + bool ok = sc_delay_buffer_offer(db, dframe.frame); + sc_delayed_frame_destroy(&dframe); + if (!ok) { + LOGE("Delayed frame could not be pushed, stopping"); + sc_mutex_lock(&db->b.mutex); + // Prevent to push any new packet + db->b.stopped = true; + sc_mutex_unlock(&db->b.mutex); + goto stopped; + } + } + +stopped: + assert(db->b.stopped); + + // Flush queue + while (!sc_vecdeque_is_empty(&db->b.queue)) { + struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->b.queue); + sc_delayed_frame_destroy(dframe); + } + + LOGD("Buffering thread ended"); + + return 0; +} + +bool +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, + const struct sc_delay_buffer_callbacks *cbs, + void *cbs_userdata) { + assert(delay >= 0); + + if (delay) { + bool ok = sc_mutex_init(&db->b.mutex); + if (!ok) { + return false; + } + + ok = sc_cond_init(&db->b.queue_cond); + if (!ok) { + sc_mutex_destroy(&db->b.mutex); + return false; + } + + ok = sc_cond_init(&db->b.wait_cond); + if (!ok) { + sc_cond_destroy(&db->b.queue_cond); + sc_mutex_destroy(&db->b.mutex); + return false; + } + + sc_clock_init(&db->b.clock); + sc_vecdeque_init(&db->b.queue); + } + + assert(cbs); + assert(cbs->on_new_frame); + + db->delay = delay; + db->cbs = cbs; + db->cbs_userdata = cbs_userdata; + + return true; +} + +bool +sc_delay_buffer_start(struct sc_delay_buffer *db) { + if (db->delay) { + bool ok = + sc_thread_create(&db->b.thread, run_buffering, "scrcpy-dbuf", db); + if (!ok) { + LOGE("Could not start buffering thread"); + return false; + } + } + + return true; +} + +void +sc_delay_buffer_stop(struct sc_delay_buffer *db) { + if (db->delay) { + sc_mutex_lock(&db->b.mutex); + db->b.stopped = true; + sc_cond_signal(&db->b.queue_cond); + sc_cond_signal(&db->b.wait_cond); + sc_mutex_unlock(&db->b.mutex); + } +} + +void +sc_delay_buffer_join(struct sc_delay_buffer *db) { + if (db->delay) { + sc_thread_join(&db->b.thread, NULL); + } +} + +void +sc_delay_buffer_destroy(struct sc_delay_buffer *db) { + if (db->delay) { + sc_cond_destroy(&db->b.wait_cond); + sc_cond_destroy(&db->b.queue_cond); + sc_mutex_destroy(&db->b.mutex); + } +} + +bool +sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame) { + if (!db->delay) { + // No buffering + return sc_delay_buffer_offer(db, frame); + } + + sc_mutex_lock(&db->b.mutex); + + if (db->b.stopped) { + sc_mutex_unlock(&db->b.mutex); + return false; + } + + sc_tick pts = SC_TICK_FROM_US(frame->pts); + sc_clock_update(&db->b.clock, sc_tick_now(), pts); + sc_cond_signal(&db->b.wait_cond); + + if (db->b.clock.count == 1) { + sc_mutex_unlock(&db->b.mutex); + // First frame, offer it immediately, for two reasons: + // - not to delay the opening of the scrcpy window + // - the buffering estimation needs at least two clock points, so it + // could not handle the first frame + return sc_delay_buffer_offer(db, frame); + } + + struct sc_delayed_frame dframe; + bool ok = sc_delayed_frame_init(&dframe, frame); + if (!ok) { + sc_mutex_unlock(&db->b.mutex); + return false; + } + +#ifndef SC_BUFFERING_NDEBUG + dframe.push_date = sc_tick_now(); +#endif + + ok = sc_vecdeque_push(&db->b.queue, dframe); + if (!ok) { + sc_mutex_unlock(&db->b.mutex); + LOG_OOM(); + return false; + } + + sc_cond_signal(&db->b.queue_cond); + + sc_mutex_unlock(&db->b.mutex); + + return true; +} diff --git a/app/src/delay_buffer.h b/app/src/delay_buffer.h new file mode 100644 index 00000000..9e5347c7 --- /dev/null +++ b/app/src/delay_buffer.h @@ -0,0 +1,69 @@ +#ifndef SC_DELAY_BUFFER_H +#define SC_DELAY_BUFFER_H + +#include "common.h" + +#include + +#include "clock.h" +#include "util/thread.h" +#include "util/tick.h" +#include "util/vecdeque.h" + +// forward declarations +typedef struct AVFrame AVFrame; + +struct sc_delayed_frame { + AVFrame *frame; +#ifndef NDEBUG + sc_tick push_date; +#endif +}; + +struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame); + +struct sc_delay_buffer { + sc_tick delay; + + // only if delay > 0 + struct { + sc_thread thread; + sc_mutex mutex; + sc_cond queue_cond; + sc_cond wait_cond; + + struct sc_clock clock; + struct sc_delayed_frame_queue queue; + bool stopped; + } b; // buffering + + const struct sc_delay_buffer_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_delay_buffer_callbacks { + bool (*on_new_frame)(struct sc_delay_buffer *db, const AVFrame *frame, + void *userdata); +}; + +bool +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, + const struct sc_delay_buffer_callbacks *cbs, + void *cbs_userdata); + +bool +sc_delay_buffer_start(struct sc_delay_buffer *db); + +void +sc_delay_buffer_stop(struct sc_delay_buffer *db); + +void +sc_delay_buffer_join(struct sc_delay_buffer *db); + +void +sc_delay_buffer_destroy(struct sc_delay_buffer *db); + +bool +sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame); + +#endif diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 74a4b042..da47a0b5 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -8,32 +8,13 @@ #include "util/log.h" -#define SC_BUFFERING_NDEBUG // comment to debug - static bool -sc_video_buffer_frame_init(struct sc_video_buffer_frame *vb_frame, - const AVFrame *frame) { - vb_frame->frame = av_frame_alloc(); - if (!vb_frame->frame) { - return false; - } - - if (av_frame_ref(vb_frame->frame, frame)) { - av_frame_free(&vb_frame->frame); - return false; - } - - return true; -} +sc_delay_buffer_on_new_frame(struct sc_delay_buffer *db, const AVFrame *frame, + void *userdata) { + (void) db; -static void -sc_video_buffer_frame_destroy(struct sc_video_buffer_frame *vb_frame) { - av_frame_unref(vb_frame->frame); - av_frame_free(&vb_frame->frame); -} + struct sc_video_buffer *vb = userdata; -static bool -sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) { bool previous_skipped; bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped); if (!ok) { @@ -43,83 +24,8 @@ sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) { return vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); } -static int -run_buffering(void *data) { - struct sc_video_buffer *vb = data; - - assert(vb->buffering_time > 0); - - for (;;) { - sc_mutex_lock(&vb->b.mutex); - - while (!vb->b.stopped && sc_vecdeque_is_empty(&vb->b.queue)) { - sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex); - } - - if (vb->b.stopped) { - sc_mutex_unlock(&vb->b.mutex); - goto stopped; - } - - struct sc_video_buffer_frame vb_frame = sc_vecdeque_pop(&vb->b.queue); - - sc_tick max_deadline = sc_tick_now() + vb->buffering_time; - // PTS (written by the server) are expressed in microseconds - sc_tick pts = SC_TICK_FROM_US(vb_frame.frame->pts); - - bool timed_out = false; - while (!vb->b.stopped && !timed_out) { - sc_tick deadline = sc_clock_to_system_time(&vb->b.clock, pts) - + vb->buffering_time; - if (deadline > max_deadline) { - deadline = max_deadline; - } - - timed_out = - !sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline); - } - - if (vb->b.stopped) { - sc_video_buffer_frame_destroy(&vb_frame); - sc_mutex_unlock(&vb->b.mutex); - goto stopped; - } - - sc_mutex_unlock(&vb->b.mutex); - -#ifndef SC_BUFFERING_NDEBUG - LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick, - pts, vb_frame.push_date, sc_tick_now()); -#endif - - bool ok = sc_video_buffer_offer(vb, vb_frame.frame); - sc_video_buffer_frame_destroy(&vb_frame); - if (!ok) { - LOGE("Delayed frame could not be pushed, stopping"); - sc_mutex_lock(&vb->b.mutex); - // Prevent to push any new packet - vb->b.stopped = true; - sc_mutex_unlock(&vb->b.mutex); - goto stopped; - } - } - -stopped: - assert(vb->b.stopped); - - // Flush queue - while (!sc_vecdeque_is_empty(&vb->b.queue)) { - struct sc_video_buffer_frame *p = sc_vecdeque_popref(&vb->b.queue); - sc_video_buffer_frame_destroy(p); - } - - LOGD("Buffering thread ended"); - - return 0; -} - bool -sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, +sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick delay, const struct sc_video_buffer_callbacks *cbs, void *cbs_userdata) { bool ok = sc_frame_buffer_init(&vb->fb); @@ -127,135 +33,49 @@ sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, return false; } - assert(buffering_time >= 0); - if (buffering_time) { - ok = sc_mutex_init(&vb->b.mutex); - if (!ok) { - sc_frame_buffer_destroy(&vb->fb); - return false; - } - - ok = sc_cond_init(&vb->b.queue_cond); - if (!ok) { - sc_mutex_destroy(&vb->b.mutex); - sc_frame_buffer_destroy(&vb->fb); - return false; - } + static const struct sc_delay_buffer_callbacks db_cbs = { + .on_new_frame = sc_delay_buffer_on_new_frame, + }; - ok = sc_cond_init(&vb->b.wait_cond); - if (!ok) { - sc_cond_destroy(&vb->b.queue_cond); - sc_mutex_destroy(&vb->b.mutex); - sc_frame_buffer_destroy(&vb->fb); - return false; - } - - sc_clock_init(&vb->b.clock); - sc_vecdeque_init(&vb->b.queue); + ok = sc_delay_buffer_init(&vb->db, delay, &db_cbs, vb); + if (!ok) { + sc_frame_buffer_destroy(&vb->fb); + return false; } assert(cbs); assert(cbs->on_new_frame); - vb->buffering_time = buffering_time; vb->cbs = cbs; vb->cbs_userdata = cbs_userdata; + return true; } bool sc_video_buffer_start(struct sc_video_buffer *vb) { - if (vb->buffering_time) { - bool ok = - sc_thread_create(&vb->b.thread, run_buffering, "scrcpy-vbuf", vb); - if (!ok) { - LOGE("Could not start buffering thread"); - return false; - } - } - - return true; + return sc_delay_buffer_start(&vb->db); } void sc_video_buffer_stop(struct sc_video_buffer *vb) { - if (vb->buffering_time) { - sc_mutex_lock(&vb->b.mutex); - vb->b.stopped = true; - sc_cond_signal(&vb->b.queue_cond); - sc_cond_signal(&vb->b.wait_cond); - sc_mutex_unlock(&vb->b.mutex); - } + return sc_delay_buffer_stop(&vb->db); } void sc_video_buffer_join(struct sc_video_buffer *vb) { - if (vb->buffering_time) { - sc_thread_join(&vb->b.thread, NULL); - } + return sc_delay_buffer_join(&vb->db); } void sc_video_buffer_destroy(struct sc_video_buffer *vb) { sc_frame_buffer_destroy(&vb->fb); - if (vb->buffering_time) { - sc_cond_destroy(&vb->b.wait_cond); - sc_cond_destroy(&vb->b.queue_cond); - sc_mutex_destroy(&vb->b.mutex); - } + sc_delay_buffer_destroy(&vb->db); } bool sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { - if (!vb->buffering_time) { - // No buffering - return sc_video_buffer_offer(vb, frame); - } - - sc_mutex_lock(&vb->b.mutex); - - if (vb->b.stopped) { - sc_mutex_unlock(&vb->b.mutex); - return false; - } - - sc_tick pts = SC_TICK_FROM_US(frame->pts); - sc_clock_update(&vb->b.clock, sc_tick_now(), pts); - sc_cond_signal(&vb->b.wait_cond); - - if (vb->b.clock.count == 1) { - sc_mutex_unlock(&vb->b.mutex); - // First frame, offer it immediately, for two reasons: - // - not to delay the opening of the scrcpy window - // - the buffering estimation needs at least two clock points, so it - // could not handle the first frame - return sc_video_buffer_offer(vb, frame); - } - - struct sc_video_buffer_frame vb_frame; - bool ok = sc_video_buffer_frame_init(&vb_frame, frame); - if (!ok) { - sc_mutex_unlock(&vb->b.mutex); - LOG_OOM(); - return false; - } - -#ifndef SC_BUFFERING_NDEBUG - vb_frame.push_date = sc_tick_now(); -#endif - - ok = sc_vecdeque_push(&vb->b.queue, vb_frame); - if (!ok) { - sc_mutex_unlock(&vb->b.mutex); - LOG_OOM(); - return false; - } - - sc_cond_signal(&vb->b.queue_cond); - - sc_mutex_unlock(&vb->b.mutex); - - return true; + return sc_delay_buffer_push(&vb->db, frame); } void diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index d183a484..ebca3915 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -5,41 +5,13 @@ #include -#include "clock.h" +#include "delay_buffer.h" #include "frame_buffer.h" -#include "util/thread.h" -#include "util/tick.h" -#include "util/vecdeque.h" - -// forward declarations -typedef struct AVFrame AVFrame; - -struct sc_video_buffer_frame { - AVFrame *frame; -#ifndef NDEBUG - sc_tick push_date; -#endif -}; - -struct sc_video_buffer_frame_queue SC_VECDEQUE(struct sc_video_buffer_frame); struct sc_video_buffer { + struct sc_delay_buffer db; struct sc_frame_buffer fb; - sc_tick buffering_time; - - // only if buffering_time > 0 - struct { - sc_thread thread; - sc_mutex mutex; - sc_cond queue_cond; - sc_cond wait_cond; - - struct sc_clock clock; - struct sc_video_buffer_frame_queue queue; - bool stopped; - } b; // buffering - const struct sc_video_buffer_callbacks *cbs; void *cbs_userdata; }; @@ -50,7 +22,7 @@ struct sc_video_buffer_callbacks { }; bool -sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, +sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick delay, const struct sc_video_buffer_callbacks *cbs, void *cbs_userdata); From c39054a63da52ab266ed26d78e2329c48e4e0216 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 09:07:25 +0100 Subject: [PATCH 0767/1133] Introduce packet source trait There was a packet sink trait, implemented by components able to receive AVPackets, but each packet source had to manually send packets to sinks. In order to mutualise sink management, add a packet source trait. --- app/meson.build | 1 + app/src/trait/packet_source.c | 70 +++++++++++++++++++++++++++++++++++ app/src/trait/packet_source.h | 41 ++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 app/src/trait/packet_source.c create mode 100644 app/src/trait/packet_source.h diff --git a/app/meson.build b/app/meson.build index 7749d664..82105876 100644 --- a/app/meson.build +++ b/app/meson.build @@ -30,6 +30,7 @@ src = [ 'src/server.c', 'src/version.c', 'src/video_buffer.c', + 'src/trait/packet_source.c', 'src/util/acksync.c', 'src/util/bytebuf.c', 'src/util/file.c', diff --git a/app/src/trait/packet_source.c b/app/src/trait/packet_source.c new file mode 100644 index 00000000..df678e16 --- /dev/null +++ b/app/src/trait/packet_source.c @@ -0,0 +1,70 @@ +#include "packet_source.h" + +void +sc_packet_source_init(struct sc_packet_source *source) { + source->sink_count = 0; +} + +void +sc_packet_source_add_sink(struct sc_packet_source *source, + struct sc_packet_sink *sink) { + assert(source->sink_count < SC_PACKET_SOURCE_MAX_SINKS); + assert(sink); + assert(sink->ops); + source->sinks[source->sink_count++] = sink; +} + +static void +sc_packet_source_sinks_close_firsts(struct sc_packet_source *source, + unsigned count) { + while (count) { + struct sc_packet_sink *sink = source->sinks[--count]; + sink->ops->close(sink); + } +} + +bool +sc_packet_source_sinks_open(struct sc_packet_source *source, + const AVCodec *codec) { + assert(source->sink_count); + for (unsigned i = 0; i < source->sink_count; ++i) { + struct sc_packet_sink *sink = source->sinks[i]; + if (!sink->ops->open(sink, codec)) { + sc_packet_source_sinks_close_firsts(source, i); + return false; + } + } + + return true; +} + +void +sc_packet_source_sinks_close(struct sc_packet_source *source) { + assert(source->sink_count); + sc_packet_source_sinks_close_firsts(source, source->sink_count); +} + +bool +sc_packet_source_sinks_push(struct sc_packet_source *source, + const AVPacket *packet) { + assert(source->sink_count); + for (unsigned i = 0; i < source->sink_count; ++i) { + struct sc_packet_sink *sink = source->sinks[i]; + if (!sink->ops->push(sink, packet)) { + return false; + } + } + + return true; +} + +void +sc_packet_source_sinks_disable(struct sc_packet_source *source) { + assert(source->sink_count); + for (unsigned i = 0; i < source->sink_count; ++i) { + struct sc_packet_sink *sink = source->sinks[i]; + if (sink->ops->disable) { + sink->ops->disable(sink); + } + } +} diff --git a/app/src/trait/packet_source.h b/app/src/trait/packet_source.h new file mode 100644 index 00000000..c34aa5d3 --- /dev/null +++ b/app/src/trait/packet_source.h @@ -0,0 +1,41 @@ +#ifndef SC_PACKET_SOURCE_H +#define SC_PACKET_SOURCE_H + +#include "common.h" + +#include "packet_sink.h" + +#define SC_PACKET_SOURCE_MAX_SINKS 2 + +/** + * Packet source trait + * + * Component able to send AVPackets should implement this trait. + */ +struct sc_packet_source { + struct sc_packet_sink *sinks[SC_PACKET_SOURCE_MAX_SINKS]; + unsigned sink_count; +}; + +void +sc_packet_source_init(struct sc_packet_source *source); + +void +sc_packet_source_add_sink(struct sc_packet_source *source, + struct sc_packet_sink *sink); + +bool +sc_packet_source_sinks_open(struct sc_packet_source *source, + const AVCodec *codec); + +void +sc_packet_source_sinks_close(struct sc_packet_source *source); + +bool +sc_packet_source_sinks_push(struct sc_packet_source *source, + const AVPacket *packet); + +void +sc_packet_source_sinks_disable(struct sc_packet_source *source); + +#endif From f3197e178d297544774c37c766907b1992929e67 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 09:20:37 +0100 Subject: [PATCH 0768/1133] Use packet source trait in demuxer --- app/src/demuxer.c | 83 +++++------------------------------------------ app/src/demuxer.h | 11 ++----- app/src/scrcpy.c | 13 +++++--- 3 files changed, 19 insertions(+), 88 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 5977a28a..15a595a0 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -112,65 +112,6 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { return true; } -static bool -push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) { - for (unsigned i = 0; i < demuxer->sink_count; ++i) { - struct sc_packet_sink *sink = demuxer->sinks[i]; - if (!sink->ops->push(sink, packet)) { - return false; - } - } - - return true; -} - -static bool -sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) { - bool ok = push_packet_to_sinks(demuxer, packet); - if (!ok) { - LOGE("Demuxer '%s': could not process packet", demuxer->name); - return false; - } - - return true; -} - -static void -sc_demuxer_close_first_sinks(struct sc_demuxer *demuxer, unsigned count) { - while (count) { - struct sc_packet_sink *sink = demuxer->sinks[--count]; - sink->ops->close(sink); - } -} - -static inline void -sc_demuxer_close_sinks(struct sc_demuxer *demuxer) { - sc_demuxer_close_first_sinks(demuxer, demuxer->sink_count); -} - -static bool -sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) { - for (unsigned i = 0; i < demuxer->sink_count; ++i) { - struct sc_packet_sink *sink = demuxer->sinks[i]; - if (!sink->ops->open(sink, codec)) { - sc_demuxer_close_first_sinks(demuxer, i); - return false; - } - } - - return true; -} - -static void -sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) { - for (unsigned i = 0; i < demuxer->sink_count; ++i) { - struct sc_packet_sink *sink = demuxer->sinks[i]; - if (sink->ops->disable) { - sink->ops->disable(sink); - } - } -} - static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; @@ -189,7 +130,7 @@ run_demuxer(void *data) { if (raw_codec_id == 0) { LOGW("Demuxer '%s': stream explicitly disabled by the device", demuxer->name); - sc_demuxer_disable_sinks(demuxer); + sc_packet_source_sinks_disable(&demuxer->packet_source); status = SC_DEMUXER_STATUS_DISABLED; goto end; } @@ -204,7 +145,7 @@ run_demuxer(void *data) { if (codec_id == AV_CODEC_ID_NONE) { LOGE("Demuxer '%s': stream disabled due to unsupported codec", demuxer->name); - sc_demuxer_disable_sinks(demuxer); + sc_packet_source_sinks_disable(&demuxer->packet_source); goto end; } @@ -212,11 +153,11 @@ run_demuxer(void *data) { if (!codec) { LOGE("Demuxer '%s': stream disabled due to missing decoder", demuxer->name); - sc_demuxer_disable_sinks(demuxer); + sc_packet_source_sinks_disable(&demuxer->packet_source); goto end; } - if (!sc_demuxer_open_sinks(demuxer, codec)) { + if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec)) { goto end; } @@ -253,10 +194,10 @@ run_demuxer(void *data) { } } - ok = sc_demuxer_push_packet(demuxer, packet); + ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet); av_packet_unref(packet); if (!ok) { - // cannot process packet (error already logged) + // The sink already logged its concrete error break; } } @@ -269,7 +210,7 @@ run_demuxer(void *data) { av_packet_free(&packet); finally_close_sinks: - sc_demuxer_close_sinks(demuxer); + sc_packet_source_sinks_close(&demuxer->packet_source); end: demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata); @@ -283,7 +224,7 @@ sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, demuxer->name = name; // statically allocated demuxer->socket = socket; - demuxer->sink_count = 0; + sc_packet_source_init(&demuxer->packet_source); assert(cbs && cbs->on_ended); @@ -291,14 +232,6 @@ sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, demuxer->cbs_userdata = cbs_userdata; } -void -sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) { - assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS); - assert(sink); - assert(sink->ops); - demuxer->sinks[demuxer->sink_count++] = sink; -} - bool sc_demuxer_start(struct sc_demuxer *demuxer) { LOGD("Demuxer '%s': starting thread", demuxer->name); diff --git a/app/src/demuxer.h b/app/src/demuxer.h index d0e41add..5587d12d 100644 --- a/app/src/demuxer.h +++ b/app/src/demuxer.h @@ -8,21 +8,19 @@ #include #include +#include "trait/packet_source.h" #include "trait/packet_sink.h" #include "util/net.h" #include "util/thread.h" -#define SC_DEMUXER_MAX_SINKS 2 - struct sc_demuxer { + struct sc_packet_source packet_source; // packet source trait + const char *name; // must be statically allocated (e.g. a string literal) sc_socket socket; sc_thread thread; - struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS]; - unsigned sink_count; - const struct sc_demuxer_callbacks *cbs; void *cbs_userdata; }; @@ -43,9 +41,6 @@ void sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket, const struct sc_demuxer_callbacks *cbs, void *cbs_userdata); -void -sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink); - bool sc_demuxer_start(struct sc_demuxer *demuxer); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4355d71b..d9625a44 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -444,11 +444,13 @@ scrcpy(struct scrcpy_options *options) { #endif if (needs_video_decoder) { sc_decoder_init(&s->video_decoder, "video"); - sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink); + sc_packet_source_add_sink(&s->video_demuxer.packet_source, + &s->video_decoder.packet_sink); } if (needs_audio_decoder) { sc_decoder_init(&s->audio_decoder, "audio"); - sc_demuxer_add_sink(&s->audio_demuxer, &s->audio_decoder.packet_sink); + sc_packet_source_add_sink(&s->audio_demuxer.packet_source, + &s->audio_decoder.packet_sink); } if (options->record_filename) { @@ -467,10 +469,11 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.video_packet_sink); + sc_packet_source_add_sink(&s->video_demuxer.packet_source, + &s->recorder.video_packet_sink); if (options->audio) { - sc_demuxer_add_sink(&s->audio_demuxer, - &s->recorder.audio_packet_sink); + sc_packet_source_add_sink(&s->audio_demuxer.packet_source, + &s->recorder.audio_packet_sink); } } From 6543964f12b9089d2b5cf57a9aab7e7fe624842f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 09:25:25 +0100 Subject: [PATCH 0769/1133] Introduce frame source trait There was a frame sink trait, implemented by components able to receive AVFrames, but each frame source had to manually send frame to sinks. In order to mutualise sink management, add a frame sink trait. --- app/meson.build | 1 + app/src/trait/frame_source.c | 59 ++++++++++++++++++++++++++++++++++++ app/src/trait/frame_source.h | 38 +++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 app/src/trait/frame_source.c create mode 100644 app/src/trait/frame_source.h diff --git a/app/meson.build b/app/meson.build index 82105876..9f73b434 100644 --- a/app/meson.build +++ b/app/meson.build @@ -30,6 +30,7 @@ src = [ 'src/server.c', 'src/version.c', 'src/video_buffer.c', + 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', 'src/util/bytebuf.c', diff --git a/app/src/trait/frame_source.c b/app/src/trait/frame_source.c new file mode 100644 index 00000000..416eccd9 --- /dev/null +++ b/app/src/trait/frame_source.c @@ -0,0 +1,59 @@ +#include "frame_source.h" + +void +sc_frame_source_init(struct sc_frame_source *source) { + source->sink_count = 0; +} + +void +sc_frame_source_add_sink(struct sc_frame_source *source, + struct sc_frame_sink *sink) { + assert(source->sink_count < SC_FRAME_SOURCE_MAX_SINKS); + assert(sink); + assert(sink->ops); + source->sinks[source->sink_count++] = sink; +} + +static void +sc_frame_source_sinks_close_firsts(struct sc_frame_source *source, + unsigned count) { + while (count) { + struct sc_frame_sink *sink = source->sinks[--count]; + sink->ops->close(sink); + } +} + +bool +sc_frame_source_sinks_open(struct sc_frame_source *source, + const AVCodecContext *ctx) { + assert(source->sink_count); + for (unsigned i = 0; i < source->sink_count; ++i) { + struct sc_frame_sink *sink = source->sinks[i]; + if (!sink->ops->open(sink, ctx)) { + sc_frame_source_sinks_close_firsts(source, i); + return false; + } + } + + return true; +} + +void +sc_frame_source_sinks_close(struct sc_frame_source *source) { + assert(source->sink_count); + sc_frame_source_sinks_close_firsts(source, source->sink_count); +} + +bool +sc_frame_source_sinks_push(struct sc_frame_source *source, + const AVFrame *frame) { + assert(source->sink_count); + for (unsigned i = 0; i < source->sink_count; ++i) { + struct sc_frame_sink *sink = source->sinks[i]; + if (!sink->ops->push(sink, frame)) { + return false; + } + } + + return true; +} diff --git a/app/src/trait/frame_source.h b/app/src/trait/frame_source.h new file mode 100644 index 00000000..94222af0 --- /dev/null +++ b/app/src/trait/frame_source.h @@ -0,0 +1,38 @@ +#ifndef SC_FRAME_SOURCE_H +#define SC_FRAME_SOURCE_H + +#include "common.h" + +#include "frame_sink.h" + +#define SC_FRAME_SOURCE_MAX_SINKS 2 + +/** + * Frame source trait + * + * Component able to send AVFrames should implement this trait. + */ +struct sc_frame_source { + struct sc_frame_sink *sinks[SC_FRAME_SOURCE_MAX_SINKS]; + unsigned sink_count; +}; + +void +sc_frame_source_init(struct sc_frame_source *source); + +void +sc_frame_source_add_sink(struct sc_frame_source *source, + struct sc_frame_sink *sink); + +bool +sc_frame_source_sinks_open(struct sc_frame_source *source, + const AVCodecContext *ctx); + +void +sc_frame_source_sinks_close(struct sc_frame_source *source); + +bool +sc_frame_source_sinks_push(struct sc_frame_source *source, + const AVFrame *frame); + +#endif From 974227a3fcfa5b60921aeb71d43da4ac84c73d91 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 09:37:36 +0100 Subject: [PATCH 0770/1133] Use frame source trait in decoder --- app/src/decoder.c | 56 +++++------------------------------------------ app/src/decoder.h | 10 ++------- app/src/scrcpy.c | 6 +++-- 3 files changed, 12 insertions(+), 60 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index e4d59628..2931c1ec 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -11,32 +11,6 @@ /** Downcast packet_sink to decoder */ #define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink) -static void -sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) { - while (count) { - struct sc_frame_sink *sink = decoder->sinks[--count]; - sink->ops->close(sink); - } -} - -static inline void -sc_decoder_close_sinks(struct sc_decoder *decoder) { - sc_decoder_close_first_sinks(decoder, decoder->sink_count); -} - -static bool -sc_decoder_open_sinks(struct sc_decoder *decoder, const AVCodecContext *ctx) { - for (unsigned i = 0; i < decoder->sink_count; ++i) { - struct sc_frame_sink *sink = decoder->sinks[i]; - if (!sink->ops->open(sink, ctx)) { - sc_decoder_close_first_sinks(decoder, i); - return false; - } - } - - return true; -} - static bool sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder->codec_ctx = avcodec_alloc_context3(codec); @@ -66,7 +40,8 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { return false; } - if (!sc_decoder_open_sinks(decoder, decoder->codec_ctx)) { + if (!sc_frame_source_sinks_open(&decoder->frame_source, + decoder->codec_ctx)) { av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); @@ -78,24 +53,12 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { static void sc_decoder_close(struct sc_decoder *decoder) { - sc_decoder_close_sinks(decoder); + sc_frame_source_sinks_close(&decoder->frame_source); av_frame_free(&decoder->frame); avcodec_close(decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx); } -static bool -push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) { - for (unsigned i = 0; i < decoder->sink_count; ++i) { - struct sc_frame_sink *sink = decoder->sinks[i]; - if (!sink->ops->push(sink, frame)) { - return false; - } - } - - return true; -} - static bool sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { bool is_config = packet->pts == AV_NOPTS_VALUE; @@ -124,7 +87,8 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { } // a frame was received - bool ok = push_frame_to_sinks(decoder, decoder->frame); + bool ok = sc_frame_source_sinks_push(&decoder->frame_source, + decoder->frame); // A frame lost should not make the whole pipeline fail. The error, if // any, is already logged. (void) ok; @@ -157,7 +121,7 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink, void sc_decoder_init(struct sc_decoder *decoder, const char *name) { decoder->name = name; // statically allocated - decoder->sink_count = 0; + sc_frame_source_init(&decoder->frame_source); static const struct sc_packet_sink_ops ops = { .open = sc_decoder_packet_sink_open, @@ -167,11 +131,3 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) { decoder->packet_sink.ops = &ops; } - -void -sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) { - assert(decoder->sink_count < SC_DECODER_MAX_SINKS); - assert(sink); - assert(sink->ops); - decoder->sinks[decoder->sink_count++] = sink; -} diff --git a/app/src/decoder.h b/app/src/decoder.h index aace1af6..87aaf6a2 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -3,22 +3,19 @@ #include "common.h" +#include "trait/frame_source.h" #include "trait/packet_sink.h" #include #include #include -#define SC_DECODER_MAX_SINKS 2 - struct sc_decoder { struct sc_packet_sink packet_sink; // packet sink trait + struct sc_frame_source frame_source; // frame source trait const char *name; // must be statically allocated (e.g. a string literal) - struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS]; - unsigned sink_count; - AVCodecContext *codec_ctx; AVFrame *frame; }; @@ -27,7 +24,4 @@ struct sc_decoder { void sc_decoder_init(struct sc_decoder *decoder, const char *name); -void -sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink); - #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d9625a44..54858c01 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -665,7 +665,8 @@ aoa_hid_end: } screen_initialized = true; - sc_decoder_add_sink(&s->video_decoder, &s->screen.frame_sink); + sc_frame_source_add_sink(&s->video_decoder.frame_source, + &s->screen.frame_sink); } #ifdef HAVE_V4L2 @@ -675,7 +676,8 @@ aoa_hid_end: goto end; } - sc_decoder_add_sink(&s->video_decoder, &s->v4l2_sink.frame_sink); + sc_frame_source_add_sink(&s->video_decoder.frame_source, + &s->v4l2_sink.frame_sink); v4l2_sink_initialized = true; } From 1230149fdd73e9e90230b0f592612edbe078650a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 21:30:24 +0100 Subject: [PATCH 0771/1133] Use delay buffer as a frame source/sink The components needing delayed frames (sc_screen and sc_v4l2_sink) managed a sc_video_buffer instance, which itself embedded a sc_frame_buffer instance (to keep only the most recent frame). In theory, these components should not be aware of delaying: they should just receive AVFrames later, and only handle a sc_frame_buffer. Therefore, refactor sc_delay_buffer as a frame source (it consumes) frames) and a frame sink (it produces frames, after some delay), and plug an instance in the pipeline only when a delay is requested. This also removes the need for a specific sc_video_buffer. PR #3757 --- app/meson.build | 1 - app/src/decoder.c | 1 - app/src/delay_buffer.c | 158 ++++++++++++++++++++--------------------- app/src/delay_buffer.h | 33 +++------ app/src/scrcpy.c | 26 +++++-- app/src/screen.c | 40 +++-------- app/src/screen.h | 6 +- app/src/v4l2_sink.c | 61 ++++++---------- app/src/v4l2_sink.h | 7 +- app/src/video_buffer.c | 84 ---------------------- app/src/video_buffer.h | 47 ------------ 11 files changed, 147 insertions(+), 317 deletions(-) delete mode 100644 app/src/video_buffer.c delete mode 100644 app/src/video_buffer.h diff --git a/app/meson.build b/app/meson.build index 9f73b434..392fa6d0 100644 --- a/app/meson.build +++ b/app/meson.build @@ -29,7 +29,6 @@ src = [ 'src/screen.c', 'src/server.c', 'src/version.c', - 'src/video_buffer.c', 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', diff --git a/app/src/decoder.c b/app/src/decoder.c index 2931c1ec..a8168f66 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -4,7 +4,6 @@ #include #include "events.h" -#include "video_buffer.h" #include "trait/frame_sink.h" #include "util/log.h" diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 95d47c9c..72af3672 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -10,6 +10,9 @@ #define SC_BUFFERING_NDEBUG // comment to debug +/** Downcast frame_sink to sc_delay_buffer */ +#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink) + static bool sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) { dframe->frame = av_frame_alloc(); @@ -33,11 +36,6 @@ sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) { av_frame_free(&dframe->frame); } -static bool -sc_delay_buffer_offer(struct sc_delay_buffer *db, const AVFrame *frame) { - return db->cbs->on_new_frame(db, frame, db->cbs_userdata); -} - static int run_buffering(void *data) { struct sc_delay_buffer *db = data; @@ -87,12 +85,12 @@ run_buffering(void *data) { pts, dframe.push_date, sc_tick_now()); #endif - bool ok = sc_delay_buffer_offer(db, dframe.frame); + bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame); sc_delayed_frame_destroy(&dframe); if (!ok) { LOGE("Delayed frame could not be pushed, stopping"); sc_mutex_lock(&db->b.mutex); - // Prevent to push any new packet + // Prevent to push any new frame db->b.stopped = true; sc_mutex_unlock(&db->b.mutex); goto stopped; @@ -113,92 +111,77 @@ stopped: return 0; } -bool -sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, - const struct sc_delay_buffer_callbacks *cbs, - void *cbs_userdata) { - assert(delay >= 0); - - if (delay) { - bool ok = sc_mutex_init(&db->b.mutex); - if (!ok) { - return false; - } - - ok = sc_cond_init(&db->b.queue_cond); - if (!ok) { - sc_mutex_destroy(&db->b.mutex); - return false; - } +static bool +sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink, + const AVCodecContext *ctx) { + struct sc_delay_buffer *db = DOWNCAST(sink); + (void) ctx; - ok = sc_cond_init(&db->b.wait_cond); - if (!ok) { - sc_cond_destroy(&db->b.queue_cond); - sc_mutex_destroy(&db->b.mutex); - return false; - } + bool ok = sc_mutex_init(&db->b.mutex); + if (!ok) { + return false; + } - sc_clock_init(&db->b.clock); - sc_vecdeque_init(&db->b.queue); + ok = sc_cond_init(&db->b.queue_cond); + if (!ok) { + goto error_destroy_mutex; } - assert(cbs); - assert(cbs->on_new_frame); + ok = sc_cond_init(&db->b.wait_cond); + if (!ok) { + goto error_destroy_queue_cond; + } - db->delay = delay; - db->cbs = cbs; - db->cbs_userdata = cbs_userdata; + sc_clock_init(&db->b.clock); + sc_vecdeque_init(&db->b.queue); - return true; -} + if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) { + goto error_destroy_wait_cond; + } -bool -sc_delay_buffer_start(struct sc_delay_buffer *db) { - if (db->delay) { - bool ok = - sc_thread_create(&db->b.thread, run_buffering, "scrcpy-dbuf", db); - if (!ok) { - LOGE("Could not start buffering thread"); - return false; - } + ok = sc_thread_create(&db->b.thread, run_buffering, "scrcpy-dbuf", db); + if (!ok) { + LOGE("Could not start buffering thread"); + goto error_close_sinks; } return true; -} -void -sc_delay_buffer_stop(struct sc_delay_buffer *db) { - if (db->delay) { - sc_mutex_lock(&db->b.mutex); - db->b.stopped = true; - sc_cond_signal(&db->b.queue_cond); - sc_cond_signal(&db->b.wait_cond); - sc_mutex_unlock(&db->b.mutex); - } -} +error_close_sinks: + sc_frame_source_sinks_close(&db->frame_source); +error_destroy_wait_cond: + sc_cond_destroy(&db->b.wait_cond); +error_destroy_queue_cond: + sc_cond_destroy(&db->b.queue_cond); +error_destroy_mutex: + sc_mutex_destroy(&db->b.mutex); -void -sc_delay_buffer_join(struct sc_delay_buffer *db) { - if (db->delay) { - sc_thread_join(&db->b.thread, NULL); - } + return false; } -void -sc_delay_buffer_destroy(struct sc_delay_buffer *db) { - if (db->delay) { - sc_cond_destroy(&db->b.wait_cond); - sc_cond_destroy(&db->b.queue_cond); - sc_mutex_destroy(&db->b.mutex); - } +static void +sc_delay_buffer_frame_sink_close(struct sc_frame_sink *sink) { + struct sc_delay_buffer *db = DOWNCAST(sink); + + sc_mutex_lock(&db->b.mutex); + db->b.stopped = true; + sc_cond_signal(&db->b.queue_cond); + sc_cond_signal(&db->b.wait_cond); + sc_mutex_unlock(&db->b.mutex); + + sc_thread_join(&db->b.thread, NULL); + + sc_frame_source_sinks_close(&db->frame_source); + + sc_cond_destroy(&db->b.wait_cond); + sc_cond_destroy(&db->b.queue_cond); + sc_mutex_destroy(&db->b.mutex); } -bool -sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame) { - if (!db->delay) { - // No buffering - return sc_delay_buffer_offer(db, frame); - } +static bool +sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, + const AVFrame *frame) { + struct sc_delay_buffer *db = DOWNCAST(sink); sc_mutex_lock(&db->b.mutex); @@ -213,11 +196,11 @@ sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame) { if (db->b.clock.count == 1) { sc_mutex_unlock(&db->b.mutex); - // First frame, offer it immediately, for two reasons: + // First frame, push it immediately, for two reasons: // - not to delay the opening of the scrcpy window // - the buffering estimation needs at least two clock points, so it // could not handle the first frame - return sc_delay_buffer_offer(db, frame); + return sc_frame_source_sinks_push(&db->frame_source, frame); } struct sc_delayed_frame dframe; @@ -244,3 +227,20 @@ sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame) { return true; } + +void +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay) { + assert(delay > 0); + + db->delay = delay; + + sc_frame_source_init(&db->frame_source); + + static const struct sc_frame_sink_ops ops = { + .open = sc_delay_buffer_frame_sink_open, + .close = sc_delay_buffer_frame_sink_close, + .push = sc_delay_buffer_frame_sink_push, + }; + + db->frame_sink.ops = &ops; +} diff --git a/app/src/delay_buffer.h b/app/src/delay_buffer.h index 9e5347c7..4cb981c8 100644 --- a/app/src/delay_buffer.h +++ b/app/src/delay_buffer.h @@ -6,6 +6,8 @@ #include #include "clock.h" +#include "trait/frame_source.h" +#include "trait/frame_sink.h" #include "util/thread.h" #include "util/tick.h" #include "util/vecdeque.h" @@ -23,9 +25,11 @@ struct sc_delayed_frame { struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame); struct sc_delay_buffer { + struct sc_frame_source frame_source; // frame source trait + struct sc_frame_sink frame_sink; // frame sink trait + sc_tick delay; - // only if delay > 0 struct { sc_thread thread; sc_mutex mutex; @@ -36,9 +40,6 @@ struct sc_delay_buffer { struct sc_delayed_frame_queue queue; bool stopped; } b; // buffering - - const struct sc_delay_buffer_callbacks *cbs; - void *cbs_userdata; }; struct sc_delay_buffer_callbacks { @@ -46,24 +47,12 @@ struct sc_delay_buffer_callbacks { void *userdata); }; -bool -sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, - const struct sc_delay_buffer_callbacks *cbs, - void *cbs_userdata); - -bool -sc_delay_buffer_start(struct sc_delay_buffer *db); - +/** + * Initialize a delay buffer. + * + * \param delay a (strictly) positive delay + */ void -sc_delay_buffer_stop(struct sc_delay_buffer *db); - -void -sc_delay_buffer_join(struct sc_delay_buffer *db); - -void -sc_delay_buffer_destroy(struct sc_delay_buffer *db); - -bool -sc_delay_buffer_push(struct sc_delay_buffer *db, const AVFrame *frame); +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 54858c01..2688cab6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -15,6 +15,7 @@ #include "controller.h" #include "decoder.h" +#include "delay_buffer.h" #include "demuxer.h" #include "events.h" #include "file_pusher.h" @@ -45,8 +46,10 @@ struct scrcpy { struct sc_decoder video_decoder; struct sc_decoder audio_decoder; struct sc_recorder recorder; + struct sc_delay_buffer display_buffer; #ifdef HAVE_V4L2 struct sc_v4l2_sink v4l2_sink; + struct sc_delay_buffer v4l2_buffer; #endif struct sc_controller controller; struct sc_file_pusher file_pusher; @@ -657,7 +660,6 @@ aoa_hid_end: .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, .start_fps_counter = options->start_fps_counter, - .buffering_time = options->display_buffer, }; if (!sc_screen_init(&s->screen, &screen_params)) { @@ -665,19 +667,31 @@ aoa_hid_end: } screen_initialized = true; - sc_frame_source_add_sink(&s->video_decoder.frame_source, - &s->screen.frame_sink); + struct sc_frame_source *src = &s->video_decoder.frame_source; + if (options->display_buffer) { + sc_delay_buffer_init(&s->display_buffer, options->display_buffer); + sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); + src = &s->display_buffer.frame_source; + } + + sc_frame_source_add_sink(src, &s->screen.frame_sink); } #ifdef HAVE_V4L2 if (options->v4l2_device) { if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, - info->frame_size, options->v4l2_buffer)) { + info->frame_size)) { goto end; } - sc_frame_source_add_sink(&s->video_decoder.frame_source, - &s->v4l2_sink.frame_sink); + struct sc_frame_source *src = &s->video_decoder.frame_source; + if (options->v4l2_buffer) { + sc_delay_buffer_init(&s->v4l2_buffer, options->v4l2_buffer); + sc_frame_source_add_sink(src, &s->v4l2_buffer.frame_sink); + src = &s->v4l2_buffer.frame_source; + } + + sc_frame_source_add_sink(src, &s->v4l2_sink.frame_sink); v4l2_sink_initialized = true; } diff --git a/app/src/screen.c b/app/src/screen.c index ce2e74bb..b814ada1 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -7,7 +7,6 @@ #include "events.h" #include "icon.h" #include "options.h" -#include "video_buffer.h" #include "util/log.h" #define DISPLAY_MARGINS 96 @@ -359,14 +358,12 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) { static bool sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { struct sc_screen *screen = DOWNCAST(sink); - return sc_video_buffer_push(&screen->vb, frame); -} -static bool -sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, - void *userdata) { - (void) vb; - struct sc_screen *screen = userdata; + bool previous_skipped; + bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped); + if (!ok) { + return false; + } if (previous_skipped) { sc_fps_counter_add_skipped_frame(&screen->fps_counter); @@ -404,23 +401,13 @@ sc_screen_init(struct sc_screen *screen, screen->req.fullscreen = params->fullscreen; screen->req.start_fps_counter = params->start_fps_counter; - static const struct sc_video_buffer_callbacks cbs = { - .on_new_frame = sc_video_buffer_on_new_frame, - }; - - bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs, - screen); + bool ok = sc_frame_buffer_init(&screen->fb); if (!ok) { return false; } - ok = sc_video_buffer_start(&screen->vb); - if (!ok) { - goto error_destroy_video_buffer; - } - if (!sc_fps_counter_init(&screen->fps_counter)) { - goto error_stop_and_join_video_buffer; + goto error_destroy_frame_buffer; } screen->frame_size = params->frame_size; @@ -552,11 +539,8 @@ error_destroy_window: SDL_DestroyWindow(screen->window); error_destroy_fps_counter: sc_fps_counter_destroy(&screen->fps_counter); -error_stop_and_join_video_buffer: - sc_video_buffer_stop(&screen->vb); - sc_video_buffer_join(&screen->vb); -error_destroy_video_buffer: - sc_video_buffer_destroy(&screen->vb); +error_destroy_frame_buffer: + sc_frame_buffer_destroy(&screen->fb); return false; } @@ -593,13 +577,11 @@ sc_screen_hide_window(struct sc_screen *screen) { void sc_screen_interrupt(struct sc_screen *screen) { - sc_video_buffer_stop(&screen->vb); sc_fps_counter_interrupt(&screen->fps_counter); } void sc_screen_join(struct sc_screen *screen) { - sc_video_buffer_join(&screen->vb); sc_fps_counter_join(&screen->fps_counter); } @@ -613,7 +595,7 @@ sc_screen_destroy(struct sc_screen *screen) { SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); sc_fps_counter_destroy(&screen->fps_counter); - sc_video_buffer_destroy(&screen->vb); + sc_frame_buffer_destroy(&screen->fb); } static void @@ -719,7 +701,7 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) { static bool sc_screen_update_frame(struct sc_screen *screen) { av_frame_unref(screen->frame); - sc_video_buffer_consume(&screen->vb, screen->frame); + sc_frame_buffer_consume(&screen->fb, screen->frame); AVFrame *frame = screen->frame; sc_fps_counter_add_rendered_frame(&screen->fps_counter); diff --git a/app/src/screen.h b/app/src/screen.h index 0952c79c..28afea40 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -10,12 +10,12 @@ #include "controller.h" #include "coords.h" #include "fps_counter.h" +#include "frame_buffer.h" #include "input_manager.h" #include "opengl.h" #include "trait/key_processor.h" #include "trait/frame_sink.h" #include "trait/mouse_processor.h" -#include "video_buffer.h" struct sc_screen { struct sc_frame_sink frame_sink; // frame sink trait @@ -25,7 +25,7 @@ struct sc_screen { #endif struct sc_input_manager im; - struct sc_video_buffer vb; + struct sc_frame_buffer fb; struct sc_fps_counter fps_counter; // The initial requested window properties @@ -93,8 +93,6 @@ struct sc_screen_params { bool fullscreen; bool start_fps_counter; - - sc_tick buffering_time; }; // initialize screen, create window, renderer and texture (window is hidden) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 5dfe37bc..fe11614a 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -126,7 +126,7 @@ run_v4l2_sink(void *data) { vs->has_frame = false; sc_mutex_unlock(&vs->mutex); - sc_video_buffer_consume(&vs->vb, vs->frame); + sc_frame_buffer_consume(&vs->fb, vs->frame); bool ok = encode_and_write_frame(vs, vs->frame); av_frame_unref(vs->frame); @@ -141,44 +141,19 @@ run_v4l2_sink(void *data) { return 0; } -static bool -sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, - void *userdata) { - (void) vb; - struct sc_v4l2_sink *vs = userdata; - - if (!previous_skipped) { - sc_mutex_lock(&vs->mutex); - vs->has_frame = true; - sc_cond_signal(&vs->cond); - sc_mutex_unlock(&vs->mutex); - } - - return true; -} - static bool sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P); (void) ctx; - static const struct sc_video_buffer_callbacks cbs = { - .on_new_frame = sc_video_buffer_on_new_frame, - }; - - bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs); + bool ok = sc_frame_buffer_init(&vs->fb); if (!ok) { return false; } - ok = sc_video_buffer_start(&vs->vb); - if (!ok) { - goto error_video_buffer_destroy; - } - ok = sc_mutex_init(&vs->mutex); if (!ok) { - goto error_video_buffer_stop_and_join; + goto error_frame_buffer_destroy; } ok = sc_cond_init(&vs->cond); @@ -303,11 +278,8 @@ error_cond_destroy: sc_cond_destroy(&vs->cond); error_mutex_destroy: sc_mutex_destroy(&vs->mutex); -error_video_buffer_stop_and_join: - sc_video_buffer_stop(&vs->vb); - sc_video_buffer_join(&vs->vb); -error_video_buffer_destroy: - sc_video_buffer_destroy(&vs->vb); +error_frame_buffer_destroy: + sc_frame_buffer_destroy(&vs->fb); return false; } @@ -319,10 +291,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { sc_cond_signal(&vs->cond); sc_mutex_unlock(&vs->mutex); - sc_video_buffer_stop(&vs->vb); - sc_thread_join(&vs->thread, NULL); - sc_video_buffer_join(&vs->vb); av_packet_free(&vs->packet); av_frame_free(&vs->frame); @@ -332,12 +301,25 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { avformat_free_context(vs->format_ctx); sc_cond_destroy(&vs->cond); sc_mutex_destroy(&vs->mutex); - sc_video_buffer_destroy(&vs->vb); + sc_frame_buffer_destroy(&vs->fb); } static bool sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { - return sc_video_buffer_push(&vs->vb, frame); + bool previous_skipped; + bool ok = sc_frame_buffer_push(&vs->fb, frame, &previous_skipped); + if (!ok) { + return false; + } + + if (!previous_skipped) { + sc_mutex_lock(&vs->mutex); + vs->has_frame = true; + sc_cond_signal(&vs->cond); + sc_mutex_unlock(&vs->mutex); + } + + return true; } static bool @@ -360,7 +342,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 sc_size frame_size) { vs->device_name = strdup(device_name); if (!vs->device_name) { LOGE("Could not strdup v4l2 device name"); @@ -368,7 +350,6 @@ sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, } vs->frame_size = frame_size; - vs->buffering_time = buffering_time; static const struct sc_frame_sink_ops ops = { .open = sc_v4l2_frame_sink_open, diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index 339a61f2..789e31c3 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -8,19 +8,18 @@ #include "coords.h" #include "trait/frame_sink.h" -#include "video_buffer.h" +#include "frame_buffer.h" #include "util/tick.h" struct sc_v4l2_sink { struct sc_frame_sink frame_sink; // frame sink trait - struct sc_video_buffer vb; + struct sc_frame_buffer fb; AVFormatContext *format_ctx; AVCodecContext *encoder_ctx; char *device_name; struct sc_size frame_size; - sc_tick buffering_time; sc_thread thread; sc_mutex mutex; @@ -35,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 sc_size frame_size); void sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c deleted file mode 100644 index da47a0b5..00000000 --- a/app/src/video_buffer.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "video_buffer.h" - -#include -#include - -#include -#include - -#include "util/log.h" - -static bool -sc_delay_buffer_on_new_frame(struct sc_delay_buffer *db, const AVFrame *frame, - void *userdata) { - (void) db; - - struct sc_video_buffer *vb = userdata; - - bool previous_skipped; - bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped); - if (!ok) { - return false; - } - - return vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); -} - -bool -sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick delay, - const struct sc_video_buffer_callbacks *cbs, - void *cbs_userdata) { - bool ok = sc_frame_buffer_init(&vb->fb); - if (!ok) { - return false; - } - - static const struct sc_delay_buffer_callbacks db_cbs = { - .on_new_frame = sc_delay_buffer_on_new_frame, - }; - - ok = sc_delay_buffer_init(&vb->db, delay, &db_cbs, vb); - if (!ok) { - sc_frame_buffer_destroy(&vb->fb); - return false; - } - - assert(cbs); - assert(cbs->on_new_frame); - - vb->cbs = cbs; - vb->cbs_userdata = cbs_userdata; - - return true; -} - -bool -sc_video_buffer_start(struct sc_video_buffer *vb) { - return sc_delay_buffer_start(&vb->db); -} - -void -sc_video_buffer_stop(struct sc_video_buffer *vb) { - return sc_delay_buffer_stop(&vb->db); -} - -void -sc_video_buffer_join(struct sc_video_buffer *vb) { - return sc_delay_buffer_join(&vb->db); -} - -void -sc_video_buffer_destroy(struct sc_video_buffer *vb) { - sc_frame_buffer_destroy(&vb->fb); - sc_delay_buffer_destroy(&vb->db); -} - -bool -sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { - return sc_delay_buffer_push(&vb->db, frame); -} - -void -sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) { - sc_frame_buffer_consume(&vb->fb, dst); -} diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h deleted file mode 100644 index ebca3915..00000000 --- a/app/src/video_buffer.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SC_VIDEO_BUFFER_H -#define SC_VIDEO_BUFFER_H - -#include "common.h" - -#include - -#include "delay_buffer.h" -#include "frame_buffer.h" - -struct sc_video_buffer { - struct sc_delay_buffer db; - struct sc_frame_buffer fb; - - const struct sc_video_buffer_callbacks *cbs; - void *cbs_userdata; -}; - -struct sc_video_buffer_callbacks { - bool (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped, - void *userdata); -}; - -bool -sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick delay, - const struct sc_video_buffer_callbacks *cbs, - void *cbs_userdata); - -bool -sc_video_buffer_start(struct sc_video_buffer *vb); - -void -sc_video_buffer_stop(struct sc_video_buffer *vb); - -void -sc_video_buffer_join(struct sc_video_buffer *vb); - -void -sc_video_buffer_destroy(struct sc_video_buffer *vb); - -bool -sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame); - -void -sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst); - -#endif From 48a537d45c48bdff45c85197584f5570e9d9dc57 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 21:30:24 +0100 Subject: [PATCH 0772/1133] Remove anonymous struct in delay buffer For clarity, the fields used only when a delay was set were wrapped in an anonymous structure. Now that the delay buffer has been extracted to a separate component, the delay is necessarily set (it may not be 0), so the fields are always used. PR #3757 --- app/src/delay_buffer.c | 94 +++++++++++++++++++++--------------------- app/src/delay_buffer.h | 18 ++++---- 2 files changed, 55 insertions(+), 57 deletions(-) diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 72af3672..2694eb01 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -43,37 +43,37 @@ run_buffering(void *data) { assert(db->delay > 0); for (;;) { - sc_mutex_lock(&db->b.mutex); + sc_mutex_lock(&db->mutex); - while (!db->b.stopped && sc_vecdeque_is_empty(&db->b.queue)) { - sc_cond_wait(&db->b.queue_cond, &db->b.mutex); + while (!db->stopped && sc_vecdeque_is_empty(&db->queue)) { + sc_cond_wait(&db->queue_cond, &db->mutex); } - if (db->b.stopped) { - sc_mutex_unlock(&db->b.mutex); + if (db->stopped) { + sc_mutex_unlock(&db->mutex); goto stopped; } - struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->b.queue); + struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue); sc_tick max_deadline = sc_tick_now() + db->delay; // PTS (written by the server) are expressed in microseconds sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts); bool timed_out = false; - while (!db->b.stopped && !timed_out) { - sc_tick deadline = sc_clock_to_system_time(&db->b.clock, pts) + while (!db->stopped && !timed_out) { + sc_tick deadline = sc_clock_to_system_time(&db->clock, pts) + db->delay; if (deadline > max_deadline) { deadline = max_deadline; } timed_out = - !sc_cond_timedwait(&db->b.wait_cond, &db->b.mutex, deadline); + !sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline); } - bool stopped = db->b.stopped; - sc_mutex_unlock(&db->b.mutex); + bool stopped = db->stopped; + sc_mutex_unlock(&db->mutex); if (stopped) { sc_delayed_frame_destroy(&dframe); @@ -89,20 +89,20 @@ run_buffering(void *data) { sc_delayed_frame_destroy(&dframe); if (!ok) { LOGE("Delayed frame could not be pushed, stopping"); - sc_mutex_lock(&db->b.mutex); + sc_mutex_lock(&db->mutex); // Prevent to push any new frame - db->b.stopped = true; - sc_mutex_unlock(&db->b.mutex); + db->stopped = true; + sc_mutex_unlock(&db->mutex); goto stopped; } } stopped: - assert(db->b.stopped); + assert(db->stopped); // Flush queue - while (!sc_vecdeque_is_empty(&db->b.queue)) { - struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->b.queue); + while (!sc_vecdeque_is_empty(&db->queue)) { + struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue); sc_delayed_frame_destroy(dframe); } @@ -117,29 +117,29 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink, struct sc_delay_buffer *db = DOWNCAST(sink); (void) ctx; - bool ok = sc_mutex_init(&db->b.mutex); + bool ok = sc_mutex_init(&db->mutex); if (!ok) { return false; } - ok = sc_cond_init(&db->b.queue_cond); + ok = sc_cond_init(&db->queue_cond); if (!ok) { goto error_destroy_mutex; } - ok = sc_cond_init(&db->b.wait_cond); + ok = sc_cond_init(&db->wait_cond); if (!ok) { goto error_destroy_queue_cond; } - sc_clock_init(&db->b.clock); - sc_vecdeque_init(&db->b.queue); + sc_clock_init(&db->clock); + sc_vecdeque_init(&db->queue); if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) { goto error_destroy_wait_cond; } - ok = sc_thread_create(&db->b.thread, run_buffering, "scrcpy-dbuf", db); + ok = sc_thread_create(&db->thread, run_buffering, "scrcpy-dbuf", db); if (!ok) { LOGE("Could not start buffering thread"); goto error_close_sinks; @@ -150,11 +150,11 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink, error_close_sinks: sc_frame_source_sinks_close(&db->frame_source); error_destroy_wait_cond: - sc_cond_destroy(&db->b.wait_cond); + sc_cond_destroy(&db->wait_cond); error_destroy_queue_cond: - sc_cond_destroy(&db->b.queue_cond); + sc_cond_destroy(&db->queue_cond); error_destroy_mutex: - sc_mutex_destroy(&db->b.mutex); + sc_mutex_destroy(&db->mutex); return false; } @@ -163,19 +163,19 @@ static void sc_delay_buffer_frame_sink_close(struct sc_frame_sink *sink) { struct sc_delay_buffer *db = DOWNCAST(sink); - sc_mutex_lock(&db->b.mutex); - db->b.stopped = true; - sc_cond_signal(&db->b.queue_cond); - sc_cond_signal(&db->b.wait_cond); - sc_mutex_unlock(&db->b.mutex); + sc_mutex_lock(&db->mutex); + db->stopped = true; + sc_cond_signal(&db->queue_cond); + sc_cond_signal(&db->wait_cond); + sc_mutex_unlock(&db->mutex); - sc_thread_join(&db->b.thread, NULL); + sc_thread_join(&db->thread, NULL); sc_frame_source_sinks_close(&db->frame_source); - sc_cond_destroy(&db->b.wait_cond); - sc_cond_destroy(&db->b.queue_cond); - sc_mutex_destroy(&db->b.mutex); + sc_cond_destroy(&db->wait_cond); + sc_cond_destroy(&db->queue_cond); + sc_mutex_destroy(&db->mutex); } static bool @@ -183,19 +183,19 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { struct sc_delay_buffer *db = DOWNCAST(sink); - sc_mutex_lock(&db->b.mutex); + sc_mutex_lock(&db->mutex); - if (db->b.stopped) { - sc_mutex_unlock(&db->b.mutex); + if (db->stopped) { + sc_mutex_unlock(&db->mutex); return false; } sc_tick pts = SC_TICK_FROM_US(frame->pts); - sc_clock_update(&db->b.clock, sc_tick_now(), pts); - sc_cond_signal(&db->b.wait_cond); + sc_clock_update(&db->clock, sc_tick_now(), pts); + sc_cond_signal(&db->wait_cond); - if (db->b.clock.count == 1) { - sc_mutex_unlock(&db->b.mutex); + if (db->clock.count == 1) { + sc_mutex_unlock(&db->mutex); // First frame, push it immediately, for two reasons: // - not to delay the opening of the scrcpy window // - the buffering estimation needs at least two clock points, so it @@ -206,7 +206,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, struct sc_delayed_frame dframe; bool ok = sc_delayed_frame_init(&dframe, frame); if (!ok) { - sc_mutex_unlock(&db->b.mutex); + sc_mutex_unlock(&db->mutex); return false; } @@ -214,16 +214,16 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, dframe.push_date = sc_tick_now(); #endif - ok = sc_vecdeque_push(&db->b.queue, dframe); + ok = sc_vecdeque_push(&db->queue, dframe); if (!ok) { - sc_mutex_unlock(&db->b.mutex); + sc_mutex_unlock(&db->mutex); LOG_OOM(); return false; } - sc_cond_signal(&db->b.queue_cond); + sc_cond_signal(&db->queue_cond); - sc_mutex_unlock(&db->b.mutex); + sc_mutex_unlock(&db->mutex); return true; } diff --git a/app/src/delay_buffer.h b/app/src/delay_buffer.h index 4cb981c8..96fbaa3d 100644 --- a/app/src/delay_buffer.h +++ b/app/src/delay_buffer.h @@ -30,16 +30,14 @@ struct sc_delay_buffer { sc_tick delay; - struct { - sc_thread thread; - sc_mutex mutex; - sc_cond queue_cond; - sc_cond wait_cond; - - struct sc_clock clock; - struct sc_delayed_frame_queue queue; - bool stopped; - } b; // buffering + sc_thread thread; + sc_mutex mutex; + sc_cond queue_cond; + sc_cond wait_cond; + + struct sc_clock clock; + struct sc_delayed_frame_queue queue; + bool stopped; }; struct sc_delay_buffer_callbacks { From 9b3ca208bfe21ceb303798c2f05e2364aa364880 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 22:13:48 +0100 Subject: [PATCH 0773/1133] Accept clock estimation with a single point If there is only one point, assume the slope is 1. PR #3757 --- app/src/clock.c | 21 +++++++++++++-------- app/src/delay_buffer.c | 6 ++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/clock.c b/app/src/clock.c index bb2430fd..3e1a794d 100644 --- a/app/src/clock.c +++ b/app/src/clock.c @@ -18,7 +18,15 @@ sc_clock_init(struct sc_clock *clock) { static void sc_clock_estimate(struct sc_clock *clock, double *out_slope, sc_tick *out_offset) { - assert(clock->count > 1); // two points are necessary + assert(clock->count); + + if (clock->count == 1) { + // If there is only 1 point, we can't compute a slope. Assume it is 1. + struct sc_clock_point *single_point = &clock->right_sum; + *out_slope = 1; + *out_offset = single_point->system - single_point->stream; + return; + } struct sc_clock_point left_avg = { .system = clock->left_sum.system / (clock->count / 2), @@ -93,19 +101,16 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) { clock->head = (clock->head + 1) % SC_CLOCK_RANGE; - if (clock->count > 1) { - // Update estimation - sc_clock_estimate(clock, &clock->slope, &clock->offset); + // Update estimation + sc_clock_estimate(clock, &clock->slope, &clock->offset); #ifndef SC_CLOCK_NDEBUG - LOGD("Clock estimation: %f * pts + %" PRItick, - clock->slope, clock->offset); + LOGD("Clock estimation: %f * pts + %" PRItick, clock->slope, clock->offset); #endif - } } sc_tick sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) { - assert(clock->count > 1); // sc_clock_update() must have been called + assert(clock->count); // sc_clock_update() must have been called return (sc_tick) (stream * clock->slope) + clock->offset; } diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 2694eb01..360e2b66 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -196,10 +196,8 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, if (db->clock.count == 1) { sc_mutex_unlock(&db->mutex); - // First frame, push it immediately, for two reasons: - // - not to delay the opening of the scrcpy window - // - the buffering estimation needs at least two clock points, so it - // could not handle the first frame + // First frame, push it immediately, not to delay the opening of the + // scrcpy window return sc_frame_source_sinks_push(&db->frame_source, frame); } From e1333f6f3b29fd68239f3991271e63bff316abc0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 22:33:31 +0100 Subject: [PATCH 0774/1133] Optionally do not delay the first frame A delay buffer delayed all the frames except the first one, to open the scrcpy window immediately and get a picture. Make this feature optional, so that the delay buffer might also be used for audio (especially for simulating a high delay for debugging). PR #3757 --- app/src/delay_buffer.c | 8 ++++---- app/src/delay_buffer.h | 6 +++++- app/src/scrcpy.c | 5 +++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 360e2b66..9d4690a2 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -194,10 +194,8 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, sc_clock_update(&db->clock, sc_tick_now(), pts); sc_cond_signal(&db->wait_cond); - if (db->clock.count == 1) { + if (db->first_frame_asap && db->clock.count == 1) { sc_mutex_unlock(&db->mutex); - // First frame, push it immediately, not to delay the opening of the - // scrcpy window return sc_frame_source_sinks_push(&db->frame_source, frame); } @@ -227,10 +225,12 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, } void -sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay) { +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, + bool first_frame_asap) { assert(delay > 0); db->delay = delay; + db->first_frame_asap = first_frame_asap; sc_frame_source_init(&db->frame_source); diff --git a/app/src/delay_buffer.h b/app/src/delay_buffer.h index 96fbaa3d..53592372 100644 --- a/app/src/delay_buffer.h +++ b/app/src/delay_buffer.h @@ -29,6 +29,7 @@ struct sc_delay_buffer { struct sc_frame_sink frame_sink; // frame sink trait sc_tick delay; + bool first_frame_asap; sc_thread thread; sc_mutex mutex; @@ -49,8 +50,11 @@ struct sc_delay_buffer_callbacks { * Initialize a delay buffer. * * \param delay a (strictly) positive delay + * \param first_frame_asap if true, do not delay the first frame (useful for + a video stream). */ void -sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay); +sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay, + bool first_frame_asap); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2688cab6..dba1bad9 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -669,7 +669,8 @@ aoa_hid_end: struct sc_frame_source *src = &s->video_decoder.frame_source; if (options->display_buffer) { - sc_delay_buffer_init(&s->display_buffer, options->display_buffer); + sc_delay_buffer_init(&s->display_buffer, options->display_buffer, + true); sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); src = &s->display_buffer.frame_source; } @@ -686,7 +687,7 @@ aoa_hid_end: struct sc_frame_source *src = &s->video_decoder.frame_source; if (options->v4l2_buffer) { - sc_delay_buffer_init(&s->v4l2_buffer, options->v4l2_buffer); + sc_delay_buffer_init(&s->v4l2_buffer, options->v4l2_buffer, true); sc_frame_source_add_sink(src, &s->v4l2_buffer.frame_sink); src = &s->v4l2_buffer.frame_source; } From fbe0f951e113e6055c4365c2986663cb3ef16d0e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 00:43:20 +0100 Subject: [PATCH 0775/1133] Add audio player Play the decoded audio using SDL. The audio player frame sink receives the audio frames, resample them and write them to a byte buffer (introduced by this commit). On SDL audio callback (from an internal SDL thread), copy samples from this byte buffer to the SDL audio buffer. The byte buffer is protected by the SDL_AudioDeviceLock(), but it has been designed so that the producer and the consumer may write and read in parallel, provided that they don't access the same slices of the ring-buffer buffer. PR #3757 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- BUILD.md | 4 +- app/meson.build | 4 + app/src/audio_player.c | 409 +++++++++++++++++++++++++++++++++++++++++ app/src/audio_player.h | 78 ++++++++ app/src/decoder.c | 6 + app/src/scrcpy.c | 21 ++- app/src/util/average.c | 26 +++ app/src/util/average.h | 40 ++++ 8 files changed, 583 insertions(+), 5 deletions(-) create mode 100644 app/src/audio_player.c create mode 100644 app/src/audio_player.h create mode 100644 app/src/util/average.c create mode 100644 app/src/util/average.h diff --git a/BUILD.md b/BUILD.md index 0c708bde..51f8141e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -15,7 +15,7 @@ First, you need to install the required packages: 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-1.0-0-dev + libswresample-dev libusb-1.0-0 libusb-1.0-0-dev ``` Then clone the repo and execute the installation script @@ -94,7 +94,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 \ - libusb-1.0-0-dev + libswresample-dev libusb-1.0-0-dev # server build dependencies sudo apt install openjdk-11-jdk diff --git a/app/meson.build b/app/meson.build index 392fa6d0..723274c9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -4,6 +4,7 @@ src = [ 'src/adb/adb_device.c', 'src/adb/adb_parser.c', 'src/adb/adb_tunnel.c', + 'src/audio_player.c', 'src/cli.c', 'src/clock.c', 'src/compat.c', @@ -32,6 +33,7 @@ src = [ 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', + 'src/util/average.c', 'src/util/bytebuf.c', 'src/util/file.c', 'src/util/intmap.c', @@ -103,6 +105,7 @@ if not crossbuild_windows dependency('libavformat', version: '>= 57.33'), dependency('libavcodec', version: '>= 57.37'), dependency('libavutil'), + dependency('libswresample'), dependency('sdl2', version: '>= 2.0.5'), ] @@ -138,6 +141,7 @@ else cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir), cc.find_library('avformat-60', dirs: ffmpeg_bin_dir), cc.find_library('avutil-58', dirs: ffmpeg_bin_dir), + cc.find_library('swresample-4', dirs: ffmpeg_bin_dir), ], include_directories: include_directories(ffmpeg_include_dir) ) diff --git a/app/src/audio_player.c b/app/src/audio_player.c new file mode 100644 index 00000000..78a8ffe1 --- /dev/null +++ b/app/src/audio_player.c @@ -0,0 +1,409 @@ +#include "audio_player.h" + +#include + +#include "util/log.h" + +#define SC_AUDIO_PLAYER_NDEBUG // comment to debug + +/** Downcast frame_sink to sc_audio_player */ +#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink) + +#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT +#define SC_SDL_SAMPLE_FMT AUDIO_F32 + +#define SC_AUDIO_OUTPUT_BUFFER_SAMPLES 240 // 5ms at 48000Hz + +static inline uint32_t +bytes_to_samples(struct sc_audio_player *ap, size_t bytes) { + assert(bytes % (ap->nb_channels * ap->out_bytes_per_sample) == 0); + return bytes / (ap->nb_channels * ap->out_bytes_per_sample); +} + +static inline size_t +samples_to_bytes(struct sc_audio_player *ap, uint32_t samples) { + return samples * ap->nb_channels * ap->out_bytes_per_sample; +} + +static void SDLCALL +sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { + struct sc_audio_player *ap = userdata; + + // This callback is called with the lock used by SDL_AudioDeviceLock(), so + // the bytebuf is protected + + assert(len_int > 0); + size_t len = len_int; + +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] SDL callback requests %" PRIu32 " samples", + bytes_to_samples(ap, len)); +#endif + + size_t read_avail = sc_bytebuf_read_available(&ap->buf); + if (!ap->played) { + uint32_t buffered_samples = bytes_to_samples(ap, read_avail); + + // Part of the buffering is handled by inserting initial silence. The + // remaining (margin) last samples will be handled by compensation. + uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms + if (buffered_samples + margin < ap->target_buffering) { + LOGV("[Audio] Inserting initial buffering silence: %" PRIu32 + " samples", bytes_to_samples(ap, len)); + // Delay playback starting to reach the target buffering. Fill the + // whole buffer with silence (len is small compared to the + // arbitrary margin value). + memset(stream, 0, len); + return; + } + } + + size_t read = MIN(read_avail, len); + if (read) { + sc_bytebuf_read(&ap->buf, stream, read); + } + + if (read < len) { + size_t silence_bytes = len - read; + uint32_t silence_samples = bytes_to_samples(ap, silence_bytes); + // Insert silence. In theory, the inserted silent samples replace the + // missing real samples, which will arrive later, so they should be + // dropped to keep the latency minimal. However, this would cause very + // audible glitches, so let the clock compensation restore the target + // latency. + LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples", + silence_samples); + memset(stream + read, 0, silence_bytes); + + if (ap->received) { + // Inserting additional samples immediately increases buffering + ap->avg_buffering.avg += silence_samples; + } + } + + ap->played = true; +} + +static uint8_t * +sc_audio_player_get_swr_buf(struct sc_audio_player *ap, uint32_t min_samples) { + size_t min_buf_size = samples_to_bytes(ap, min_samples); + if (min_buf_size > ap->swr_buf_alloc_size) { + size_t new_size = min_buf_size + 4096; + uint8_t *buf = realloc(ap->swr_buf, new_size); + if (!buf) { + LOG_OOM(); + // Could not realloc to the requested size + return NULL; + } + ap->swr_buf = buf; + ap->swr_buf_alloc_size = new_size; + } + + return ap->swr_buf; +} + +static bool +sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, + const AVFrame *frame) { + struct sc_audio_player *ap = DOWNCAST(sink); + + SwrContext *swr_ctx = ap->swr_ctx; + + int64_t swr_delay = swr_get_delay(swr_ctx, ap->sample_rate); + // No need to av_rescale_rnd(), input and output sample rates are the same. + // Add more space (256) for clock compensation. + int dst_nb_samples = swr_delay + frame->nb_samples + 256; + + uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, dst_nb_samples); + if (!swr_buf) { + return false; + } + + int ret = swr_convert(swr_ctx, &swr_buf, dst_nb_samples, + (const uint8_t **) frame->data, frame->nb_samples); + if (ret < 0) { + LOGE("Resampling failed: %d", ret); + return false; + } + + // swr_convert() returns the number of samples which would have been + // written if the buffer was big enough. + uint32_t samples_written = MIN(ret, dst_nb_samples); + size_t swr_buf_size = samples_to_bytes(ap, samples_written); +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written); +#endif + + // Since this function is the only writer, the current available space is + // at least the previous available space. In practice, it should almost + // always be possible to write without lock. + bool lockless_write = swr_buf_size <= ap->previous_write_avail; + if (lockless_write) { + sc_bytebuf_prepare_write(&ap->buf, swr_buf, swr_buf_size); + } + + SDL_LockAudioDevice(ap->device); + + size_t read_avail = sc_bytebuf_read_available(&ap->buf); + uint32_t buffered_samples = bytes_to_samples(ap, read_avail); + + if (lockless_write) { + sc_bytebuf_commit_write(&ap->buf, swr_buf_size); + } else { + // Take care to keep full samples + size_t align = ap->nb_channels * ap->out_bytes_per_sample; + size_t write_avail = + sc_bytebuf_write_available(&ap->buf) / align * align; + if (swr_buf_size > write_avail) { + // Entering this branch is very unlikely, the ring-buffer (bytebuf) + // is allocated with a size sufficient to store 1 second more than + // the target buffering. If this happens, though, we have to skip + // old samples. + size_t cap = sc_bytebuf_capacity(&ap->buf) / align * align; + if (swr_buf_size > cap) { + // Very very unlikely: a single resampled frame should never + // exceed the ring-buffer size (or something is very wrong). + // Ignore the first bytes in swr_buf + swr_buf += swr_buf_size - cap; + swr_buf_size = cap; + // This change in samples_written will impact the + // instant_compensation below + samples_written -= bytes_to_samples(ap, swr_buf_size - cap); + } + + assert(swr_buf_size >= write_avail); + if (swr_buf_size > write_avail) { + sc_bytebuf_skip(&ap->buf, swr_buf_size - write_avail); + uint32_t skip_samples = + bytes_to_samples(ap, swr_buf_size - write_avail); + assert(buffered_samples >= skip_samples); + buffered_samples -= skip_samples; + if (ap->played) { + // Dropping input samples instantly decreases buffering + ap->avg_buffering.avg -= skip_samples; + } + } + + // It should remain exactly the expected size to write the new + // samples. + assert((sc_bytebuf_write_available(&ap->buf) / align * align) + == swr_buf_size); + } + + sc_bytebuf_write(&ap->buf, swr_buf, swr_buf_size); + } + + buffered_samples += samples_written; + assert(samples_to_bytes(ap, buffered_samples) + == sc_bytebuf_read_available(&ap->buf)); + + // Read with lock held, to be used after unlocking + bool played = ap->played; + if (played) { + uint32_t max_buffered_samples = ap->target_buffering + + 12 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES + + ap->target_buffering / 10; + if (buffered_samples > max_buffered_samples) { + uint32_t skip_samples = buffered_samples - max_buffered_samples; + size_t skip_bytes = samples_to_bytes(ap, skip_samples); + sc_bytebuf_skip(&ap->buf, skip_bytes); +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32 + " samples", skip_samples); +#endif + } + + // Number of samples added (or removed, if negative) for compensation + int32_t instant_compensation = + (int32_t) samples_written - frame->nb_samples; + + // The compensation must apply instantly, it must not be smoothed + ap->avg_buffering.avg += instant_compensation; + + // However, the buffering level must be smoothed + sc_average_push(&ap->avg_buffering, buffered_samples); + +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f", + buffered_samples, sc_average_get(&ap->avg_buffering)); +#endif + } else { + // SDL playback not started yet, do not accumulate more than + // max_initial_buffering samples, this would cause unnecessary delay + // (and glitches to compensate) on start. + uint32_t max_initial_buffering = ap->target_buffering + + 2 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES; + if (buffered_samples > max_initial_buffering) { + uint32_t skip_samples = buffered_samples - max_initial_buffering; + size_t skip_bytes = samples_to_bytes(ap, skip_samples); + sc_bytebuf_skip(&ap->buf, skip_bytes); +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples", + skip_samples); +#endif + } + } + + ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf); + ap->received = true; + + SDL_UnlockAudioDevice(ap->device); + + if (played) { + ap->samples_since_resync += samples_written; + if (ap->samples_since_resync >= ap->sample_rate) { + // Recompute compensation every second + ap->samples_since_resync = 0; + + float avg = sc_average_get(&ap->avg_buffering); + int diff = ap->target_buffering - avg; + if (diff < 0 && buffered_samples < ap->target_buffering) { + // Do not accelerate if the instant buffering level is below + // the average, this would increase underflow + diff = 0; + } + // Compensate the diff over 4 seconds (but will be recomputed after + // 1 second) + int distance = 4 * ap->sample_rate; + // Limit compensation rate to 2% + int abs_max_diff = distance / 50; + diff = CLAMP(diff, -abs_max_diff, abs_max_diff); + LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32 + " compensation=%d", ap->target_buffering, avg, + buffered_samples, diff); + int ret = swr_set_compensation(swr_ctx, diff, distance); + if (ret < 0) { + LOGW("Resampling compensation failed: %d", ret); + // not fatal + } + } + } + + return true; +} + +static bool +sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, + const AVCodecContext *ctx) { + struct sc_audio_player *ap = DOWNCAST(sink); + + SDL_AudioSpec desired = { + .freq = ctx->sample_rate, + .format = SC_SDL_SAMPLE_FMT, + .channels = ctx->ch_layout.nb_channels, + .samples = SC_AUDIO_OUTPUT_BUFFER_SAMPLES, + .callback = sc_audio_player_sdl_callback, + .userdata = ap, + }; + SDL_AudioSpec obtained; + + ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); + if (!ap->device) { + LOGE("Could not open audio device: %s", SDL_GetError()); + return false; + } + + SwrContext *swr_ctx = swr_alloc(); + if (!swr_ctx) { + LOG_OOM(); + goto error_close_audio_device; + } + ap->swr_ctx = swr_ctx; + + assert(ctx->sample_rate > 0); + assert(ctx->ch_layout.nb_channels > 0); + assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT)); + int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT); + assert(out_bytes_per_sample > 0); + + av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0); + av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0); + + av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0); + av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0); + + av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", ctx->sample_fmt, 0); + av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", SC_AV_SAMPLE_FMT, 0); + + int ret = swr_init(swr_ctx); + if (ret) { + LOGE("Failed to initialize the resampling context"); + goto error_free_swr_ctx; + } + + ap->sample_rate = ctx->sample_rate; + ap->nb_channels = ctx->ch_layout.nb_channels; + ap->out_bytes_per_sample = out_bytes_per_sample; + + ap->target_buffering = ap->target_buffering_delay * ap->sample_rate + / SC_TICK_FREQ; + + // Use a ring-buffer of the target buffering size plus 1 second between the + // producer and the consumer. It's too big on purpose, to guarantee that + // the producer and the consumer will be able to access it in parallel + // without locking. + size_t bytebuf_samples = ap->target_buffering + ap->sample_rate; + size_t bytebuf_size = samples_to_bytes(ap, bytebuf_samples); + + bool ok = sc_bytebuf_init(&ap->buf, bytebuf_size); + if (!ok) { + goto error_free_swr_ctx; + } + + size_t initial_swr_buf_size = samples_to_bytes(ap, 4096); + ap->swr_buf = malloc(initial_swr_buf_size); + if (!ap->swr_buf) { + LOG_OOM(); + goto error_destroy_bytebuf; + } + ap->swr_buf_alloc_size = initial_swr_buf_size; + + ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf); + + // Samples are produced and consumed by blocks, so the buffering must be + // smoothed to get a relatively stable value. + sc_average_init(&ap->avg_buffering, 32); + ap->samples_since_resync = 0; + + ap->received = false; + ap->played = false; + + SDL_PauseAudioDevice(ap->device, 0); + + return true; + +error_destroy_bytebuf: + sc_bytebuf_destroy(&ap->buf); +error_free_swr_ctx: + swr_free(&ap->swr_ctx); +error_close_audio_device: + SDL_CloseAudioDevice(ap->device); + + return false; +} + +static void +sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) { + struct sc_audio_player *ap = DOWNCAST(sink); + + assert(ap->device); + SDL_PauseAudioDevice(ap->device, 1); + SDL_CloseAudioDevice(ap->device); + + free(ap->swr_buf); + sc_bytebuf_destroy(&ap->buf); + swr_free(&ap->swr_ctx); +} + +void +sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering) { + ap->target_buffering_delay = target_buffering; + + static const struct sc_frame_sink_ops ops = { + .open = sc_audio_player_frame_sink_open, + .close = sc_audio_player_frame_sink_close, + .push = sc_audio_player_frame_sink_push, + }; + + ap->frame_sink.ops = &ops; +} diff --git a/app/src/audio_player.h b/app/src/audio_player.h new file mode 100644 index 00000000..c64760ec --- /dev/null +++ b/app/src/audio_player.h @@ -0,0 +1,78 @@ +#ifndef SC_AUDIO_PLAYER_H +#define SC_AUDIO_PLAYER_H + +#include "common.h" + +#include +#include "trait/frame_sink.h" +#include +#include +#include +#include + +#include +#include +#include + +struct sc_audio_player { + struct sc_frame_sink frame_sink; + + SDL_AudioDeviceID device; + + // The target buffering between the producer and the consumer. This value + // is directly use for compensation. + // Since audio capture and/or encoding on the device typically produce + // blocks of 960 samples (20ms) or 1024 samples (~21.3ms), this target + // value should be higher. + sc_tick target_buffering_delay; + uint32_t target_buffering; // in samples + + // Audio buffer to communicate between the receiver and the SDL audio + // callback (protected by SDL_AudioDeviceLock()) + struct sc_bytebuf buf; + + // The previous number of bytes available in the buffer (only used by the + // receiver thread) + size_t previous_write_avail; + + // Resampler (only used from the receiver thread) + struct SwrContext *swr_ctx; + + // The sample rate is the same for input and output + unsigned sample_rate; + // The number of channels is the same for input and output + unsigned nb_channels; + // The number of bytes per sample for a single channel + unsigned out_bytes_per_sample; + + // Target buffer for resampling (only used by the receiver thread) + uint8_t *swr_buf; + size_t swr_buf_alloc_size; + + // Number of buffered samples (may be negative on underflow) (only used by + // the receiver thread) + struct sc_average avg_buffering; + // Count the number of samples to trigger a compensation update regularly + // (only used by the receiver thread) + uint32_t samples_since_resync; + + // Set to true the first time a sample is received (protected by + // SDL_AudioDeviceLock()) + bool received; + + // Set to true the first time the SDL callback is called (protected by + // SDL_AudioDeviceLock()) + bool played; + + const struct sc_audio_player_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_audio_player_callbacks { + void (*on_ended)(struct sc_audio_player *ap, bool success, void *userdata); +}; + +void +sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering); + +#endif diff --git a/app/src/decoder.c b/app/src/decoder.c index a8168f66..4384186d 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -2,6 +2,7 @@ #include #include +#include #include "events.h" #include "trait/frame_sink.h" @@ -23,6 +24,11 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { if (codec->type == AVMEDIA_TYPE_VIDEO) { // Hardcoded video properties decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + } else { + // Hardcoded audio properties + decoder->codec_ctx->ch_layout = + (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; + decoder->codec_ctx->sample_rate = 48000; } if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index dba1bad9..3f3a34f0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -13,6 +13,7 @@ # include #endif +#include "audio_player.h" #include "controller.h" #include "decoder.h" #include "delay_buffer.h" @@ -41,6 +42,7 @@ struct scrcpy { struct sc_server server; struct sc_screen screen; + struct sc_audio_player audio_player; struct sc_demuxer video_demuxer; struct sc_demuxer audio_demuxer; struct sc_decoder video_decoder; @@ -386,9 +388,16 @@ scrcpy(struct scrcpy_options *options) { } // Initialize SDL video in addition if display is enabled - if (options->display && SDL_Init(SDL_INIT_VIDEO)) { - LOGE("Could not initialize SDL: %s", SDL_GetError()); - goto end; + if (options->display) { + if (SDL_Init(SDL_INIT_VIDEO)) { + LOGE("Could not initialize SDL video: %s", SDL_GetError()); + goto end; + } + + if (options->audio && SDL_Init(SDL_INIT_AUDIO)) { + LOGE("Could not initialize SDL audio: %s", SDL_GetError()); + goto end; + } } sdl_configure(options->display, options->disable_screensaver); @@ -676,6 +685,12 @@ aoa_hid_end: } sc_frame_source_add_sink(src, &s->screen.frame_sink); + + if (options->audio) { + sc_audio_player_init(&s->audio_player, SC_TICK_FROM_MS(50)); + sc_frame_source_add_sink(&s->audio_decoder.frame_source, + &s->audio_player.frame_sink); + } } #ifdef HAVE_V4L2 diff --git a/app/src/util/average.c b/app/src/util/average.c new file mode 100644 index 00000000..ace23d45 --- /dev/null +++ b/app/src/util/average.c @@ -0,0 +1,26 @@ +#include "average.h" + +#include + +void +sc_average_init(struct sc_average *avg, unsigned range) { + avg->range = range; + avg->avg = 0; + avg->count = 0; +} + +void +sc_average_push(struct sc_average *avg, float value) { + if (avg->count < avg->range) { + ++avg->count; + } + + assert(avg->count); + avg->avg = ((avg->count - 1) * avg->avg + value) / avg->count; +} + +float +sc_average_get(struct sc_average *avg) { + assert(avg->count); + return avg->avg; +} diff --git a/app/src/util/average.h b/app/src/util/average.h new file mode 100644 index 00000000..59fae7d1 --- /dev/null +++ b/app/src/util/average.h @@ -0,0 +1,40 @@ +#ifndef SC_AVERAGE +#define SC_AVERAGE + +#include "common.h" + +#include +#include + +struct sc_average { + // Current average value + float avg; + + // Target range, to update the average as follow: + // avg = ((range - 1) * avg + new_value) / range + unsigned range; + + // Number of values pushed when less than range (count <= range). + // The purpose is to handle the first (range - 1) values properly. + unsigned count; +}; + +void +sc_average_init(struct sc_average *avg, unsigned range); + +/** + * Push a new value to update the "rolling" average + */ +void +sc_average_push(struct sc_average *avg, float value); + +/** + * Get the current average value + * + * It is an error to call this function if sc_average_push() has not been + * called at least once. + */ +float +sc_average_get(struct sc_average *avg); + +#endif From d66b0b3dccc118600b83988173e6631ad661df51 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Feb 2023 21:41:27 +0100 Subject: [PATCH 0776/1133] Add compat support for FFmpeg < 5.1 The new chlayout API has been introduced in FFmpeg 5.1. Use the old channel_layout API on older versions. PR #3757 --- app/src/audio_player.c | 20 +++++++++++++++++--- app/src/compat.h | 7 +++++++ app/src/decoder.c | 5 +++++ app/src/recorder.c | 5 +++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 78a8ffe1..de218f1e 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -286,11 +286,19 @@ static bool sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) { struct sc_audio_player *ap = DOWNCAST(sink); +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT + assert(ctx->ch_layout.nb_channels > 0); + unsigned nb_channels = ctx->ch_layout.nb_channels; +#else + int tmp = av_get_channel_layout_nb_channels(ctx->channel_layout); + assert(tmp > 0); + unsigned nb_channels = tmp; +#endif SDL_AudioSpec desired = { .freq = ctx->sample_rate, .format = SC_SDL_SAMPLE_FMT, - .channels = ctx->ch_layout.nb_channels, + .channels = nb_channels, .samples = SC_AUDIO_OUTPUT_BUFFER_SAMPLES, .callback = sc_audio_player_sdl_callback, .userdata = ap, @@ -311,13 +319,19 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, ap->swr_ctx = swr_ctx; assert(ctx->sample_rate > 0); - assert(ctx->ch_layout.nb_channels > 0); assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT)); int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT); assert(out_bytes_per_sample > 0); +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0); av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0); +#else + av_opt_set_channel_layout(swr_ctx, "in_channel_layout", + ctx->channel_layout, 0); + av_opt_set_channel_layout(swr_ctx, "out_channel_layout", + ctx->channel_layout, 0); +#endif av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0); av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0); @@ -332,7 +346,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, } ap->sample_rate = ctx->sample_rate; - ap->nb_channels = ctx->ch_layout.nb_channels; + ap->nb_channels = nb_channels; ap->out_bytes_per_sample = out_bytes_per_sample; ap->target_buffering = ap->target_buffering_delay * ap->sample_rate diff --git a/app/src/compat.h b/app/src/compat.h index ea44437d..22563421 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -37,6 +37,13 @@ # define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL #endif +// Not documented in ffmpeg/doc/APIchanges, but the channel_layout API +// has been replaced by chlayout in FFmpeg commit +// f423497b455da06c1337846902c770028760e094. +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 23, 100) +# define SCRCPY_LAVU_HAS_CHLAYOUT +#endif + #if SDL_VERSION_ATLEAST(2, 0, 6) // # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS diff --git a/app/src/decoder.c b/app/src/decoder.c index 4384186d..e87cfd6b 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -26,8 +26,13 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; } else { // Hardcoded audio properties +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT decoder->codec_ctx->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; +#else + decoder->codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; + decoder->codec_ctx->channels = 2; +#endif decoder->codec_ctx->sample_rate = 48000; } diff --git a/app/src/recorder.c b/app/src/recorder.c index bd7c50f2..af5fe510 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -202,7 +202,12 @@ sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; stream->codecpar->codec_id = codec->id; +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT stream->codecpar->ch_layout.nb_channels = 2; +#else + stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; + stream->codecpar->channels = 2; +#endif stream->codecpar->sample_rate = 48000; recorder->audio_stream_index = stream->index; From df55bc2683e2ca2aec105d6e435cb0b170037ed7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 2 Mar 2023 23:14:01 +0100 Subject: [PATCH 0777/1133] Add --audio-buffer Expose an option to add a buffering delay (in milliseconds) before playing audio. This is similar to the options --display-buffer and --v4l2-buffer for video frames. PR #3757 --- app/scrcpy.1 | 8 ++++++++ app/src/cli.c | 15 +++++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 2 +- 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 91258414..120ea192 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -25,6 +25,14 @@ Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are Default is 128K (128000). +.TP +.BI "\-\-audio\-buffer ms +Configure the audio buffering delay (in milliseconds). + +Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches). + +Default is 50. + .TP .BI "\-\-audio\-codec " name Select an audio codec (opus or aac). diff --git a/app/src/cli.c b/app/src/cli.c index 18f3b83b..122a5891 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -71,6 +71,7 @@ enum { OPT_LIST_ENCODERS, OPT_LIST_DISPLAYS, OPT_REQUIRE_AUDIO, + OPT_AUDIO_BUFFER, }; struct sc_option { @@ -120,6 +121,15 @@ static const struct sc_option options[] = { "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 128K (128000).", }, + { + .longopt_id = OPT_AUDIO_BUFFER, + .longopt = "audio-buffer", + .argdesc = "ms", + .text = "Configure the audio buffering delay (in milliseconds).\n" + "Lower values decrease the latency, but increase the " + "likelyhood of buffer underrun (causing audio glitches).\n" + "Default is 50.", + }, { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", @@ -1822,6 +1832,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_REQUIRE_AUDIO: opts->require_audio = true; break; + case OPT_AUDIO_BUFFER: + if (!parse_buffering_time(optarg, &opts->audio_buffer)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 5dd655ce..68c16d53 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -43,6 +43,7 @@ const struct scrcpy_options scrcpy_options_default = { .display_id = 0, .display_buffer = 0, .v4l2_buffer = 0, + .audio_buffer = SC_TICK_FROM_MS(50), #ifdef HAVE_USB .otg = false, #endif diff --git a/app/src/options.h b/app/src/options.h index 5fcaf016..d9c2d228 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -125,6 +125,7 @@ struct scrcpy_options { uint32_t display_id; sc_tick display_buffer; sc_tick v4l2_buffer; + sc_tick audio_buffer; #ifdef HAVE_USB bool otg; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3f3a34f0..ce045c97 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -687,7 +687,7 @@ aoa_hid_end: sc_frame_source_add_sink(src, &s->screen.frame_sink); if (options->audio) { - sc_audio_player_init(&s->audio_player, SC_TICK_FROM_MS(50)); + sc_audio_player_init(&s->audio_player, options->audio_buffer); sc_frame_source_add_sink(&s->audio_decoder.frame_source, &s->audio_player.frame_sink); } From 02dd1be4a1ab24795a10ca769e2998dec28806f7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 00:42:51 +0100 Subject: [PATCH 0778/1133] Stop on decoder frame push error On push, frame sinks report downstream errors to stop upstream components. Do not ignore the error. --- app/src/decoder.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/decoder.c b/app/src/decoder.c index e87cfd6b..ecad8373 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -99,11 +99,11 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { // a frame was received bool ok = sc_frame_source_sinks_push(&decoder->frame_source, decoder->frame); - // A frame lost should not make the whole pipeline fail. The error, if - // any, is already logged. - (void) ok; - av_frame_unref(decoder->frame); + if (!ok) { + // Error already logged + return false; + } } return true; From 65cc9d765d8d0feb26a41c2287b3abe13dc37d12 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 18:46:59 +0100 Subject: [PATCH 0779/1133] Extract audio capture The audio capture was implemented in AudioEncoder. In order to reuse it without encoding, extract it to a separate class. PR #3757 --- .../com/genymobile/scrcpy/AudioCapture.java | 148 ++++++++++++++++++ .../com/genymobile/scrcpy/AudioEncoder.java | 136 ++-------------- 2 files changed, 161 insertions(+), 123 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioCapture.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java new file mode 100644 index 00000000..3cef7801 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -0,0 +1,148 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Intent; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.AudioTimestamp; +import android.media.MediaCodec; +import android.media.MediaRecorder; +import android.os.Build; +import android.os.SystemClock; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class AudioCapture { + + public static final int SAMPLE_RATE = 48000; + public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; + public static final int CHANNELS = 2; + public static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; + public static final int BYTES_PER_SAMPLE = 2; + + private AudioRecord recorder; + + private final AudioTimestamp timestamp = new AudioTimestamp(); + private long previousPts = 0; + private long nextPts = 0; + + public static int millisToBytes(int millis) { + return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000; + } + + private static AudioFormat createAudioFormat() { + AudioFormat.Builder builder = new AudioFormat.Builder(); + builder.setEncoding(FORMAT); + builder.setSampleRate(SAMPLE_RATE); + builder.setChannelMask(CHANNEL_CONFIG); + return builder.build(); + } + + @TargetApi(Build.VERSION_CODES.M) + @SuppressLint({"WrongConstant", "MissingPermission"}) + private static AudioRecord createAudioRecord() { + AudioRecord.Builder builder = new AudioRecord.Builder(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // On older APIs, Workarounds.fillAppInfo() must be called beforehand + builder.setContext(FakeContext.get()); + } + builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX); + builder.setAudioFormat(createAudioFormat()); + int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT); + // This buffer size does not impact latency + builder.setBufferSizeInBytes(8 * minBufferSize); + return builder.build(); + } + + private static void startWorkaroundAndroid11() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + // Android 11 requires Apps to be at foreground to record audio. + // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. + // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android + // shell ("com.android.shell"). + // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the + // foreground. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); + ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); + // Wait for activity to start + SystemClock.sleep(150); + } + } + } + + private static void stopWorkaroundAndroid11() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); + } + } + + public void start() throws AudioCaptureForegroundException { + startWorkaroundAndroid11(); + try { + recorder = createAudioRecord(); + recorder.startRecording(); + } catch (UnsupportedOperationException e) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Ln.e("Failed to start audio capture"); + Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy."); + throw new AudioCaptureForegroundException(); + } + throw e; + } finally { + stopWorkaroundAndroid11(); + } + } + + public void stop() { + if (recorder != null) { + // Will call .stop() if necessary, without throwing an IllegalStateException + recorder.release(); + } + } + + @TargetApi(Build.VERSION_CODES.N) + public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) throws IOException { + int r = recorder.read(directBuffer, size); + if (r < 0) { + return r; + } + + long pts; + + int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); + if (ret == AudioRecord.SUCCESS) { + pts = timestamp.nanoTime / 1000; + } else { + if (nextPts == 0) { + Ln.w("Could not get any audio timestamp"); + } + // compute from previous timestamp and packet size + pts = nextPts; + } + + long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); + nextPts = pts + durationUs; + + if (previousPts != 0 && pts < previousPts) { + // Audio PTS may come from two sources: + // - recorder.getTimestamp() if the call works; + // - an estimation from the previous PTS and the packet size as a fallback. + // + // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. + pts = previousPts + 1; + } + previousPts = pts; + + outBufferInfo.set(0, r, pts, 0); + return r; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index cc786bdb..8b60d37e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -1,22 +1,12 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.ServiceManager; - -import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.content.ComponentName; -import android.content.Intent; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.AudioTimestamp; import android.media.MediaCodec; import android.media.MediaFormat; -import android.media.MediaRecorder; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; -import android.os.SystemClock; import java.io.IOException; import java.nio.ByteBuffer; @@ -44,14 +34,11 @@ public final class AudioEncoder { } } - private static final int SAMPLE_RATE = 48000; - private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; - private static final int CHANNELS = 2; - private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; - private static final int BYTES_PER_SAMPLE = 2; + private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE; + private static final int CHANNELS = AudioCapture.CHANNELS; private static final int READ_MS = 5; // milliseconds - private static final int READ_SIZE = SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * READ_MS / 1000; + private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); private final Streamer streamer; private final int bitRate; @@ -78,30 +65,6 @@ public final class AudioEncoder { this.encoderName = encoderName; } - private static AudioFormat createAudioFormat() { - AudioFormat.Builder builder = new AudioFormat.Builder(); - builder.setEncoding(FORMAT); - builder.setSampleRate(SAMPLE_RATE); - builder.setChannelMask(CHANNEL_CONFIG); - return builder.build(); - } - - @TargetApi(Build.VERSION_CODES.M) - @SuppressLint({"WrongConstant", "MissingPermission"}) - private static AudioRecord createAudioRecord() { - AudioRecord.Builder builder = new AudioRecord.Builder(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // On older APIs, Workarounds.fillAppInfo() must be called beforehand - builder.setContext(FakeContext.get()); - } - builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX); - builder.setAudioFormat(createAudioFormat()); - int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT); - // This buffer size does not impact latency - builder.setBufferSizeInBytes(8 * minBufferSize); - return builder.build(); - } - private static MediaFormat createFormat(String mimeType, int bitRate, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, mimeType); @@ -122,47 +85,18 @@ public final class AudioEncoder { } @TargetApi(Build.VERSION_CODES.N) - private void inputThread(MediaCodec mediaCodec, AudioRecord recorder) throws IOException, InterruptedException { - final AudioTimestamp timestamp = new AudioTimestamp(); - long previousPts = 0; - long nextPts = 0; + private void inputThread(MediaCodec mediaCodec, AudioCapture capture) throws IOException, InterruptedException { + final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (!Thread.currentThread().isInterrupted()) { InputTask task = inputTasks.take(); ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); - int r = recorder.read(buffer, READ_SIZE); + int r = capture.read(buffer, READ_SIZE, bufferInfo); if (r < 0) { throw new IOException("Could not read audio: " + r); } - long pts; - - int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); - if (ret == AudioRecord.SUCCESS) { - pts = timestamp.nanoTime / 1000; - } else { - if (nextPts == 0) { - Ln.w("Could not get any audio timestamp"); - } - // compute from previous timestamp and packet size - pts = nextPts; - } - - long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); - nextPts = pts + durationUs; - - if (previousPts != 0 && pts < previousPts) { - // Audio PTS may come from two sources: - // - recorder.getTimestamp() if the call works; - // - an estimation from the previous PTS and the packet size as a fallback. - // - // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. - pts = previousPts + 1; - } - - previousPts = pts; - - mediaCodec.queueInputBuffer(task.index, 0, r, pts, 0); + mediaCodec.queueInputBuffer(task.index, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags); } } @@ -223,32 +157,6 @@ public final class AudioEncoder { } } - private static void startWorkaroundAndroid11() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - // Android 11 requires Apps to be at foreground to record audio. - // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. - // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android - // shell ("com.android.shell"). - // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the - // foreground. - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); - ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); - // Wait for activity to start - SystemClock.sleep(150); - } - } - } - - private static void stopWorkaroundAndroid11() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); - } - } - @TargetApi(Build.VERSION_CODES.M) public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { @@ -258,10 +166,9 @@ public final class AudioEncoder { } MediaCodec mediaCodec = null; - AudioRecord recorder = null; + AudioCapture capture = new AudioCapture(); boolean mediaCodecStarted = false; - boolean recorderStarted = false; try { Codec codec = streamer.getCodec(); mediaCodec = createMediaCodec(codec, encoderName); @@ -273,27 +180,13 @@ public final class AudioEncoder { mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper())); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); - startWorkaroundAndroid11(); - try { - recorder = createAudioRecord(); - recorder.startRecording(); - } catch (UnsupportedOperationException e) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - Ln.e("Failed to start audio capture"); - Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy."); - throw new AudioCaptureForegroundException(); - } - throw e; - } finally { - stopWorkaroundAndroid11(); - } - recorderStarted = true; + capture.start(); final MediaCodec mediaCodecRef = mediaCodec; - final AudioRecord recorderRef = recorder; + final AudioCapture captureRef = capture; inputThread = new Thread(() -> { try { - inputThread(mediaCodecRef, recorderRef); + inputThread(mediaCodecRef, captureRef); } catch (IOException | InterruptedException e) { Ln.e("Audio capture error", e); } finally { @@ -366,11 +259,8 @@ public final class AudioEncoder { } mediaCodec.release(); } - if (recorder != null) { - if (recorderStarted) { - recorder.stop(); - } - recorder.release(); + if (capture != null) { + capture.stop(); } } } From dc228eaad0ea53f57c27e9aba2b51a422ed3aa94 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 18:49:05 +0100 Subject: [PATCH 0780/1133] Extract async processor interface On the server side, several components are started, stopped and joined. Extract an interface to handle them generically. This will help to support both encoded and raw audio stream, because they will be two different concrete components, but implementing the same interface. PR #3757 --- .../com/genymobile/scrcpy/AsyncProcessor.java | 7 ++++ .../com/genymobile/scrcpy/AudioEncoder.java | 2 +- .../com/genymobile/scrcpy/Controller.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 35 +++++++++---------- 4 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java new file mode 100644 index 00000000..cbc435b0 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java @@ -0,0 +1,7 @@ +package com.genymobile.scrcpy; + +public interface AsyncProcessor { + void start(); + void stop(); + void join() throws InterruptedException; +} diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 8b60d37e..0ba424ca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -public final class AudioEncoder { +public final class AudioEncoder implements AsyncProcessor { private static class InputTask { private final int index; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 02d77cb1..59fae602 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -14,7 +14,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -public class Controller { +public class Controller implements AsyncProcessor { private static final int DEFAULT_DEVICE_ID = 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 35da6965..3d3e02fd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -5,6 +5,7 @@ import android.os.BatteryManager; import android.os.Build; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -91,8 +92,7 @@ public final class Server { Workarounds.fillAppInfo(); } - Controller controller = null; - AudioEncoder audioEncoder = null; + List asyncProcessors = new ArrayList<>(); try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { if (options.getSendDeviceMeta()) { @@ -101,24 +101,27 @@ public final class Server { } if (control) { - controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); - controller.start(); - - final Controller controllerRef = controller; - device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text)); + Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); + device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); + asyncProcessors.add(controller); } if (audio) { Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), options.getSendFrameMeta()); - audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); - audioEncoder.start(); + AudioEncoder audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); + asyncProcessors.add(audioRecorder); } Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), options.getSendFrameMeta()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); + + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.start(); + } + try { // synchronous screenEncoder.streamScreen(); @@ -131,20 +134,14 @@ public final class Server { } finally { Ln.d("Screen streaming stopped"); initThread.interrupt(); - if (audioEncoder != null) { - audioEncoder.stop(); - } - if (controller != null) { - controller.stop(); + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.stop(); } try { initThread.join(); - if (audioEncoder != null) { - audioEncoder.join(); - } - if (controller != null) { - controller.join(); + for (AsyncProcessor asyncProcessor : asyncProcessors) { + asyncProcessor.join(); } } catch (InterruptedException e) { // ignore From 66b6c06443130a8a11984ca2cd6d80f5cd3db6ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 21:14:28 +0100 Subject: [PATCH 0781/1133] Add raw audio recorder Add an alternative AudioRecorder to stream raw packets without encoding. PR #3757 --- .../com/genymobile/scrcpy/AudioCodec.java | 3 +- .../genymobile/scrcpy/AudioRawRecorder.java | 75 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 11 ++- 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java index dc000e98..1f3b07a0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -4,7 +4,8 @@ import android.media.MediaFormat; public enum AudioCodec implements Codec { OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), - AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC); + AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC), + RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW); private final int id; // 4-byte ASCII representation of the name private final String name; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java new file mode 100644 index 00000000..2e483daa --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -0,0 +1,75 @@ +package com.genymobile.scrcpy; + +import android.media.MediaCodec; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class AudioRawRecorder implements AsyncProcessor { + + private final Streamer streamer; + + private Thread thread; + + private static final int READ_MS = 5; // milliseconds + private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); + + public AudioRawRecorder(Streamer streamer) { + this.streamer = streamer; + } + + private void record() throws IOException, AudioCaptureForegroundException { + final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); + final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + + AudioCapture capture = new AudioCapture(); + try { + capture.start(); + + streamer.writeHeader(); + while (!Thread.currentThread().isInterrupted()) { + buffer.position(0); + int r = capture.read(buffer, READ_SIZE, bufferInfo); + if (r < 0) { + throw new IOException("Could not read audio: " + r); + } + buffer.limit(r); + + streamer.writePacket(buffer, bufferInfo); + } + } catch (Throwable e) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(false); + throw e; + } finally { + capture.stop(); + } + } + + public void start() { + thread = new Thread(() -> { + try { + record(); + } catch (AudioCaptureForegroundException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (IOException e) { + Ln.e("Audio recording error", e); + } finally { + Ln.d("Audio recorder stopped"); + } + }); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + } + } + + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 3d3e02fd..86555e3b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -107,9 +107,16 @@ public final class Server { } if (audio) { - Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(), + AudioCodec audioCodec = options.getAudioCodec(); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecId(), options.getSendFrameMeta()); - AudioEncoder audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); + AsyncProcessor audioRecorder; + if (audioCodec == AudioCodec.RAW) { + audioRecorder = new AudioRawRecorder(audioStreamer); + } else { + audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), + options.getAudioEncoder()); + } asyncProcessors.add(audioRecorder); } From d2952c7e93f151d773f38aa368b7163b0ff31cf2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 21:19:37 +0100 Subject: [PATCH 0782/1133] Add --audio-codec=raw option Add support for raw (PCM S16 LE) audio codec (a raw decoder is included in FFmpeg). PR #3757 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 13 +++++++++++-- app/src/demuxer.c | 3 +++ app/src/options.h | 1 + app/src/server.c | 2 ++ 7 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 74c3ee57..c3649364 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -78,7 +78,7 @@ _scrcpy() { return ;; --audio-codec) - COMPREPLY=($(compgen -W 'opus aac' -- "$cur")) + COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) return ;; --lock-video-orientation) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index b28201a4..d713761c 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,7 +10,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' - '--audio-codec=[Select the audio codec]:codec:(opus aac)' + '--audio-codec=[Select the audio codec]:codec:(opus aac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 120ea192..e8e36188 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -35,7 +35,7 @@ Default is 50. .TP .BI "\-\-audio\-codec " name -Select an audio codec (opus or aac). +Select an audio codec (opus, aac or raw). Default is opus. diff --git a/app/src/cli.c b/app/src/cli.c index 122a5891..d45be878 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -134,7 +134,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", .argdesc = "name", - .text = "Select an audio codec (opus or aac).\n" + .text = "Select an audio codec (opus, aac or raw).\n" "Default is opus.", }, { @@ -1522,7 +1522,11 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_AAC; return true; } - LOGE("Unsupported audio codec: %s (expected opus or aac)", optarg); + if (!strcmp(optarg, "raw")) { + *codec = SC_CODEC_RAW; + return true; + } + LOGE("Unsupported audio codec: %s (expected opus, aac or raw)", optarg); return false; } @@ -1923,6 +1927,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->record_filename && opts->audio_codec == SC_CODEC_RAW) { + LOGW("Recording does not support RAW audio codec"); + return false; + } + if (!opts->control) { if (opts->turn_screen_off) { LOGE("Could not request to turn screen off if control is disabled"); diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 15a595a0..a4fa19f4 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -25,6 +25,7 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { #define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII #define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII #define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII" +#define SC_CODEC_ID_RAW UINT32_C(0x00726177) // "raw" in ASCII switch (codec_id) { case SC_CODEC_ID_H264: return AV_CODEC_ID_H264; @@ -36,6 +37,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { return AV_CODEC_ID_OPUS; case SC_CODEC_ID_AAC: return AV_CODEC_ID_AAC; + case SC_CODEC_ID_RAW: + return AV_CODEC_ID_PCM_S16LE; default: LOGE("Unknown codec id 0x%08" PRIx32, codec_id); return AV_CODEC_ID_NONE; diff --git a/app/src/options.h b/app/src/options.h index d9c2d228..06b4ddfa 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -29,6 +29,7 @@ enum sc_codec { SC_CODEC_AV1, SC_CODEC_OPUS, SC_CODEC_AAC, + SC_CODEC_RAW, }; enum sc_lock_video_orientation { diff --git a/app/src/server.c b/app/src/server.c index 9d4fb098..7b503427 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -173,6 +173,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "opus"; case SC_CODEC_AAC: return "aac"; + case SC_CODEC_RAW: + return "raw"; default: return NULL; } From 7da45c246e919386f7d4fd3facc4597bb056e752 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Mar 2023 21:58:57 +0100 Subject: [PATCH 0783/1133] Warn on ignored audio options For raw audio codec, some audio options are ignored. PR #3757 --- app/src/cli.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index d45be878..8a0b6aa4 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1932,6 +1932,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + if (opts->audio_codec == SC_CODEC_RAW) { + if (opts->audio_bit_rate) { + LOGW("--audio-bit-rate is ignored for raw audio codec"); + } + if (opts->audio_codec_options) { + LOGW("--audio-codec-options is ignored for raw audio codec"); + } + if (opts->audio_encoder) { + LOGW("--audio-encoder is ignored for raw audio codec"); + } + } + if (!opts->control) { if (opts->turn_screen_off) { LOGE("Could not request to turn screen off if control is disabled"); From bb56472d4eb15d7c318a05a8236af6d0f5a46a04 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Mar 2023 20:07:03 +0100 Subject: [PATCH 0784/1133] Print server logs and newline in one call System.out.println() first prints the message, then the new line. Between these two calls, the client might print a message, breaking formatting. Instead, call System.out.print() with '\n' appended to the message. --- server/src/main/java/com/genymobile/scrcpy/Ln.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index c39fc621..291f26ff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -39,28 +39,28 @@ public final class Ln { public static void v(String message) { if (isEnabled(Level.VERBOSE)) { Log.v(TAG, message); - System.out.println(PREFIX + "VERBOSE: " + message); + System.out.print(PREFIX + "VERBOSE: " + message + '\n'); } } public static void d(String message) { if (isEnabled(Level.DEBUG)) { Log.d(TAG, message); - System.out.println(PREFIX + "DEBUG: " + message); + System.out.print(PREFIX + "DEBUG: " + message + '\n'); } } public static void i(String message) { if (isEnabled(Level.INFO)) { Log.i(TAG, message); - System.out.println(PREFIX + "INFO: " + message); + System.out.print(PREFIX + "INFO: " + message + '\n'); } } public static void w(String message, Throwable throwable) { if (isEnabled(Level.WARN)) { Log.w(TAG, message, throwable); - System.out.println(PREFIX + "WARN: " + message); + System.out.print(PREFIX + "WARN: " + message + '\n'); if (throwable != null) { throwable.printStackTrace(); } @@ -74,7 +74,7 @@ public final class Ln { public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); - System.out.println(PREFIX + "ERROR: " + message); + System.out.print(PREFIX + "ERROR: " + message + "\n"); if (throwable != null) { throwable.printStackTrace(); } From 4a25f3e53bdac8c2510cc1c7f06e479a17a42e6b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Mar 2023 20:13:08 +0100 Subject: [PATCH 0785/1133] Print info logs to stdout All server logs were printed to stdout, while all client logs were printed to stderr. Instead, use stderr for warnings and errors, stdout for the others: - stdout: verbose, debug, info - stderr: warn, error --- app/src/util/log.c | 22 +++++++++++++++++++ .../main/java/com/genymobile/scrcpy/Ln.java | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/util/log.c b/app/src/util/log.c index 25b1f26e..0975e54a 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -125,8 +125,30 @@ sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) { free(local_fmt); } +static const char *const sc_sdl_log_priority_names[SDL_NUM_LOG_PRIORITIES] = { + [SDL_LOG_PRIORITY_VERBOSE] = "VERBOSE", + [SDL_LOG_PRIORITY_DEBUG] = "DEBUG", + [SDL_LOG_PRIORITY_INFO] = "INFO", + [SDL_LOG_PRIORITY_WARN] = "WARN", + [SDL_LOG_PRIORITY_ERROR] = "ERROR", + [SDL_LOG_PRIORITY_CRITICAL] = "CRITICAL", +}; + +static void SDLCALL +sc_sdl_log_print(void *userdata, int category, SDL_LogPriority priority, + const char *message) { + (void) userdata; + (void) category; + + FILE *out = priority < SDL_LOG_PRIORITY_WARN ? stdout : stderr; + assert(priority < SDL_NUM_LOG_PRIORITIES); + const char *prio_name = sc_sdl_log_priority_names[priority]; + fprintf(out, "%s: %s\n", prio_name, message); +} + void sc_log_configure() { + SDL_LogSetOutputFunction(sc_sdl_log_print, NULL); // Redirect FFmpeg logs to SDL logs av_log_set_callback(sc_av_log_callback); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 291f26ff..199c29be 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -60,7 +60,7 @@ public final class Ln { public static void w(String message, Throwable throwable) { if (isEnabled(Level.WARN)) { Log.w(TAG, message, throwable); - System.out.print(PREFIX + "WARN: " + message + '\n'); + System.err.print(PREFIX + "WARN: " + message + '\n'); if (throwable != null) { throwable.printStackTrace(); } @@ -74,7 +74,7 @@ public final class Ln { public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); - System.out.print(PREFIX + "ERROR: " + message + "\n"); + System.err.print(PREFIX + "ERROR: " + message + "\n"); if (throwable != null) { throwable.printStackTrace(); } From 5ee59e0f133b079f0652f10b80bb6320b41be278 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Mar 2023 21:34:42 +0100 Subject: [PATCH 0786/1133] Add thread priority API Expose an API to change the priority of the current thread. --- app/src/compat.h | 4 ++++ app/src/util/thread.c | 33 +++++++++++++++++++++++++++++++++ app/src/util/thread.h | 10 ++++++++++ 3 files changed, 47 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 22563421..00cb7204 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -54,6 +54,10 @@ # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR #endif +#if SDL_VERSION_ATLEAST(2, 0, 16) +# define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL +#endif + #ifndef HAVE_STRDUP char *strdup(const char *s); #endif diff --git a/app/src/util/thread.c b/app/src/util/thread.c index f9687add..94921fb7 100644 --- a/app/src/util/thread.c +++ b/app/src/util/thread.c @@ -23,6 +23,39 @@ sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, return true; } +static SDL_ThreadPriority +to_sdl_thread_priority(enum sc_thread_priority priority) { + switch (priority) { + case SC_THREAD_PRIORITY_TIME_CRITICAL: +#ifdef SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL + return SDL_THREAD_PRIORITY_TIME_CRITICAL; +#else + // fall through +#endif + case SC_THREAD_PRIORITY_HIGH: + return SDL_THREAD_PRIORITY_HIGH; + case SC_THREAD_PRIORITY_NORMAL: + return SDL_THREAD_PRIORITY_NORMAL; + case SC_THREAD_PRIORITY_LOW: + return SDL_THREAD_PRIORITY_LOW; + default: + assert(!"Unknown thread priority"); + return 0; + } +} + +bool +sc_thread_set_priority(enum sc_thread_priority priority) { + SDL_ThreadPriority sdl_priority = to_sdl_thread_priority(priority); + int r = SDL_SetThreadPriority(sdl_priority); + if (r) { + LOGD("Could not set thread priority: %s", SDL_GetError()); + return false; + } + + return true; +} + void sc_thread_join(sc_thread *thread, int *status) { SDL_WaitThread(thread->thread, status); diff --git a/app/src/util/thread.h b/app/src/util/thread.h index 7add6f1c..4183adac 100644 --- a/app/src/util/thread.h +++ b/app/src/util/thread.h @@ -21,6 +21,13 @@ typedef struct sc_thread { SDL_Thread *thread; } sc_thread; +enum sc_thread_priority { + SC_THREAD_PRIORITY_LOW, + SC_THREAD_PRIORITY_NORMAL, + SC_THREAD_PRIORITY_HIGH, + SC_THREAD_PRIORITY_TIME_CRITICAL, +}; + typedef struct sc_mutex { SDL_mutex *mutex; #ifndef NDEBUG @@ -39,6 +46,9 @@ sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, void sc_thread_join(sc_thread *thread, int *status); +bool +sc_thread_set_priority(enum sc_thread_priority priority); + bool sc_mutex_init(sc_mutex *mutex); From aa450ffc3f618a286c0eb7a5f60244b53d650e75 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Mar 2023 21:37:27 +0100 Subject: [PATCH 0787/1133] Increase audio thread priority The audio demuxer thread is the one filling the audio buffer read by the SDL audio thread. It is time critical to avoid buffer underflow. --- app/src/audio_player.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index de218f1e..85de0620 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -382,6 +382,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, ap->received = false; ap->played = false; + // The thread calling open() is the thread calling push(), which fills the + // audio buffer consumed by the SDL audio thread. + ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL); + if (!ok) { + ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH); + (void) ok; // We don't care if it worked, at least we tried + } + SDL_PauseAudioDevice(ap->device, 0); return true; From 408f45863617465cee36453fb9ce2c7403e159e0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 8 Mar 2023 21:40:39 +0100 Subject: [PATCH 0788/1133] Decrease recorder thread priority Recording is background task, writing the packets to a file is not urgent. --- app/src/recorder.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/recorder.c b/app/src/recorder.c index af5fe510..572d3e24 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -505,6 +505,10 @@ static int run_recorder(void *data) { struct sc_recorder *recorder = data; + // Recording is a background task + bool ok = sc_thread_set_priority(SC_THREAD_PRIORITY_LOW); + (void) ok; // We don't care if it worked + bool success = sc_recorder_record(recorder); sc_mutex_lock(&recorder->mutex); From d93582724ddb300044e0a3e0e7c728390da5762d Mon Sep 17 00:00:00 2001 From: "chengjian.scj" Date: Thu, 2 Mar 2023 17:57:13 +0800 Subject: [PATCH 0789/1133] Initialize interrupted field explicitly The field sc_fps_counter.interrupted was never initialized explicitly. Signed-off-by: Romain Vimont --- app/src/fps_counter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 85312821..dd4ae1da 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -96,6 +96,7 @@ run_fps_counter(void *data) { bool sc_fps_counter_start(struct sc_fps_counter *counter) { sc_mutex_lock(&counter->mutex); + counter->interrupted = false; counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL; counter->nr_rendered = 0; counter->nr_skipped = 0; From 46f691817983c8c251956e5115ace6a8af8e08d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:42:59 +0100 Subject: [PATCH 0790/1133] Stop and join sc_file_pusher only if initialized The sc_file_pusher is lazy-initialized, but it was stopped and joined in all cases (accessing uninitialized values). Detected by poisoning the struct scrcpy instance with ASAN enabled. --- app/src/file_pusher.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/file_pusher.c b/app/src/file_pusher.c index b49e93e5..06911052 100644 --- a/app/src/file_pusher.c +++ b/app/src/file_pusher.c @@ -172,14 +172,18 @@ sc_file_pusher_start(struct sc_file_pusher *fp) { void sc_file_pusher_stop(struct sc_file_pusher *fp) { - sc_mutex_lock(&fp->mutex); - fp->stopped = true; - sc_cond_signal(&fp->event_cond); - sc_intr_interrupt(&fp->intr); - sc_mutex_unlock(&fp->mutex); + if (fp->initialized) { + sc_mutex_lock(&fp->mutex); + fp->stopped = true; + sc_cond_signal(&fp->event_cond); + sc_intr_interrupt(&fp->intr); + sc_mutex_unlock(&fp->mutex); + } } void sc_file_pusher_join(struct sc_file_pusher *fp) { - sc_thread_join(&fp->thread, NULL); + if (fp->initialized) { + sc_thread_join(&fp->thread, NULL); + } } From 4db50ddbb7b80df2f5b8240027619bf1ed7decb1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 15:55:44 +0100 Subject: [PATCH 0791/1133] Enable log signaling buffering threshold exceeded It is as important as underflow logs. --- app/src/audio_player.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 85de0620..77de0ddb 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -207,10 +207,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, uint32_t skip_samples = buffered_samples - max_buffered_samples; size_t skip_bytes = samples_to_bytes(ap, skip_samples); sc_bytebuf_skip(&ap->buf, skip_bytes); -#ifndef SC_AUDIO_PLAYER_NDEBUG LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32 " samples", skip_samples); -#endif } // Number of samples added (or removed, if negative) for compensation From 4bdf632dfadec432582db64bff898588a4ed6e13 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 19:25:45 +0100 Subject: [PATCH 0792/1133] Pass AVCodecContext to packet sinks Create the codec context from the demuxer, so that it can fill context data for the decoder and recorder. --- app/src/audio_player.c | 1 + app/src/decoder.c | 50 ++++++----------------------------- app/src/decoder.h | 2 +- app/src/demuxer.c | 32 +++++++++++++++++++++- app/src/recorder.c | 10 +++---- app/src/trait/frame_sink.h | 3 +-- app/src/trait/packet_sink.h | 8 +++--- app/src/trait/packet_source.c | 4 +-- app/src/trait/packet_source.h | 2 +- 9 files changed, 52 insertions(+), 60 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 77de0ddb..7a348f93 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -1,5 +1,6 @@ #include "audio_player.h" +#include #include #include "util/log.h" diff --git a/app/src/decoder.c b/app/src/decoder.c index ecad8373..5d42b8b0 100644 --- a/app/src/decoder.c +++ b/app/src/decoder.c @@ -12,52 +12,20 @@ #define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink) static bool -sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { - decoder->codec_ctx = avcodec_alloc_context3(codec); - if (!decoder->codec_ctx) { - LOG_OOM(); - return false; - } - - decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; - - if (codec->type == AVMEDIA_TYPE_VIDEO) { - // Hardcoded video properties - decoder->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; - } else { - // Hardcoded audio properties -#ifdef SCRCPY_LAVU_HAS_CHLAYOUT - decoder->codec_ctx->ch_layout = - (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; -#else - decoder->codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; - decoder->codec_ctx->channels = 2; -#endif - decoder->codec_ctx->sample_rate = 48000; - } - - if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { - LOGE("Decoder '%s': could not open codec", decoder->name); - avcodec_free_context(&decoder->codec_ctx); - return false; - } - +sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) { decoder->frame = av_frame_alloc(); if (!decoder->frame) { LOG_OOM(); - avcodec_close(decoder->codec_ctx); - avcodec_free_context(&decoder->codec_ctx); return false; } - if (!sc_frame_source_sinks_open(&decoder->frame_source, - decoder->codec_ctx)) { + if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) { av_frame_free(&decoder->frame); - avcodec_close(decoder->codec_ctx); - avcodec_free_context(&decoder->codec_ctx); return false; } + decoder->ctx = ctx; + return true; } @@ -65,8 +33,6 @@ static void sc_decoder_close(struct sc_decoder *decoder) { sc_frame_source_sinks_close(&decoder->frame_source); av_frame_free(&decoder->frame); - avcodec_close(decoder->codec_ctx); - avcodec_free_context(&decoder->codec_ctx); } static bool @@ -77,7 +43,7 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { return true; } - int ret = avcodec_send_packet(decoder->codec_ctx, packet); + int ret = avcodec_send_packet(decoder->ctx, packet); if (ret < 0 && ret != AVERROR(EAGAIN)) { LOGE("Decoder '%s': could not send video packet: %d", decoder->name, ret); @@ -85,7 +51,7 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { } for (;;) { - ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); + ret = avcodec_receive_frame(decoder->ctx, decoder->frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } @@ -110,9 +76,9 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { } static bool -sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { +sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) { struct sc_decoder *decoder = DOWNCAST(sink); - return sc_decoder_open(decoder, codec); + return sc_decoder_open(decoder, ctx); } static void diff --git a/app/src/decoder.h b/app/src/decoder.h index 87aaf6a2..ba8903f4 100644 --- a/app/src/decoder.h +++ b/app/src/decoder.h @@ -16,7 +16,7 @@ struct sc_decoder { const char *name; // must be statically allocated (e.g. a string literal) - AVCodecContext *codec_ctx; + AVCodecContext *ctx; AVFrame *frame; }; diff --git a/app/src/demuxer.c b/app/src/demuxer.c index a4fa19f4..eabcb81e 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -160,10 +160,37 @@ run_demuxer(void *data) { goto end; } - if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec)) { + AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); + if (!codec_ctx) { + LOG_OOM(); goto end; } + codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; + + if (codec->type == AVMEDIA_TYPE_VIDEO) { + // Hardcoded video properties + codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + } else { + // Hardcoded audio properties +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT + codec_ctx->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; +#else + codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; + codec_ctx->channels = 2; +#endif + codec_ctx->sample_rate = 48000; + } + + if (avcodec_open2(codec_ctx, codec, NULL) < 0) { + LOGE("Demuxer '%s': could not open codec", demuxer->name); + goto finally_free_context; + } + + if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) { + goto finally_free_context; + } + // Config packets must be merged with the next non-config packet only for // video streams bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO; @@ -214,6 +241,9 @@ run_demuxer(void *data) { av_packet_free(&packet); finally_close_sinks: sc_packet_source_sinks_close(&demuxer->packet_source); +finally_free_context: + // This also calls avcodec_close() internally + avcodec_free_context(&codec_ctx); end: demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata); diff --git a/app/src/recorder.c b/app/src/recorder.c index 572d3e24..1e89608a 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -536,9 +536,8 @@ run_recorder(void *data) { static bool sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, - const AVCodec *codec) { + AVCodecContext *ctx) { struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); - assert(codec); sc_mutex_lock(&recorder->mutex); if (recorder->stopped) { @@ -546,7 +545,7 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, return false; } - recorder->video_codec = codec; + recorder->video_codec = ctx->codec; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); @@ -601,15 +600,14 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, static bool sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, - const AVCodec *codec) { + AVCodecContext *ctx) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); // only written from this thread, no need to lock assert(!recorder->audio_disabled); - assert(codec); sc_mutex_lock(&recorder->mutex); - recorder->audio_codec = codec; + recorder->audio_codec = ctx->codec; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); diff --git a/app/src/trait/frame_sink.h b/app/src/trait/frame_sink.h index 30bf0d37..8ef248b6 100644 --- a/app/src/trait/frame_sink.h +++ b/app/src/trait/frame_sink.h @@ -7,8 +7,6 @@ #include #include -typedef struct AVFrame AVFrame; - /** * Frame sink trait. * @@ -19,6 +17,7 @@ struct sc_frame_sink { }; struct sc_frame_sink_ops { + /* The codec context is valid until the sink is closed */ bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx); void (*close)(struct sc_frame_sink *sink); bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame); diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h index 099c8c52..84cfe814 100644 --- a/app/src/trait/packet_sink.h +++ b/app/src/trait/packet_sink.h @@ -5,9 +5,7 @@ #include #include - -typedef struct AVCodec AVCodec; -typedef struct AVPacket AVPacket; +#include /** * Packet sink trait. @@ -19,8 +17,8 @@ struct sc_packet_sink { }; struct sc_packet_sink_ops { - /* The codec instance is static, it is valid until the end of the program */ - bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); + /* The codec context is valid until the sink is closed */ + bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx); void (*close)(struct sc_packet_sink *sink); bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); diff --git a/app/src/trait/packet_source.c b/app/src/trait/packet_source.c index df678e16..c0836f1d 100644 --- a/app/src/trait/packet_source.c +++ b/app/src/trait/packet_source.c @@ -25,11 +25,11 @@ sc_packet_source_sinks_close_firsts(struct sc_packet_source *source, bool sc_packet_source_sinks_open(struct sc_packet_source *source, - const AVCodec *codec) { + AVCodecContext *ctx) { assert(source->sink_count); for (unsigned i = 0; i < source->sink_count; ++i) { struct sc_packet_sink *sink = source->sinks[i]; - if (!sink->ops->open(sink, codec)) { + if (!sink->ops->open(sink, ctx)) { sc_packet_source_sinks_close_firsts(source, i); return false; } diff --git a/app/src/trait/packet_source.h b/app/src/trait/packet_source.h index c34aa5d3..16d56e86 100644 --- a/app/src/trait/packet_source.h +++ b/app/src/trait/packet_source.h @@ -26,7 +26,7 @@ sc_packet_source_add_sink(struct sc_packet_source *source, bool sc_packet_source_sinks_open(struct sc_packet_source *source, - const AVCodec *codec); + AVCodecContext *ctx); void sc_packet_source_sinks_close(struct sc_packet_source *source); From 5052e15f7fd5049d9c65712182c1a4a1d349a65e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 21:50:34 +0100 Subject: [PATCH 0793/1133] Create recorder streams from packet sinks ops Previously, the packet sink push() implementation just set the codec and notified a wait condition. Then the recorder thread read the codec and created the AVStream. But this was racy: an AVFrame could be pushed before the creation of the AVStream, causing its video_stream_index or audio_stream_index to be initialized to -1. Also, in the future, the AVStream initialization might need data provided by the packet sink open(), so initialize it there (with a mutex). --- app/src/recorder.c | 128 +++++++++++++++++++-------------------------- app/src/recorder.h | 7 +-- 2 files changed, 57 insertions(+), 78 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 1e89608a..8f8a1a89 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -150,70 +150,22 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { avformat_free_context(recorder->ctx); } -static bool +static void sc_recorder_wait_video_stream(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->video_codec && !recorder->stopped) { + while (!recorder->video_init && !recorder->stopped) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } - const AVCodec *codec = recorder->video_codec; sc_mutex_unlock(&recorder->mutex); - - if (codec) { - AVStream *stream = avformat_new_stream(recorder->ctx, codec); - if (!stream) { - return false; - } - - stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - stream->codecpar->codec_id = codec->id; - stream->codecpar->format = AV_PIX_FMT_YUV420P; - stream->codecpar->width = recorder->declared_frame_size.width; - stream->codecpar->height = recorder->declared_frame_size.height; - - recorder->video_stream_index = stream->index; - } - - return true; } -static bool +static void sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->audio_codec && !recorder->audio_disabled - && !recorder->stopped) { + while (!recorder->audio_init && !recorder->stopped) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } - - if (recorder->audio_disabled) { - // Reset audio flag. From there, the recorder thread may access this - // flag without any mutex. - recorder->audio = false; - } - - const AVCodec *codec = recorder->audio_codec; sc_mutex_unlock(&recorder->mutex); - - if (codec) { - AVStream *stream = avformat_new_stream(recorder->ctx, codec); - if (!stream) { - return false; - } - - stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; - stream->codecpar->codec_id = codec->id; -#ifdef SCRCPY_LAVU_HAS_CHLAYOUT - stream->codecpar->ch_layout.nb_channels = 2; -#else - stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; - stream->codecpar->channels = 2; -#endif - stream->codecpar->sample_rate = 48000; - - recorder->audio_stream_index = stream->index; - } - - return true; } static inline bool @@ -480,18 +432,10 @@ sc_recorder_record(struct sc_recorder *recorder) { return false; } - ok = sc_recorder_wait_video_stream(recorder); - if (!ok) { - sc_recorder_close_output_file(recorder); - return false; - } + sc_recorder_wait_video_stream(recorder); if (recorder->audio) { - ok = sc_recorder_wait_audio_stream(recorder); - if (!ok) { - sc_recorder_close_output_file(recorder); - return false; - } + sc_recorder_wait_audio_stream(recorder); } // If recorder->stopped, process any queued packet anyway @@ -538,6 +482,8 @@ static bool sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) { struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); + // only written from this thread, no need to lock + assert(!recorder->video_init); sc_mutex_lock(&recorder->mutex); if (recorder->stopped) { @@ -545,7 +491,21 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, return false; } - recorder->video_codec = ctx->codec; + AVStream *stream = avformat_new_stream(recorder->ctx, ctx->codec); + if (!stream) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + + stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + stream->codecpar->codec_id = ctx->codec->id; + stream->codecpar->format = AV_PIX_FMT_YUV420P; + stream->codecpar->width = recorder->declared_frame_size.width; + stream->codecpar->height = recorder->declared_frame_size.height; + + recorder->video_stream_index = stream->index; + + recorder->video_init = true; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); @@ -555,6 +515,8 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, static void sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); + // only written from this thread, no need to lock + assert(recorder->video_init); sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder @@ -567,6 +529,8 @@ static bool sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { struct sc_recorder *recorder = DOWNCAST_VIDEO(sink); + // only written from this thread, no need to lock + assert(recorder->video_init); sc_mutex_lock(&recorder->mutex); @@ -604,10 +568,29 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); // only written from this thread, no need to lock - assert(!recorder->audio_disabled); + assert(!recorder->audio_init); sc_mutex_lock(&recorder->mutex); - recorder->audio_codec = ctx->codec; + + AVStream *stream = avformat_new_stream(recorder->ctx, ctx->codec); + if (!stream) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + + stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + stream->codecpar->codec_id = ctx->codec->id; +#ifdef SCRCPY_LAVU_HAS_CHLAYOUT + stream->codecpar->ch_layout.nb_channels = 2; +#else + stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; + stream->codecpar->channels = 2; +#endif + stream->codecpar->sample_rate = 48000; + + recorder->audio_stream_index = stream->index; + + recorder->audio_init = true; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); @@ -619,7 +602,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); // only written from this thread, no need to lock - assert(!recorder->audio_disabled); + assert(recorder->audio_init); sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder @@ -634,7 +617,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); // only written from this thread, no need to lock - assert(!recorder->audio_disabled); + assert(recorder->audio_init); sc_mutex_lock(&recorder->mutex); @@ -671,13 +654,13 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); // only written from this thread, no need to lock - assert(!recorder->audio_disabled); - assert(!recorder->audio_codec); + assert(!recorder->audio_init); LOGW("Audio stream recording disabled"); sc_mutex_lock(&recorder->mutex); - recorder->audio_disabled = true; + recorder->audio = false; + recorder->audio_init = true; sc_cond_signal(&recorder->stream_cond); sc_mutex_unlock(&recorder->mutex); } @@ -714,9 +697,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, sc_vecdeque_init(&recorder->audio_queue); recorder->stopped = false; - recorder->video_codec = NULL; - recorder->audio_codec = NULL; - recorder->audio_disabled = false; + recorder->video_init = false; + recorder->audio_init = false; recorder->video_stream_index = -1; recorder->audio_stream_index = -1; diff --git a/app/src/recorder.h b/app/src/recorder.h index e3d5f018..35758db7 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -43,11 +43,8 @@ struct sc_recorder { // wake up the recorder thread once the video or audio codec is known sc_cond stream_cond; - const AVCodec *video_codec; - const AVCodec *audio_codec; - // Instead of providing an audio_codec, the demuxer may notify that the - // stream is disabled if the device could not capture audio - bool audio_disabled; + bool video_init; + bool audio_init; int video_stream_index; int audio_stream_index; From a9f6001f51b9348d65578178a3e1f6a89efc4aa8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 21:54:28 +0100 Subject: [PATCH 0794/1133] Simplify recorder After the refactor performed by the previous commit, the functions to wait the video stream and the audio stream could be inlined. --- app/src/recorder.c | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 8f8a1a89..9b646055 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -150,24 +150,6 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { avformat_free_context(recorder->ctx); } -static void -sc_recorder_wait_video_stream(struct sc_recorder *recorder) { - sc_mutex_lock(&recorder->mutex); - while (!recorder->video_init && !recorder->stopped) { - sc_cond_wait(&recorder->stream_cond, &recorder->mutex); - } - sc_mutex_unlock(&recorder->mutex); -} - -static void -sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { - sc_mutex_lock(&recorder->mutex); - while (!recorder->audio_init && !recorder->stopped) { - sc_cond_wait(&recorder->stream_cond, &recorder->mutex); - } - sc_mutex_unlock(&recorder->mutex); -} - static inline bool sc_recorder_has_empty_queues(struct sc_recorder *recorder) { if (sc_vecdeque_is_empty(&recorder->video_queue)) { @@ -188,8 +170,10 @@ static bool sc_recorder_process_header(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped && sc_recorder_has_empty_queues(recorder)) { - sc_cond_wait(&recorder->queue_cond, &recorder->mutex); + while (!recorder->stopped && (!recorder->video_init + || !recorder->audio_init + || sc_recorder_has_empty_queues(recorder))) { + sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } if (sc_vecdeque_is_empty(&recorder->video_queue)) { @@ -432,14 +416,6 @@ sc_recorder_record(struct sc_recorder *recorder) { return false; } - sc_recorder_wait_video_stream(recorder); - - if (recorder->audio) { - sc_recorder_wait_audio_stream(recorder); - } - - // If recorder->stopped, process any queued packet anyway - ok = sc_recorder_process_packets(recorder); sc_recorder_close_output_file(recorder); return ok; From be985b8242e0626288674b84c5039725170f8f0c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:46:56 +0100 Subject: [PATCH 0795/1133] Copy codec parameters from context Now that the recorder have access to the codec context, it may automatically initialize the stream codec parameters. The V4L2 sink could do the same. --- app/src/recorder.c | 23 +++++++++++------------ app/src/v4l2_sink.c | 8 +++++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 9b646055..e8484256 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -473,9 +473,12 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, return false; } - stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - stream->codecpar->codec_id = ctx->codec->id; - stream->codecpar->format = AV_PIX_FMT_YUV420P; + int r = avcodec_parameters_from_context(stream->codecpar, ctx); + if (r < 0) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + stream->codecpar->width = recorder->declared_frame_size.width; stream->codecpar->height = recorder->declared_frame_size.height; @@ -554,15 +557,11 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, return false; } - stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; - stream->codecpar->codec_id = ctx->codec->id; -#ifdef SCRCPY_LAVU_HAS_CHLAYOUT - stream->codecpar->ch_layout.nb_channels = 2; -#else - stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO; - stream->codecpar->channels = 2; -#endif - stream->codecpar->sample_rate = 48000; + int r = avcodec_parameters_from_context(stream->codecpar, ctx); + if (r < 0) { + sc_mutex_unlock(&recorder->mutex); + return false; + } recorder->audio_stream_index = stream->index; diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index fe11614a..c6714d18 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -205,9 +205,11 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { goto error_avformat_free_context; } - ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - ostream->codecpar->codec_id = encoder->id; - ostream->codecpar->format = AV_PIX_FMT_YUV420P; + int r = avcodec_parameters_from_context(ostream->codecpar, ctx); + if (r < 0) { + goto error_avformat_free_context; + } + ostream->codecpar->width = vs->frame_size.width; ostream->codecpar->height = vs->frame_size.height; From aa1efbc35c468b1748b2ae4c542da443ab0e4714 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:47:38 +0100 Subject: [PATCH 0796/1133] Rename sendCodecId to sendCodecMeta This will allow the codec header to contain more than the codec id. --- .../src/main/java/com/genymobile/scrcpy/Options.java | 10 +++++----- .../src/main/java/com/genymobile/scrcpy/Server.java | 12 ++++++------ .../main/java/com/genymobile/scrcpy/Streamer.java | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index bcf235ed..2a3de757 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -40,7 +40,7 @@ public class Options { private boolean sendDeviceMeta = true; // send device name and size private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean sendDummyByte = true; // write a byte on start to detect connection issues - private boolean sendCodecId = true; // write the codec ID (4 bytes) before the stream + private boolean sendCodecMeta = true; // write the codec metadata before the stream public Ln.Level getLogLevel() { return logLevel; @@ -282,11 +282,11 @@ public class Options { this.sendDummyByte = sendDummyByte; } - public boolean getSendCodecId() { - return sendCodecId; + public boolean getSendCodecMeta() { + return sendCodecMeta; } - public void setSendCodecId(boolean sendCodecId) { - this.sendCodecId = sendCodecId; + public void setSendCodecMeta(boolean sendCodecMeta) { + this.sendCodecMeta = sendCodecMeta; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 86555e3b..2ece7415 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -108,7 +108,7 @@ public final class Server { if (audio) { AudioCodec audioCodec = options.getAudioCodec(); - Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecId(), + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta()); AsyncProcessor audioRecorder; if (audioCodec == AudioCodec.RAW) { @@ -120,7 +120,7 @@ public final class Server { asyncProcessors.add(audioRecorder); } - Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(), + Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), options.getSendFrameMeta()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); @@ -315,9 +315,9 @@ public final class Server { boolean sendDummyByte = Boolean.parseBoolean(value); options.setSendDummyByte(sendDummyByte); break; - case "send_codec_id": - boolean sendCodecId = Boolean.parseBoolean(value); - options.setSendCodecId(sendCodecId); + case "send_codec_meta": + boolean sendCodecMeta = Boolean.parseBoolean(value); + options.setSendCodecMeta(sendCodecMeta); break; case "raw_video_stream": boolean rawVideoStream = Boolean.parseBoolean(value); @@ -325,7 +325,7 @@ public final class Server { options.setSendDeviceMeta(false); options.setSendFrameMeta(false); options.setSendDummyByte(false); - options.setSendCodecId(false); + options.setSendCodecMeta(false); } break; default: diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index 9bfe7e91..f099cf4f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -15,15 +15,15 @@ public final class Streamer { private final FileDescriptor fd; private final Codec codec; - private final boolean sendCodecId; + private final boolean sendCodecMeta; private final boolean sendFrameMeta; private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); - public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecId, boolean sendFrameMeta) { + public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) { this.fd = fd; this.codec = codec; - this.sendCodecId = sendCodecId; + this.sendCodecMeta = sendCodecMeta; this.sendFrameMeta = sendFrameMeta; } @@ -32,7 +32,7 @@ public final class Streamer { } public void writeHeader() throws IOException { - if (sendCodecId) { + if (sendCodecMeta) { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putInt(codec.getId()); buffer.flip(); From 3a72f3fb4da0dca0ab34824396c3efdf1819e5df Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:49:40 +0100 Subject: [PATCH 0797/1133] Report errors on screen event error Make scrcpy fail if an important screen event (like frame update) fails. --- app/src/scrcpy.c | 4 +++- app/src/screen.c | 22 ++++++++++++---------- app/src/screen.h | 3 ++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ce045c97..09a8f918 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -175,7 +175,9 @@ event_loop(struct scrcpy *s) { LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; default: - sc_screen_handle_event(&s->screen, &event); + if (!sc_screen_handle_event(&s->screen, &event)) { + return SCRCPY_EXIT_FAILURE; + } break; } } diff --git a/app/src/screen.c b/app/src/screen.c index b814ada1..56463711 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -790,7 +790,7 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI; } -void +bool sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { bool relative_mode = sc_screen_is_relative_mode(screen); @@ -798,14 +798,15 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { case SC_EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); if (!ok) { - LOGW("Frame update failed\n"); + LOGE("Frame update failed\n"); + return false; } - return; + return true; } case SDL_WINDOWEVENT: if (!screen->has_frame) { // Do nothing - return; + return true; } switch (event->window.event) { case SDL_WINDOWEVENT_EXPOSED: @@ -836,7 +837,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { } break; } - return; + return true; case SDL_KEYDOWN: if (relative_mode) { SDL_Keycode key = event->key.keysym.sym; @@ -849,7 +850,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { screen->mouse_capture_key_pressed = 0; } // Mouse capture keys are never forwarded to the device - return; + return true; } } break; @@ -865,7 +866,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { sc_screen_toggle_mouse_capture(screen); } // Mouse capture keys are never forwarded to the device - return; + return true; } } break; @@ -875,7 +876,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (relative_mode && !sc_screen_get_mouse_capture(screen)) { // Do not forward to input manager, the mouse will be captured // on SDL_MOUSEBUTTONUP - return; + return true; } break; case SDL_FINGERMOTION: @@ -884,18 +885,19 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { if (relative_mode) { // Touch events are not compatible with relative mode // (coordinates are not relative) - return; + return true; } break; case SDL_MOUSEBUTTONUP: if (relative_mode && !sc_screen_get_mouse_capture(screen)) { sc_screen_set_mouse_capture(screen, true); - return; + return true; } break; } sc_input_manager_handle_event(&screen->im, event); + return true; } struct sc_point diff --git a/app/src/screen.h b/app/src/screen.h index 28afea40..57927894 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -136,7 +136,8 @@ void sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); // react to SDL events -void +// If this function returns false, scrcpy must exit with an error. +bool sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event); // convert point from window coordinates to frame coordinates From 238ab872ba0ea18c2553dfe3c2202d64d67d8960 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 09:21:49 +0100 Subject: [PATCH 0798/1133] Pass video size as codec metadata On initial connection, scrcpy sent some device metadata: - the device name (to be used as window title) - the initial video size (before any frame or even SPS/PPS) But it is better to provide the initial video size as part as the video stream, so that it can be demuxed and exposed via AVCodecContext to sinks. This avoids to pass an explicit "initial frame size" for the screen, the recorder and the v4l2 sink. --- app/src/demuxer.c | 24 +++++- app/src/events.h | 1 + app/src/recorder.c | 5 -- app/src/recorder.h | 2 - app/src/scrcpy.c | 6 +- app/src/screen.c | 79 +++++++++++++------ app/src/screen.h | 1 - app/src/server.c | 7 +- app/src/server.h | 1 - app/src/v4l2_sink.c | 12 +-- app/src/v4l2_sink.h | 4 +- .../com/genymobile/scrcpy/AudioEncoder.java | 2 +- .../genymobile/scrcpy/AudioRawRecorder.java | 2 +- .../genymobile/scrcpy/DesktopConnection.java | 8 +- .../com/genymobile/scrcpy/ScreenEncoder.java | 2 +- .../java/com/genymobile/scrcpy/Server.java | 3 +- .../java/com/genymobile/scrcpy/Streamer.java | 14 +++- 17 files changed, 104 insertions(+), 69 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index eabcb81e..96303155 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -57,6 +57,20 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) { return true; } +static bool +sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width, + uint32_t *height) { + uint8_t data[8]; + ssize_t r = net_recv_all(demuxer->socket, data, 8); + if (r < 8) { + return false; + } + + *width = sc_read32be(data); + *height = sc_read32be(data + 4); + return true; +} + static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { // The video stream contains raw packets, without time information. When we @@ -169,7 +183,15 @@ run_demuxer(void *data) { codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; if (codec->type == AVMEDIA_TYPE_VIDEO) { - // Hardcoded video properties + uint32_t width; + uint32_t height; + ok = sc_demuxer_recv_video_size(demuxer, &width, &height); + if (!ok) { + goto finally_free_context; + } + + codec_ctx->width = width; + codec_ctx->height = height; codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; } else { // Hardcoded audio properties diff --git a/app/src/events.h b/app/src/events.h index 0a45b652..609e3198 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -5,3 +5,4 @@ #define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4) #define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5) #define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) +#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7) diff --git a/app/src/recorder.c b/app/src/recorder.c index e8484256..2fc95eca 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -479,9 +479,6 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, return false; } - stream->codecpar->width = recorder->declared_frame_size.width; - stream->codecpar->height = recorder->declared_frame_size.height; - recorder->video_stream_index = stream->index; recorder->video_init = true; @@ -643,7 +640,6 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool audio, - struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { recorder->filename = strdup(filename); if (!recorder->filename) { @@ -679,7 +675,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->audio_stream_index = -1; recorder->format = format; - recorder->declared_frame_size = declared_frame_size; assert(cbs && cbs->on_ended); recorder->cbs = cbs; diff --git a/app/src/recorder.h b/app/src/recorder.h index 35758db7..41b8db65 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -31,7 +31,6 @@ struct sc_recorder { char *filename; enum sc_record_format format; AVFormatContext *ctx; - struct sc_size declared_frame_size; sc_thread thread; sc_mutex mutex; @@ -61,7 +60,6 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool audio, - struct sc_size declared_frame_size, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 09a8f918..9e5ec6f0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -473,7 +473,7 @@ scrcpy(struct scrcpy_options *options) { }; if (!sc_recorder_init(&s->recorder, options->record_filename, options->record_format, options->audio, - info->frame_size, &recorder_cbs, NULL)) { + &recorder_cbs, NULL)) { goto end; } recorder_initialized = true; @@ -660,7 +660,6 @@ aoa_hid_end: .clipboard_autosync = options->clipboard_autosync, .shortcut_mods = &options->shortcut_mods, .window_title = window_title, - .frame_size = info->frame_size, .always_on_top = options->always_on_top, .window_x = options->window_x, .window_y = options->window_y, @@ -697,8 +696,7 @@ aoa_hid_end: #ifdef HAVE_V4L2 if (options->v4l2_device) { - if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, - info->frame_size)) { + if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device)) { goto end; } diff --git a/app/src/screen.c b/app/src/screen.c index 56463711..f74fd8a5 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -239,7 +239,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) { } } -static inline SDL_Texture * +static bool create_texture(struct sc_screen *screen) { SDL_Renderer *renderer = screen->renderer; struct sc_size size = screen->frame_size; @@ -247,7 +247,8 @@ create_texture(struct sc_screen *screen) { SDL_TEXTUREACCESS_STREAMING, size.width, size.height); if (!texture) { - return NULL; + LOGE("Could not create texture: %s", SDL_GetError()); + return false; } if (screen->mipmaps) { @@ -263,7 +264,8 @@ create_texture(struct sc_screen *screen) { SDL_GL_UnbindTexture(texture); } - return texture; + screen->texture = texture; + return true; } // render the texture to the renderer @@ -335,7 +337,25 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink, (void) ctx; struct sc_screen *screen = DOWNCAST(sink); - (void) screen; + + assert(ctx->width > 0 && ctx->width <= 0xFFFF); + assert(ctx->height > 0 && ctx->height <= 0xFFFF); + // screen->frame_size is never used before the event is pushed, and the + // event acts as a memory barrier so it is safe without mutex + screen->frame_size.width = ctx->width; + screen->frame_size.height = ctx->height; + + static SDL_Event event = { + .type = SC_EVENT_SCREEN_INIT_SIZE, + }; + + // Post the event on the UI thread (the texture must be created from there) + int ret = SDL_PushEvent(&event); + if (ret < 0) { + LOGW("Could not post init size event: %s", SDL_GetError()); + return false; + } + #ifndef NDEBUG screen->open = true; #endif @@ -410,14 +430,10 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_frame_buffer; } - screen->frame_size = params->frame_size; screen->rotation = params->rotation; if (screen->rotation) { LOGI("Initial display rotation set to %u", screen->rotation); } - struct sc_size content_size = - get_rotated_size(screen->frame_size, screen->rotation); - screen->content_size = content_size; uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE @@ -485,18 +501,10 @@ sc_screen_init(struct sc_screen *screen, LOGW("Could not load icon"); } - LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width, - params->frame_size.height); - screen->texture = create_texture(screen); - if (!screen->texture) { - LOGE("Could not create texture: %s", SDL_GetError()); - goto error_destroy_renderer; - } - screen->frame = av_frame_alloc(); if (!screen->frame) { LOG_OOM(); - goto error_destroy_texture; + goto error_destroy_renderer; } struct sc_input_manager_params im_params = { @@ -531,8 +539,6 @@ sc_screen_init(struct sc_screen *screen, return true; -error_destroy_texture: - SDL_DestroyTexture(screen->texture); error_destroy_renderer: SDL_DestroyRenderer(screen->renderer); error_destroy_window: @@ -591,7 +597,9 @@ sc_screen_destroy(struct sc_screen *screen) { assert(!screen->open); #endif av_frame_free(&screen->frame); - SDL_DestroyTexture(screen->texture); + if (screen->texture) { + SDL_DestroyTexture(screen->texture); + } SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); sc_fps_counter_destroy(&screen->fps_counter); @@ -655,6 +663,23 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) { sc_screen_render(screen, true); } +static bool +sc_screen_init_size(struct sc_screen *screen) { + // Before first frame + assert(!screen->has_frame); + assert(!screen->texture); + + // The requested size is passed via screen->frame_size + + struct sc_size content_size = + get_rotated_size(screen->frame_size, screen->rotation); + screen->content_size = content_size; + + LOGI("Initial texture: %" PRIu16 "x%" PRIu16, + screen->frame_size.width, screen->frame_size.height); + return create_texture(screen); +} + // recreate the texture and resize the window if the frame size has changed static bool prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { @@ -673,11 +698,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { LOGI("New texture: %" PRIu16 "x%" PRIu16, screen->frame_size.width, screen->frame_size.height); - screen->texture = create_texture(screen); - if (!screen->texture) { - LOGE("Could not create texture: %s", SDL_GetError()); - return false; - } + return create_texture(screen); } return true; @@ -795,6 +816,14 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { bool relative_mode = sc_screen_is_relative_mode(screen); switch (event->type) { + case SC_EVENT_SCREEN_INIT_SIZE: + // The initial size is passed via screen->frame_size + bool ok = sc_screen_init_size(screen); + if (!ok) { + LOGE("Could not initialize screen size"); + return false; + } + return true; case SC_EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); if (!ok) { diff --git a/app/src/screen.h b/app/src/screen.h index 57927894..4fca04d8 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -78,7 +78,6 @@ struct sc_screen_params { const struct sc_shortcut_mods *shortcut_mods; const char *window_title; - struct sc_size frame_size; bool always_on_top; int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED diff --git a/app/src/server.c b/app/src/server.c index 7b503427..8c4e9a95 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -441,9 +441,9 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, static bool device_read_info(struct sc_intr *intr, sc_socket device_socket, struct sc_server_info *info) { - unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4]; + unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH]; ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf)); - if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) { + if (r < SC_DEVICE_NAME_FIELD_LENGTH) { LOGE("Could not retrieve device information"); return false; } @@ -451,9 +451,6 @@ device_read_info(struct sc_intr *intr, sc_socket device_socket, buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; memcpy(info->device_name, (char *) buf, sizeof(info->device_name)); - unsigned char *fields = &buf[SC_DEVICE_NAME_FIELD_LENGTH]; - info->frame_size.width = sc_read16be(fields); - info->frame_size.height = sc_read16be(&fields[2]); return true; } diff --git a/app/src/server.h b/app/src/server.h index 8edf2666..c425856b 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -18,7 +18,6 @@ #define SC_DEVICE_NAME_FIELD_LENGTH 64 struct sc_server_info { char device_name[SC_DEVICE_NAME_FIELD_LENGTH]; - struct sc_size frame_size; }; struct sc_server_params { diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index c6714d18..717d2bd5 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -210,9 +210,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { goto error_avformat_free_context; } - ostream->codecpar->width = vs->frame_size.width; - ostream->codecpar->height = vs->frame_size.height; - int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE); if (ret < 0) { LOGE("Failed to open output device: %s", vs->device_name); @@ -226,8 +223,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { goto error_avio_close; } - vs->encoder_ctx->width = vs->frame_size.width; - vs->encoder_ctx->height = vs->frame_size.height; + vs->encoder_ctx->width = ctx->width; + vs->encoder_ctx->height = ctx->height; vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P; vs->encoder_ctx->time_base.num = 1; vs->encoder_ctx->time_base.den = 1; @@ -343,16 +340,13 @@ 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_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name) { vs->device_name = strdup(device_name); if (!vs->device_name) { LOGE("Could not strdup v4l2 device name"); return false; } - vs->frame_size = frame_size; - static const struct sc_frame_sink_ops ops = { .open = sc_v4l2_frame_sink_open, .close = sc_v4l2_frame_sink_close, diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index 789e31c3..365a739d 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -19,7 +19,6 @@ struct sc_v4l2_sink { AVCodecContext *encoder_ctx; char *device_name; - struct sc_size frame_size; sc_thread thread; sc_mutex mutex; @@ -33,8 +32,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_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name); void sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 0ba424ca..24d685c5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -101,7 +101,7 @@ public final class AudioEncoder implements AsyncProcessor { } private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException { - streamer.writeHeader(); + streamer.writeAudioHeader(); while (!Thread.currentThread().isInterrupted()) { OutputTask task = outputTasks.take(); diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 2e483daa..4b1b5bd0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -26,7 +26,7 @@ public final class AudioRawRecorder implements AsyncProcessor { try { capture.start(); - streamer.writeHeader(); + streamer.writeAudioHeader(); while (!Thread.currentThread().isInterrupted()) { buffer.position(0); int r = capture.read(buffer, READ_SIZE, bufferInfo); diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 3e743621..4bfff726 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -122,18 +122,14 @@ public final class DesktopConnection implements Closeable { } } - public void sendDeviceMeta(String deviceName, int width, int height) throws IOException { - byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; + public void sendDeviceMeta(String deviceName) throws IOException { + byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH]; byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8); int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1); System.arraycopy(deviceNameBytes, 0, buffer, 0, len); // byte[] are always 0-initialized in java, no need to set '\0' explicitly - buffer[DEVICE_NAME_FIELD_LENGTH] = (byte) (width >> 8); - buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; - buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); - buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; IO.writeFully(videoFd, buffer, 0, buffer.length); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f5f996ba..015cc993 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -66,7 +66,7 @@ public class ScreenEncoder implements Device.RotationListener { IBinder display = createDisplay(); device.setRotationListener(this); - streamer.writeHeader(); + streamer.writeVideoHeader(device.getScreenInfo().getVideoSize()); boolean alive; try { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2ece7415..5800487d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -96,8 +96,7 @@ public final class Server { try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { if (options.getSendDeviceMeta()) { - Size videoSize = device.getScreenInfo().getVideoSize(); - connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); + connection.sendDeviceMeta(Device.getDeviceName()); } if (control) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index f099cf4f..39f74fb6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -30,8 +30,7 @@ public final class Streamer { public Codec getCodec() { return codec; } - - public void writeHeader() throws IOException { + public void writeAudioHeader() throws IOException { if (sendCodecMeta) { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putInt(codec.getId()); @@ -40,6 +39,17 @@ public final class Streamer { } } + public void writeVideoHeader(Size videoSize) throws IOException { + if (sendCodecMeta) { + ByteBuffer buffer = ByteBuffer.allocate(12); + buffer.putInt(codec.getId()); + buffer.putInt(videoSize.getWidth()); + buffer.putInt(videoSize.getHeight()); + buffer.flip(); + IO.writeFully(fd, buffer); + } + } + public void writeDisableStream(boolean error) throws IOException { // Writing a specific code as codec-id means that the device disables the stream // code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only From bb509d9317ec073cd650f9ae9796a24b4cfff34c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 09:31:32 +0100 Subject: [PATCH 0799/1133] Define the audio output buffer in milliseconds In theory, this buffer must be dimensioned for a target duration, so its size in bytes should depend on the sample rate. --- app/src/audio_player.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 7a348f93..a4a73f8d 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -13,7 +13,7 @@ #define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT #define SC_SDL_SAMPLE_FMT AUDIO_F32 -#define SC_AUDIO_OUTPUT_BUFFER_SAMPLES 240 // 5ms at 48000Hz +#define SC_AUDIO_OUTPUT_BUFFER_MS 5 static inline uint32_t bytes_to_samples(struct sc_audio_player *ap, size_t bytes) { @@ -202,8 +202,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, bool played = ap->played; if (played) { uint32_t max_buffered_samples = ap->target_buffering - + 12 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES - + ap->target_buffering / 10; + + 12 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000 + + ap->target_buffering / 10; if (buffered_samples > max_buffered_samples) { uint32_t skip_samples = buffered_samples - max_buffered_samples; size_t skip_bytes = samples_to_bytes(ap, skip_samples); @@ -231,7 +231,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // max_initial_buffering samples, this would cause unnecessary delay // (and glitches to compensate) on start. uint32_t max_initial_buffering = ap->target_buffering - + 2 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES; + + 2 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000; if (buffered_samples > max_initial_buffering) { uint32_t skip_samples = buffered_samples - max_initial_buffering; size_t skip_bytes = samples_to_bytes(ap, skip_samples); @@ -298,7 +298,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, .freq = ctx->sample_rate, .format = SC_SDL_SAMPLE_FMT, .channels = nb_channels, - .samples = SC_AUDIO_OUTPUT_BUFFER_SAMPLES, + .samples = SC_AUDIO_OUTPUT_BUFFER_MS * ctx->sample_rate / 1000, .callback = sc_audio_player_sdl_callback, .userdata = ap, }; From 14f9d82fdad4bce7887a65603946e43e1a8318e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 10:13:00 +0100 Subject: [PATCH 0800/1133] Add audio sample ring-buffer Add a thin wrapper around bytebuf to handle samples instead of bytes. This simplifies the audio player, which mostly handles samples. --- app/src/audio_player.c | 120 ++++++++++++++++------------------------ app/src/audio_player.h | 10 ++-- app/src/util/audiobuf.h | 94 +++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 76 deletions(-) create mode 100644 app/src/util/audiobuf.h diff --git a/app/src/audio_player.c b/app/src/audio_player.c index a4a73f8d..320af082 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -15,42 +15,32 @@ #define SC_AUDIO_OUTPUT_BUFFER_MS 5 -static inline uint32_t -bytes_to_samples(struct sc_audio_player *ap, size_t bytes) { - assert(bytes % (ap->nb_channels * ap->out_bytes_per_sample) == 0); - return bytes / (ap->nb_channels * ap->out_bytes_per_sample); -} - -static inline size_t -samples_to_bytes(struct sc_audio_player *ap, uint32_t samples) { - return samples * ap->nb_channels * ap->out_bytes_per_sample; -} +#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES)) +#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES)) static void SDLCALL sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { struct sc_audio_player *ap = userdata; // This callback is called with the lock used by SDL_AudioDeviceLock(), so - // the bytebuf is protected + // the audiobuf is protected assert(len_int > 0); size_t len = len_int; + uint32_t count = TO_SAMPLES(len); #ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] SDL callback requests %" PRIu32 " samples", - bytes_to_samples(ap, len)); + LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count); #endif - size_t read_avail = sc_bytebuf_read_available(&ap->buf); + uint32_t buffered_samples = sc_audiobuf_read_available(&ap->buf); if (!ap->played) { - uint32_t buffered_samples = bytes_to_samples(ap, read_avail); - // Part of the buffering is handled by inserting initial silence. The // remaining (margin) last samples will be handled by compensation. uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms if (buffered_samples + margin < ap->target_buffering) { LOGV("[Audio] Inserting initial buffering silence: %" PRIu32 - " samples", bytes_to_samples(ap, len)); + " samples", count); // Delay playback starting to reach the target buffering. Fill the // whole buffer with silence (len is small compared to the // arbitrary margin value). @@ -59,26 +49,25 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { } } - size_t read = MIN(read_avail, len); + uint32_t read = MIN(buffered_samples, count); if (read) { - sc_bytebuf_read(&ap->buf, stream, read); + sc_audiobuf_read(&ap->buf, stream, read); } - if (read < len) { - size_t silence_bytes = len - read; - uint32_t silence_samples = bytes_to_samples(ap, silence_bytes); + if (read < count) { + uint32_t silence = count - read; // Insert silence. In theory, the inserted silent samples replace the // missing real samples, which will arrive later, so they should be // dropped to keep the latency minimal. However, this would cause very // audible glitches, so let the clock compensation restore the target // latency. LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples", - silence_samples); - memset(stream + read, 0, silence_bytes); + silence); + memset(stream + read, 0, TO_BYTES(silence)); if (ap->received) { // Inserting additional samples immediately increases buffering - ap->avg_buffering.avg += silence_samples; + ap->avg_buffering.avg += silence; } } @@ -87,7 +76,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { static uint8_t * sc_audio_player_get_swr_buf(struct sc_audio_player *ap, uint32_t min_samples) { - size_t min_buf_size = samples_to_bytes(ap, min_samples); + size_t min_buf_size = TO_BYTES(min_samples); if (min_buf_size > ap->swr_buf_alloc_size) { size_t new_size = min_buf_size + 4096; uint8_t *buf = realloc(ap->swr_buf, new_size); @@ -130,7 +119,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // swr_convert() returns the number of samples which would have been // written if the buffer was big enough. uint32_t samples_written = MIN(ret, dst_nb_samples); - size_t swr_buf_size = samples_to_bytes(ap, samples_written); #ifndef SC_AUDIO_PLAYER_NDEBUG LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written); #endif @@ -138,46 +126,40 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // Since this function is the only writer, the current available space is // at least the previous available space. In practice, it should almost // always be possible to write without lock. - bool lockless_write = swr_buf_size <= ap->previous_write_avail; + bool lockless_write = samples_written <= ap->previous_write_avail; if (lockless_write) { - sc_bytebuf_prepare_write(&ap->buf, swr_buf, swr_buf_size); + sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written); } SDL_LockAudioDevice(ap->device); - size_t read_avail = sc_bytebuf_read_available(&ap->buf); - uint32_t buffered_samples = bytes_to_samples(ap, read_avail); + uint32_t buffered_samples = sc_audiobuf_read_available(&ap->buf); if (lockless_write) { - sc_bytebuf_commit_write(&ap->buf, swr_buf_size); + sc_audiobuf_commit_write(&ap->buf, samples_written); } else { - // Take care to keep full samples - size_t align = ap->nb_channels * ap->out_bytes_per_sample; - size_t write_avail = - sc_bytebuf_write_available(&ap->buf) / align * align; - if (swr_buf_size > write_avail) { - // Entering this branch is very unlikely, the ring-buffer (bytebuf) - // is allocated with a size sufficient to store 1 second more than - // the target buffering. If this happens, though, we have to skip - // old samples. - size_t cap = sc_bytebuf_capacity(&ap->buf) / align * align; - if (swr_buf_size > cap) { + uint32_t write_avail = sc_audiobuf_write_available(&ap->buf); + if (samples_written > write_avail) { + // Entering this branch is very unlikely, the audio buffer is + // allocated with a size sufficient to store 1 second more than the + // target buffering. If this happens, though, we have to skip old + // samples. + uint32_t cap = sc_audiobuf_capacity(&ap->buf); + if (samples_written > cap) { // Very very unlikely: a single resampled frame should never - // exceed the ring-buffer size (or something is very wrong). + // exceed the audio buffer size (or something is very wrong). // Ignore the first bytes in swr_buf - swr_buf += swr_buf_size - cap; - swr_buf_size = cap; + swr_buf += TO_BYTES(samples_written - cap); // This change in samples_written will impact the // instant_compensation below - samples_written -= bytes_to_samples(ap, swr_buf_size - cap); + samples_written = cap; } - assert(swr_buf_size >= write_avail); - if (swr_buf_size > write_avail) { - sc_bytebuf_skip(&ap->buf, swr_buf_size - write_avail); - uint32_t skip_samples = - bytes_to_samples(ap, swr_buf_size - write_avail); + assert(samples_written >= write_avail); + if (samples_written > write_avail) { + uint32_t skip_samples = samples_written - write_avail; assert(buffered_samples >= skip_samples); + sc_audiobuf_skip(&ap->buf, skip_samples); buffered_samples -= skip_samples; if (ap->played) { // Dropping input samples instantly decreases buffering @@ -187,16 +169,14 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // It should remain exactly the expected size to write the new // samples. - assert((sc_bytebuf_write_available(&ap->buf) / align * align) - == swr_buf_size); + assert(sc_audiobuf_write_available(&ap->buf) == samples_written); } - sc_bytebuf_write(&ap->buf, swr_buf, swr_buf_size); + sc_audiobuf_write(&ap->buf, swr_buf, samples_written); } buffered_samples += samples_written; - assert(samples_to_bytes(ap, buffered_samples) - == sc_bytebuf_read_available(&ap->buf)); + assert(buffered_samples == sc_audiobuf_read_available(&ap->buf)); // Read with lock held, to be used after unlocking bool played = ap->played; @@ -206,8 +186,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, + ap->target_buffering / 10; if (buffered_samples > max_buffered_samples) { uint32_t skip_samples = buffered_samples - max_buffered_samples; - size_t skip_bytes = samples_to_bytes(ap, skip_samples); - sc_bytebuf_skip(&ap->buf, skip_bytes); + sc_audiobuf_skip(&ap->buf, skip_samples); LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32 " samples", skip_samples); } @@ -234,8 +213,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, + 2 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000; if (buffered_samples > max_initial_buffering) { uint32_t skip_samples = buffered_samples - max_initial_buffering; - size_t skip_bytes = samples_to_bytes(ap, skip_samples); - sc_bytebuf_skip(&ap->buf, skip_bytes); + sc_audiobuf_skip(&ap->buf, skip_samples); #ifndef SC_AUDIO_PLAYER_NDEBUG LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples", skip_samples); @@ -243,7 +221,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, } } - ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf); + ap->previous_write_avail = sc_audiobuf_write_available(&ap->buf); ap->received = true; SDL_UnlockAudioDevice(ap->device); @@ -355,23 +333,23 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, // producer and the consumer. It's too big on purpose, to guarantee that // the producer and the consumer will be able to access it in parallel // without locking. - size_t bytebuf_samples = ap->target_buffering + ap->sample_rate; - size_t bytebuf_size = samples_to_bytes(ap, bytebuf_samples); + size_t audiobuf_samples = ap->target_buffering + ap->sample_rate; - bool ok = sc_bytebuf_init(&ap->buf, bytebuf_size); + size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample; + bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples); if (!ok) { goto error_free_swr_ctx; } - size_t initial_swr_buf_size = samples_to_bytes(ap, 4096); + size_t initial_swr_buf_size = TO_BYTES(4096); ap->swr_buf = malloc(initial_swr_buf_size); if (!ap->swr_buf) { LOG_OOM(); - goto error_destroy_bytebuf; + goto error_destroy_audiobuf; } ap->swr_buf_alloc_size = initial_swr_buf_size; - ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf); + ap->previous_write_avail = sc_audiobuf_write_available(&ap->buf); // Samples are produced and consumed by blocks, so the buffering must be // smoothed to get a relatively stable value. @@ -393,8 +371,8 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, return true; -error_destroy_bytebuf: - sc_bytebuf_destroy(&ap->buf); +error_destroy_audiobuf: + sc_audiobuf_destroy(&ap->buf); error_free_swr_ctx: swr_free(&ap->swr_ctx); error_close_audio_device: @@ -412,7 +390,7 @@ sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) { SDL_CloseAudioDevice(ap->device); free(ap->swr_buf); - sc_bytebuf_destroy(&ap->buf); + sc_audiobuf_destroy(&ap->buf); swr_free(&ap->swr_ctx); } diff --git a/app/src/audio_player.h b/app/src/audio_player.h index c64760ec..e82735c2 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -5,8 +5,8 @@ #include #include "trait/frame_sink.h" +#include #include -#include #include #include @@ -29,11 +29,11 @@ struct sc_audio_player { // Audio buffer to communicate between the receiver and the SDL audio // callback (protected by SDL_AudioDeviceLock()) - struct sc_bytebuf buf; + struct sc_audiobuf buf; - // The previous number of bytes available in the buffer (only used by the - // receiver thread) - size_t previous_write_avail; + // The previous empty space in the buffer (only used by the receiver + // thread) + uint32_t previous_write_avail; // Resampler (only used from the receiver thread) struct SwrContext *swr_ctx; diff --git a/app/src/util/audiobuf.h b/app/src/util/audiobuf.h new file mode 100644 index 00000000..16bf20d3 --- /dev/null +++ b/app/src/util/audiobuf.h @@ -0,0 +1,94 @@ +#ifndef SC_AUDIOBUF_H +#define SC_AUDIOBUF_H + +#include "common.h" + +#include +#include + +#include "util/bytebuf.h" + +/** + * Wrapper around bytebuf to read and write samples + * + * Each sample takes sample_size bytes. + */ +struct sc_audiobuf { + struct sc_bytebuf buf; + size_t sample_size; +}; + +static inline uint32_t +sc_audiobuf_to_samples(struct sc_audiobuf *buf, size_t bytes) { + assert(bytes % buf->sample_size == 0); + return bytes / buf->sample_size; +} + +static inline size_t +sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) { + return samples * buf->sample_size; +} + +static inline bool +sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size, + uint32_t capacity) { + buf->sample_size = sample_size; + return sc_bytebuf_init(&buf->buf, capacity * sample_size + 1); +} + +static inline void +sc_audiobuf_read(struct sc_audiobuf *buf, uint8_t *to, uint32_t samples) { + size_t bytes = sc_audiobuf_to_bytes(buf, samples); + sc_bytebuf_read(&buf->buf, to, bytes); +} + +static inline void +sc_audiobuf_skip(struct sc_audiobuf *buf, uint32_t samples) { + size_t bytes = sc_audiobuf_to_bytes(buf, samples); + sc_bytebuf_skip(&buf->buf, bytes); +} + +static inline void +sc_audiobuf_write(struct sc_audiobuf *buf, const uint8_t *from, + uint32_t samples) { + size_t bytes = sc_audiobuf_to_bytes(buf, samples); + sc_bytebuf_write(&buf->buf, from, bytes); +} + +static inline void +sc_audiobuf_prepare_write(struct sc_audiobuf *buf, const uint8_t *from, + uint32_t samples) { + size_t bytes = sc_audiobuf_to_bytes(buf, samples); + sc_bytebuf_prepare_write(&buf->buf, from, bytes); +} + +static inline void +sc_audiobuf_commit_write(struct sc_audiobuf *buf, uint32_t samples) { + size_t bytes = sc_audiobuf_to_bytes(buf, samples); + sc_bytebuf_commit_write(&buf->buf, bytes); +} + +static inline uint32_t +sc_audiobuf_read_available(struct sc_audiobuf *buf) { + size_t bytes = sc_bytebuf_read_available(&buf->buf); + return sc_audiobuf_to_samples(buf, bytes); +} + +static inline uint32_t +sc_audiobuf_write_available(struct sc_audiobuf *buf) { + size_t bytes = sc_bytebuf_write_available(&buf->buf); + return sc_audiobuf_to_samples(buf, bytes); +} + +static inline uint32_t +sc_audiobuf_capacity(struct sc_audiobuf *buf) { + size_t bytes = sc_bytebuf_capacity(&buf->buf); + return sc_audiobuf_to_samples(buf, bytes); +} + +static inline void +sc_audiobuf_destroy(struct sc_audiobuf *buf) { + sc_bytebuf_destroy(&buf->buf); +} + +#endif From e06acc1ba23b7a2dd6a822d04e51a031e629aa35 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 10:16:43 +0100 Subject: [PATCH 0801/1133] Simplify bytebuf naming Rename read_available to can_read and write_available to can_write. This is more readable. --- app/src/audio_player.c | 24 ++++++++++----------- app/src/audio_player.h | 2 +- app/src/util/audiobuf.h | 8 +++---- app/src/util/bytebuf.c | 8 +++---- app/src/util/bytebuf.h | 6 +++--- app/tests/test_bytebuf.c | 46 ++++++++++++++++++++-------------------- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 320af082..652711c6 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -33,7 +33,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count); #endif - uint32_t buffered_samples = sc_audiobuf_read_available(&ap->buf); + uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); if (!ap->played) { // Part of the buffering is handled by inserting initial silence. The // remaining (margin) last samples will be handled by compensation. @@ -126,20 +126,20 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // Since this function is the only writer, the current available space is // at least the previous available space. In practice, it should almost // always be possible to write without lock. - bool lockless_write = samples_written <= ap->previous_write_avail; + bool lockless_write = samples_written <= ap->previous_can_write; if (lockless_write) { sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written); } SDL_LockAudioDevice(ap->device); - uint32_t buffered_samples = sc_audiobuf_read_available(&ap->buf); + uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); if (lockless_write) { sc_audiobuf_commit_write(&ap->buf, samples_written); } else { - uint32_t write_avail = sc_audiobuf_write_available(&ap->buf); - if (samples_written > write_avail) { + uint32_t can_write = sc_audiobuf_can_write(&ap->buf); + if (samples_written > can_write) { // Entering this branch is very unlikely, the audio buffer is // allocated with a size sufficient to store 1 second more than the // target buffering. If this happens, though, we have to skip old @@ -155,9 +155,9 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, samples_written = cap; } - assert(samples_written >= write_avail); - if (samples_written > write_avail) { - uint32_t skip_samples = samples_written - write_avail; + assert(samples_written >= can_write); + if (samples_written > can_write) { + uint32_t skip_samples = samples_written - can_write; assert(buffered_samples >= skip_samples); sc_audiobuf_skip(&ap->buf, skip_samples); buffered_samples -= skip_samples; @@ -169,14 +169,14 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // It should remain exactly the expected size to write the new // samples. - assert(sc_audiobuf_write_available(&ap->buf) == samples_written); + assert(sc_audiobuf_can_write(&ap->buf) == samples_written); } sc_audiobuf_write(&ap->buf, swr_buf, samples_written); } buffered_samples += samples_written; - assert(buffered_samples == sc_audiobuf_read_available(&ap->buf)); + assert(buffered_samples == sc_audiobuf_can_read(&ap->buf)); // Read with lock held, to be used after unlocking bool played = ap->played; @@ -221,7 +221,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, } } - ap->previous_write_avail = sc_audiobuf_write_available(&ap->buf); + ap->previous_can_write = sc_audiobuf_can_write(&ap->buf); ap->received = true; SDL_UnlockAudioDevice(ap->device); @@ -349,7 +349,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, } ap->swr_buf_alloc_size = initial_swr_buf_size; - ap->previous_write_avail = sc_audiobuf_write_available(&ap->buf); + ap->previous_can_write = sc_audiobuf_can_write(&ap->buf); // Samples are produced and consumed by blocks, so the buffering must be // smoothed to get a relatively stable value. diff --git a/app/src/audio_player.h b/app/src/audio_player.h index e82735c2..f4670939 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -33,7 +33,7 @@ struct sc_audio_player { // The previous empty space in the buffer (only used by the receiver // thread) - uint32_t previous_write_avail; + uint32_t previous_can_write; // Resampler (only used from the receiver thread) struct SwrContext *swr_ctx; diff --git a/app/src/util/audiobuf.h b/app/src/util/audiobuf.h index 16bf20d3..8616d539 100644 --- a/app/src/util/audiobuf.h +++ b/app/src/util/audiobuf.h @@ -69,14 +69,14 @@ sc_audiobuf_commit_write(struct sc_audiobuf *buf, uint32_t samples) { } static inline uint32_t -sc_audiobuf_read_available(struct sc_audiobuf *buf) { - size_t bytes = sc_bytebuf_read_available(&buf->buf); +sc_audiobuf_can_read(struct sc_audiobuf *buf) { + size_t bytes = sc_bytebuf_can_read(&buf->buf); return sc_audiobuf_to_samples(buf, bytes); } static inline uint32_t -sc_audiobuf_write_available(struct sc_audiobuf *buf) { - size_t bytes = sc_bytebuf_write_available(&buf->buf); +sc_audiobuf_can_write(struct sc_audiobuf *buf) { + size_t bytes = sc_bytebuf_can_write(&buf->buf); return sc_audiobuf_to_samples(buf, bytes); } diff --git a/app/src/util/bytebuf.c b/app/src/util/bytebuf.c index eac69e9c..93544d72 100644 --- a/app/src/util/bytebuf.c +++ b/app/src/util/bytebuf.c @@ -30,7 +30,7 @@ sc_bytebuf_destroy(struct sc_bytebuf *buf) { void sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) { assert(len); - assert(len <= sc_bytebuf_read_available(buf)); + assert(len <= sc_bytebuf_can_read(buf)); assert(buf->tail != buf->head); // the buffer could not be empty size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size; @@ -50,7 +50,7 @@ sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) { void sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) { assert(len); - assert(len <= sc_bytebuf_read_available(buf)); + assert(len <= sc_bytebuf_can_read(buf)); assert(buf->tail != buf->head); // the buffer could not be empty buf->tail = (buf->tail + len) % buf->alloc_size; @@ -78,7 +78,7 @@ sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) { void sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { assert(len); - assert(len <= sc_bytebuf_write_available(buf)); + assert(len <= sc_bytebuf_can_write(buf)); sc_bytebuf_write_step0(buf, from, len); sc_bytebuf_write_step1(buf, len); @@ -99,6 +99,6 @@ sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from, void sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) { - assert(len <= sc_bytebuf_write_available(buf)); + assert(len <= sc_bytebuf_can_write(buf)); sc_bytebuf_write_step1(buf, len); } diff --git a/app/src/util/bytebuf.h b/app/src/util/bytebuf.h index e8279ef8..1448f752 100644 --- a/app/src/util/bytebuf.h +++ b/app/src/util/bytebuf.h @@ -86,7 +86,7 @@ sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len); * It is an error to read more bytes than available. */ static inline size_t -sc_bytebuf_read_available(struct sc_bytebuf *buf) { +sc_bytebuf_can_read(struct sc_bytebuf *buf) { return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size; } @@ -96,12 +96,12 @@ sc_bytebuf_read_available(struct sc_bytebuf *buf) { * It is an error to write more bytes than available. */ static inline size_t -sc_bytebuf_write_available(struct sc_bytebuf *buf) { +sc_bytebuf_can_write(struct sc_bytebuf *buf) { return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size; } /** - * Return the actual capacity of the buffer (read available + write available) + * Return the actual capacity of the buffer (can_read() + can_write()) */ static inline size_t sc_bytebuf_capacity(struct sc_bytebuf *buf) { diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c index 75af3073..c85e79ec 100644 --- a/app/tests/test_bytebuf.c +++ b/app/tests/test_bytebuf.c @@ -13,23 +13,23 @@ void test_bytebuf_simple(void) { assert(ok); sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1); - assert(sc_bytebuf_read_available(&buf) == 5); + assert(sc_bytebuf_can_read(&buf) == 5); sc_bytebuf_read(&buf, data, 4); assert(!strncmp((char *) data, "hell", 4)); sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1); - assert(sc_bytebuf_read_available(&buf) == 7); + assert(sc_bytebuf_can_read(&buf) == 7); sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_read_available(&buf) == 8); + assert(sc_bytebuf_can_read(&buf) == 8); sc_bytebuf_read(&buf, &data[4], 8); - assert(sc_bytebuf_read_available(&buf) == 0); + assert(sc_bytebuf_can_read(&buf) == 0); data[12] = '\0'; assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_read_available(&buf) == 0); + assert(sc_bytebuf_can_read(&buf) == 0); sc_bytebuf_destroy(&buf); } @@ -42,31 +42,31 @@ void test_bytebuf_boundaries(void) { assert(ok); sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 6); + assert(sc_bytebuf_can_read(&buf) == 6); sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 12); + assert(sc_bytebuf_can_read(&buf) == 12); sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 18); + assert(sc_bytebuf_can_read(&buf) == 18); sc_bytebuf_read(&buf, data, 9); assert(!strncmp((char *) data, "hello hel", 9)); - assert(sc_bytebuf_read_available(&buf) == 9); + assert(sc_bytebuf_can_read(&buf) == 9); sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1); - assert(sc_bytebuf_read_available(&buf) == 14); + assert(sc_bytebuf_can_read(&buf) == 14); sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_read_available(&buf) == 15); + assert(sc_bytebuf_can_read(&buf) == 15); sc_bytebuf_skip(&buf, 3); - assert(sc_bytebuf_read_available(&buf) == 12); + assert(sc_bytebuf_can_read(&buf) == 12); sc_bytebuf_read(&buf, data, 12); data[12] = '\0'; assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_read_available(&buf) == 0); + assert(sc_bytebuf_can_read(&buf) == 0); sc_bytebuf_destroy(&buf); } @@ -79,37 +79,37 @@ void test_bytebuf_two_steps_write(void) { assert(ok); sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 6); + assert(sc_bytebuf_can_read(&buf) == 6); sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 12); + assert(sc_bytebuf_can_read(&buf) == 12); sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 12); // write not committed yet + assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet sc_bytebuf_read(&buf, data, 9); assert(!strncmp((char *) data, "hello hel", 3)); - assert(sc_bytebuf_read_available(&buf) == 3); + assert(sc_bytebuf_can_read(&buf) == 3); sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1); - assert(sc_bytebuf_read_available(&buf) == 9); + assert(sc_bytebuf_can_read(&buf) == 9); sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1); - assert(sc_bytebuf_read_available(&buf) == 9); // write not committed yet + assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet sc_bytebuf_commit_write(&buf, sizeof("world") - 1); - assert(sc_bytebuf_read_available(&buf) == 14); + assert(sc_bytebuf_can_read(&buf) == 14); sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_read_available(&buf) == 15); + assert(sc_bytebuf_can_read(&buf) == 15); sc_bytebuf_skip(&buf, 3); - assert(sc_bytebuf_read_available(&buf) == 12); + assert(sc_bytebuf_can_read(&buf) == 12); sc_bytebuf_read(&buf, data, 12); data[12] = '\0'; assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_read_available(&buf) == 0); + assert(sc_bytebuf_can_read(&buf) == 0); sc_bytebuf_destroy(&buf); } From 0b8a5ca923bdd17bd551e9f5565979373ec319c4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:15:38 +0100 Subject: [PATCH 0802/1133] Do not read avg_buffering from the player thread On buffer underflow, the average buffering must be updated, but it is intended to be accessed only from the receiver thread. Make the player and the receiver thread communicate the underflow via a new field (ap->underflow). --- app/src/audio_player.c | 8 ++++++-- app/src/audio_player.h | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 652711c6..d9aba58a 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -67,7 +67,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { if (ap->received) { // Inserting additional samples immediately increases buffering - ap->avg_buffering.avg += silence; + ap->underflow += silence; } } @@ -194,9 +194,12 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // Number of samples added (or removed, if negative) for compensation int32_t instant_compensation = (int32_t) samples_written - frame->nb_samples; + int32_t inserted_silence = (int32_t) ap->underflow; // The compensation must apply instantly, it must not be smoothed - ap->avg_buffering.avg += instant_compensation; + ap->avg_buffering.avg += instant_compensation + inserted_silence; + + ap->underflow = 0; // reset // However, the buffering level must be smoothed sc_average_push(&ap->avg_buffering, buffered_samples); @@ -358,6 +361,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, ap->received = false; ap->played = false; + ap->underflow = 0; // The thread calling open() is the thread calling push(), which fills the // audio buffer consumed by the SDL audio thread. diff --git a/app/src/audio_player.h b/app/src/audio_player.h index f4670939..3227f2fe 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -56,6 +56,10 @@ struct sc_audio_player { // (only used by the receiver thread) uint32_t samples_since_resync; + // Number of silence samples inserted since the last received packet + // (protected by SDL_AudioDeviceLock()) + uint32_t underflow; + // Set to true the first time a sample is received (protected by // SDL_AudioDeviceLock()) bool received; From eca87665455f6d5a3092e90150e5a7929045cf1a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Mar 2023 22:19:28 +0100 Subject: [PATCH 0803/1133] Compute buffering and compensation without lock Once underflow has been read with a lock, the buffering and compensation may be performed without shared variables. --- app/src/audio_player.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index d9aba58a..1bff2b76 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -180,6 +180,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // Read with lock held, to be used after unlocking bool played = ap->played; + uint32_t underflow = ap->underflow; + if (played) { uint32_t max_buffered_samples = ap->target_buffering + 12 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000 @@ -191,23 +193,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, " samples", skip_samples); } - // Number of samples added (or removed, if negative) for compensation - int32_t instant_compensation = - (int32_t) samples_written - frame->nb_samples; - int32_t inserted_silence = (int32_t) ap->underflow; - - // The compensation must apply instantly, it must not be smoothed - ap->avg_buffering.avg += instant_compensation + inserted_silence; - - ap->underflow = 0; // reset - - // However, the buffering level must be smoothed - sc_average_push(&ap->avg_buffering, buffered_samples); - -#ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f", - buffered_samples, sc_average_get(&ap->avg_buffering)); -#endif + // reset (the current value was copied to a local variable) + ap->underflow = 0; } else { // SDL playback not started yet, do not accumulate more than // max_initial_buffering samples, this would cause unnecessary delay @@ -230,6 +217,23 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, SDL_UnlockAudioDevice(ap->device); if (played) { + // Number of samples added (or removed, if negative) for compensation + int32_t instant_compensation = + (int32_t) samples_written - frame->nb_samples; + int32_t inserted_silence = (int32_t) underflow; + + // The compensation must apply instantly, it must not be smoothed + ap->avg_buffering.avg += instant_compensation + inserted_silence; + + + // However, the buffering level must be smoothed + sc_average_push(&ap->avg_buffering, buffered_samples); + +#ifndef SC_AUDIO_PLAYER_NDEBUG + LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f", + buffered_samples, sc_average_get(&ap->avg_buffering)); +#endif + ap->samples_since_resync += samples_written; if (ap->samples_since_resync >= ap->sample_rate) { // Recompute compensation every second From 2380879376bd0aeeffb0262f98b9f168bfe198fb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:01:45 +0100 Subject: [PATCH 0804/1133] Remove unused IOException IOException may not be thrown from this method. --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 3cef7801..9228e3d7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -14,7 +14,6 @@ import android.media.MediaRecorder; import android.os.Build; import android.os.SystemClock; -import java.io.IOException; import java.nio.ByteBuffer; public final class AudioCapture { @@ -110,7 +109,7 @@ public final class AudioCapture { } @TargetApi(Build.VERSION_CODES.N) - public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) throws IOException { + public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) { int r = recorder.read(directBuffer, size); if (r < 0) { return r; From f5bb9e576dfae8444215e36e4fbd28bc2947f4c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:11:57 +0100 Subject: [PATCH 0805/1133] Upgrade SDL (2.26.4) for Windows Include the latest version of SDL in Windows releases. --- app/prebuilt-deps/prepare-sdl.sh | 6 +++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index 644ed72d..60bffae9 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.26.1 +DEP_DIR=SDL2-2.26.4 -FILENAME=SDL2-devel-2.26.1-mingw.tar.gz -SHA256SUM=aa43e1531a89551f9f9e14b27953a81d4ac946a9e574b5813cd0f2b36e83cc1c +FILENAME=SDL2-devel-2.26.4-mingw.tar.gz +SHA256SUM=fe899c8642caac2f180b1ee6f786857ddcaa0adc1fa82474312b09dd47d74712 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index a02e798a..18834af4 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win32' -prebuilt_sdl2 = 'SDL2-2.26.1/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 126de36e..1c7c0875 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win64' -prebuilt_sdl2 = 'SDL2-2.26.1/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 75e5a9c0..3c60d80e 100644 --- a/release.mk +++ b/release.mk @@ -102,7 +102,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.26.1/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.26.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 @@ -121,7 +121,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.26.1/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.26.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 From cc07f8dac4247c7d6b175b1f58e71d50e87f85db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:15:24 +0100 Subject: [PATCH 0806/1133] Upgrade platform-tools (34.0.1) for Windows Include the latest version of adb in Windows releases. --- app/prebuilt-deps/prepare-adb.sh | 6 +++--- release.mk | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh index 0e4e498c..cc139095 100755 --- a/app/prebuilt-deps/prepare-adb.sh +++ b/app/prebuilt-deps/prepare-adb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=platform-tools-33.0.3 +DEP_DIR=platform-tools-34.0.1 -FILENAME=platform-tools_r33.0.3-windows.zip -SHA256SUM=1e59afd40a74c5c0eab0a9fad3f0faf8a674267106e0b19921be9f67081808c2 +FILENAME=platform-tools_r34.0.1-windows.zip +SHA256SUM=5dd9c2be744c224fa3a7cbe30ba02d2cb378c763bd0f797a7e47e9f3156a5daa if [[ -d "$DEP_DIR" ]] then diff --git a/release.mk b/release.mk index 3c60d80e..0f5cbe24 100644 --- a/release.mk +++ b/release.mk @@ -99,9 +99,9 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.26.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -118,9 +118,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-33.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.26.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 5512777404617ed34a2e0343985f949ae4b554c2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:29:56 +0100 Subject: [PATCH 0807/1133] Remove deprecated option --render-expired-frames This option did nothing since it was deprecated. Totally remove it. --- app/src/cli.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 8a0b6aa4..fc5f0a2f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -18,8 +18,7 @@ #define STR(x) STR_IMPL_(x) enum { - OPT_RENDER_EXPIRED_FRAMES = 1000, - OPT_BIT_RATE, + OPT_BIT_RATE = 1000, OPT_WINDOW_TITLE, OPT_PUSH_TARGET, OPT_ALWAYS_ON_TOP, @@ -471,11 +470,6 @@ static const struct sc_option options[] = { "\"opengles2\", \"opengles\", \"metal\" and \"software\".\n" "", }, - { - // deprecated - .longopt_id = OPT_RENDER_EXPIRED_FRAMES, - .longopt = "render-expired-frames", - }, { .longopt_id = OPT_REQUIRE_AUDIO, .longopt = "require-audio", @@ -1660,10 +1654,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'w': opts->stay_awake = true; break; - case OPT_RENDER_EXPIRED_FRAMES: - LOGW("Option --render-expired-frames has been removed. This " - "flag has been ignored."); - break; case OPT_WINDOW_TITLE: opts->window_title = optarg; break; From 426dfbf21d9518fc8b325fc1ccf106f3b03e33c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:32:53 +0100 Subject: [PATCH 0808/1133] Remove dead code about the deprecated -F option The -F option was already removed. --- app/src/cli.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index fc5f0a2f..016b07d7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1564,9 +1564,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'f': opts->fullscreen = true; break; - case 'F': - LOGW("Deprecated option -F. Use --record-format instead."); - // fall through case OPT_RECORD_FORMAT: if (!parse_record_format(optarg, &opts->record_format)) { return false; From c22c87ededb2b97249a956b0d0a29b16a14bb5d8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:40:39 +0100 Subject: [PATCH 0809/1133] Fail on deprecated options Suggest the video and audio specific options instead. --- app/src/cli.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 016b07d7..cb101a51 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1535,8 +1535,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) { switch (c) { case OPT_BIT_RATE: - LOGW("--bit-rate is deprecated, use --video-bit-rate instead."); - // fall through + LOGE("--bit-rate has been removed, " + "use --video-bit-rate or --audio-bit-rate."); + return false; case 'b': if (!parse_bit_rate(optarg, &opts->video_bit_rate)) { return false; @@ -1709,9 +1710,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->forward_key_repeat = false; break; case OPT_CODEC_OPTIONS: - LOGW("--codec-options is deprecated, use --video-codec-options " - "instead."); - // fall through + LOGE("--codec-options has been removed, " + "use --video-codec-options or --audio-codec-options."); + return false; case OPT_VIDEO_CODEC_OPTIONS: opts->video_codec_options = optarg; break; @@ -1719,8 +1720,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->audio_codec_options = optarg; break; case OPT_ENCODER: - LOGW("--encoder is deprecated, use --video-encoder instead."); - // fall through + LOGE("--encoder has been removed, " + "use --video-encoder or --audio-encoder."); + return false; case OPT_VIDEO_ENCODER: opts->video_encoder = optarg; break; @@ -1775,8 +1777,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->start_fps_counter = true; break; case OPT_CODEC: - LOGW("--codec is deprecated, use --video-codec instead."); - // fall through + LOGE("--codec has been removed, " + "use --video-codec or --audio-codec."); + return false; case OPT_VIDEO_CODEC: if (!parse_video_codec(optarg, &opts->video_codec)) { return false; From 73727e7fdfdd579f14f957dce680a3fc9e454437 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 18:21:46 +0100 Subject: [PATCH 0810/1133] Disable clock drift compensation for tiny values For less than 1ms, the estimated drift is just noise. --- app/src/audio_player.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 1bff2b76..aa34c316 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -241,7 +241,10 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, float avg = sc_average_get(&ap->avg_buffering); int diff = ap->target_buffering - avg; - if (diff < 0 && buffered_samples < ap->target_buffering) { + if (abs(diff) < ap->sample_rate / 1000) { + // Do not compensate for less than 1ms, the error is just noise + diff = 0; + } else if (diff < 0 && buffered_samples < ap->target_buffering) { // Do not accelerate if the instant buffering level is below // the average, this would increase underflow diff = 0; From 0bf866fa8d98e8d35c08de5c69e33b80ccda8c44 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 23:00:48 +0100 Subject: [PATCH 0811/1133] Apply new compensation only if it changed If the compensation is the same (typically when it is 0), do not reapply it. --- app/src/audio_player.c | 14 ++++++++++---- app/src/audio_player.h | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index aa34c316..0511ec1f 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -258,10 +258,15 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32 " compensation=%d", ap->target_buffering, avg, buffered_samples, diff); - int ret = swr_set_compensation(swr_ctx, diff, distance); - if (ret < 0) { - LOGW("Resampling compensation failed: %d", ret); - // not fatal + + if (diff != ap->compensation) { + int ret = swr_set_compensation(swr_ctx, diff, distance); + if (ret < 0) { + LOGW("Resampling compensation failed: %d", ret); + // not fatal + } else { + ap->compensation = diff; + } } } } @@ -369,6 +374,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, ap->received = false; ap->played = false; ap->underflow = 0; + ap->compensation = 0; // The thread calling open() is the thread calling push(), which fills the // audio buffer consumed by the SDL audio thread. diff --git a/app/src/audio_player.h b/app/src/audio_player.h index 3227f2fe..4dd9c4dc 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -60,6 +60,9 @@ struct sc_audio_player { // (protected by SDL_AudioDeviceLock()) uint32_t underflow; + // Current applied compensation value (only used by the receiver thread) + int compensation; + // Set to true the first time a sample is received (protected by // SDL_AudioDeviceLock()) bool received; From affda26bfa5678f358f834248179cbe98e38dcc2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Mar 2023 16:43:56 +0100 Subject: [PATCH 0812/1133] Document audio player Add some high-level documentation on the audio player implementation. --- app/src/audio_player.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 0511ec1f..5abc9088 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -7,6 +7,52 @@ #define SC_AUDIO_PLAYER_NDEBUG // comment to debug +/** + * Real-time audio player with configurable latency + * + * As input, the player regularly receives AVFrames of decoded audio samples. + * As output, an SDL callback regularly requests audio samples to be played. + * In the middle, an audio buffer stores the samples produced but not consumed + * yet. + * + * The goal of the player is to feed the audio output with a latency as low as + * possible while avoiding buffer underrun (i.e. not being able to provide + * samples when requested). + * + * The player aims to feed the audio output with as little latency as possible + * while avoiding buffer underrun. To achieve this, it attempts to maintain the + * average buffering (the number of samples present in the buffer) around a + * target value. If this target buffering is too low, then buffer underrun will + * occur frequently. If it is too high, then latency will become unacceptable. + * This target value is configured using the scrcpy option --audio-buffer. + * + * The player cannot adjust the sample input rate (it receives samples produced + * in real-time) or the sample output rate (it must provide samples as + * requested by the audio output callback). Therefore, it may only apply + * compensation by resampling (converting _m_ input samples to _n_ output + * samples). + * + * The compensation itself is applied by libswresample (FFmpeg). It is + * configured using swr_set_compensation(). An important work for the player + * is to estimate the compensation value regularly and apply it. + * + * The estimated buffering level is the result of averaging the "natural" + * buffering (samples are produced and consumed by blocks, so it must be + * smoothed), and making instant adjustments resulting of its own actions + * (explicit compensation and silence insertion on underflow), which are not + * smoothed. + * + * Buffer underflow events can occur when packets arrive too late. In that case, + * the player inserts silence. Once the packets finally arrive (late), one + * strategy could be to drop the samples that were replaced by silence, in + * order to keep a minimal latency. However, dropping samples in case of buffer + * underflow is inadvisable, as it would temporarily increase the underflow + * even more and cause very noticeable audio glitches. + * + * Therefore, the player doesn't drop any sample on underflow. The compensation + * mechanism will absorb the delay introduced by the inserted silence. + */ + /** Downcast frame_sink to sc_audio_player */ #define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink) From 05a55e36878b78754d0c031d5c81dc7de7ac1893 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Mar 2023 09:08:42 +0100 Subject: [PATCH 0813/1133] Happy new year 2023! --- LICENSE | 2 +- README.md | 2 +- app/scrcpy.1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index bea74a6b..55f96811 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ identification within third-party archives. Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont + Copyright (C) 2018-2023 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index a2e275f9..765a6466 100644 --- a/README.md +++ b/README.md @@ -1185,7 +1185,7 @@ Read the [developers page]. ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2022 Romain Vimont + Copyright (C) 2018-2023 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e8e36188..65357686 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -571,7 +571,7 @@ Copyright \(co 2018 Genymobile Genymobile .UE -Copyright \(co 2018\-2022 +Copyright \(co 2018\-2023 .MT rom@rom1v.com Romain Vimont .ME From f12590ed08b892b289fb382dd1e4f89fe2640a2d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 4 Mar 2023 08:56:35 +0100 Subject: [PATCH 0814/1133] Rework README and documentation The README.md page is HUGE. Split it up. Also document audio forwarding and improve installation instructions for each platform and user documentation. PR #3774 --- FAQ.md | 123 +--- README.md | 1205 ++-------------------------------- doc/audio.md | 90 +++ BUILD.md => doc/build.md | 60 +- doc/control.md | 149 +++++ DEVELOP.md => doc/develop.md | 0 doc/device.md | 228 +++++++ doc/hid-otg.md | 108 +++ doc/linux.md | 79 +++ doc/macos.md | 47 ++ doc/recording.md | 44 ++ doc/shortcuts.md | 68 ++ doc/tunnels.md | 123 ++++ doc/v4l2.md | 65 ++ doc/video.md | 175 +++++ doc/window.md | 55 ++ doc/windows.md | 84 +++ 17 files changed, 1387 insertions(+), 1316 deletions(-) create mode 100644 doc/audio.md rename BUILD.md => doc/build.md (85%) create mode 100644 doc/control.md rename DEVELOP.md => doc/develop.md (100%) create mode 100644 doc/device.md create mode 100644 doc/hid-otg.md create mode 100644 doc/linux.md create mode 100644 doc/macos.md create mode 100644 doc/recording.md create mode 100644 doc/shortcuts.md create mode 100644 doc/tunnels.md create mode 100644 doc/v4l2.md create mode 100644 doc/video.md create mode 100644 doc/window.md create mode 100644 doc/windows.md diff --git a/FAQ.md b/FAQ.md index e6c3c94d..9b22c447 100644 --- a/FAQ.md +++ b/FAQ.md @@ -164,32 +164,6 @@ keyboard][hid] (HID). ## Client issues -### The quality is low - -If the definition of your client window is smaller than that of your device -screen, then you might get poor quality, especially visible on text (see [#40]). - -[#40]: https://github.com/Genymobile/scrcpy/issues/40 - -This problem should be fixed in scrcpy v1.22: **update to the latest version**. - -On older versions, you must configure the [scaling behavior]: - -> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > -> Override high DPI scaling behavior > Scaling performed by: _Application_. - -[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 - -Also, to improve downscaling quality, trilinear filtering is enabled -automatically if the renderer is OpenGL and if it supports mipmapping. - -On Windows, you might want to force OpenGL to enable mipmapping: - -``` -scrcpy --render-driver=opengl -``` - - ### Issue with Wayland By default, SDL uses x11 on Linux. The [video driver] can be changed via the @@ -224,102 +198,15 @@ As a workaround, [disable "Block compositing"][kwin]. ### Exception -There may be many reasons. One common cause is that the hardware encoder of your -device is not able to encode at the given definition: - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> android.media.MediaCodec$CodecException: Error 0xfffffc0e -> ... -> Exit due to uncaughtException in main thread: -> ERROR: Could not open video stream -> INFO: Initial texture: 1080x2336 -> ``` - -or - -> ``` -> ERROR: Exception on thread Thread[main,5,main] -> java.lang.IllegalStateException -> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) -> ``` - -Just try with a lower definition: +If you get any exception related to `MediaCodec`: ``` -scrcpy -m 1920 -scrcpy -m 1024 -scrcpy -m 800 +ERROR: Exception on thread Thread[main,5,main] +java.lang.IllegalStateException + at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) ``` -Since scrcpy v1.22, scrcpy automatically tries again with a lower definition -before failing. This behavior can be disabled with `--no-downsize-on-error`. - -You could also try another [encoder](README.md#encoder). - - -If you encounter this exception on Android 12, then just upgrade to scrcpy >= -1.18 (see [#2129]): - -``` -> ERROR: Exception on thread Thread[main,5,main] -java.lang.AssertionError: java.lang.reflect.InvocationTargetException - at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75) - ... -Caused by: java.lang.reflect.InvocationTargetException - at java.lang.reflect.Method.invoke(Native Method) - at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73) - ... 7 more -Caused by: java.lang.IllegalArgumentException: displayToken must not be null - at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067) - at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147) - ... 9 more -``` - -[#2129]: https://github.com/Genymobile/scrcpy/issues/2129 - - -## Command line on Windows - -Since v1.22, a "shortcut" has been added to directly open a terminal in the -scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your -command. For example: - -``` -scrcpy --record file.mkv -``` - -You could also open a terminal and go to the scrcpy folder manually: - - 1. Press Windows+r, this opens a dialog box. - 2. Type `cmd` and press Enter, this opens a terminal. - 3. Go to your _scrcpy_ directory, by typing (adapt the path): - - ```bat - cd C:\Users\user\Downloads\scrcpy-win64-xxx - ``` - - and press Enter - 4. Type your command. For example: - - ```bat - scrcpy --record file.mkv - ``` - -If you plan to always use the same arguments, create a file `myscrcpy.bat` -(enable [show file extensions] to avoid confusion) in the `scrcpy` directory, -containing your command. For example: - -```bat -scrcpy --prefer-text --turn-screen-off --stay-awake -``` - -Then just double-click on that file. - -You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs` -to add some arguments. - -[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ +then try with another [encoder](doc/video.md#codec). ## Translations diff --git a/README.md b/README.md index 765a6466..bc6f3bd3 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,10 @@ _pronounced "**scr**een **c**o**py**"_ -[Read in another language](#translations) - -This application provides display and control of Android devices connected via -USB or [over TCP/IP](#tcpip-wireless). It does not require any _root_ access. -It works on _GNU/Linux_, _Windows_ and _macOS_. +This application mirrors Android devices (video and audio) connected via +USB or [over TCP/IP](doc/device.md#tcpip-wireless), and allows to control the +device with the keyboard and the mouse of the computer. It does not require any +_root_ access. It works on _Linux_, _Windows_ and _macOS_. ![screenshot](assets/screenshot-debian-600.jpg) @@ -26,1161 +25,96 @@ It focuses on: [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 Its features include: - - [recording](#recording) - - mirroring with [Android device screen off](#turn-screen-off) - - [copy-paste](#copy-paste) in both directions - - [configurable quality](#capture-configuration) - - Android device [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) - - [physical mouse simulation (HID)](#physical-mouse-simulation-hid) - - [OTG mode](#otg) + - [audio forwarding](doc/audio.md) (Android >= 11) + - [recording](doc/recording.md) + - mirroring with [Android device screen off](doc/device.md#turn-screen-off) + - [copy-paste](doc/control.md#copy-paste) in both directions + - [configurable quality](doc/video.md) + - Android device [as a webcam (V4L2)](doc/v4l2.md) (Linux-only) + - [physical keyboard/mouse simulation (HID)](doc/hid-otg.md) + - [OTG mode](doc/hid-otg.md#otg) - and more… ## Requirements The Android device requires at least API 21 (Android 5.0). -Make sure you [enable adb debugging][enable-adb] on your device(s). +[Audio forwarding](doc/audio.md) is supported from API 30 (Android 11). + +Make sure you [enabled adb debugging][enable-adb] on your device(s). [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling -On some devices, you also need to enable [an additional option][control] to -control it using a keyboard and mouse. +On some devices, you also need to enable [an additional option][control] `USB +debugging (Security Settings)` (this is an item different from `USB debugging`) +to control it using a keyboard and mouse. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 ## Get the app -Packaging status - -### Summary - - - Linux: `apt install scrcpy` - - Windows: [download][direct-win64] - - macOS: `brew install scrcpy` - -Build from sources: [BUILD] ([simplified process][BUILD_simple]) - -[BUILD]: BUILD.md -[BUILD_simple]: BUILD.md#simple - - -### Linux - -On Debian and Ubuntu: - -``` -apt install scrcpy -``` - -On Arch Linux: - -``` -pacman -S scrcpy -``` - -A [Snap] package is available: [`scrcpy`][snap-link]. - -[snap-link]: https://snapstats.org/snaps/scrcpy - -[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) - -For Fedora, a [COPR] package is available: [`scrcpy`][copr-link]. - -[COPR]: https://fedoraproject.org/wiki/Category:Copr -[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ - - -For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link]. - -[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - -You can also [build the app manually][BUILD] ([simplified -process][BUILD_simple]). - - -### Windows - -For Windows, a prebuilt archive with all the dependencies (including `adb`) is -available: - - - [`scrcpy-win64-v1.25.zip`][direct-win64] - SHA-256: `db65125e9c65acd00359efb7cea9c05f63cc7ccd5833000cd243cc92f5053028` - -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-win64-v1.25.zip - -It is also available in [Chocolatey]: - -[Chocolatey]: https://chocolatey.org/ - -```bash -choco install scrcpy -choco install adb # if you don't have it yet -``` - -And in [Scoop]: - -```bash -scoop install scrcpy -scoop install adb # if you don't have it yet -``` - -[Scoop]: https://scoop.sh - -You can also [build the app manually][BUILD]. - - -### macOS - -The application is available in [Homebrew]. Just install it: - -[Homebrew]: https://brew.sh/ - -```bash -brew install scrcpy -``` - -You need `adb`, accessible from your `PATH`. If you don't have it yet: - -```bash -brew install android-platform-tools -``` - -It's also available in [MacPorts], which sets up `adb` for you: - -```bash -sudo port install scrcpy -``` - -[MacPorts]: https://www.macports.org/ - - -You can also [build the app manually][BUILD]. - - -## Run - -Plug an Android device into your computer, and execute: - -```bash -scrcpy -``` - -It accepts command-line arguments, listed by: - -```bash -scrcpy --help -``` - -## Features - -### Capture configuration - -#### Reduce size - -Sometimes, it is useful to mirror an Android device at a lower resolution to -increase performance. - -To limit both the width and height to some value (e.g. 1024): - -```bash -scrcpy --max-size=1024 -scrcpy -m 1024 # short version -``` - -The other dimension is computed so that the Android device aspect ratio is -preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. - - -#### Change bit-rate - -The default bit-rate is 8 Mbps. To change the video bitrate (e.g. to 2 Mbps): - -```bash -scrcpy --video-bit-rate=2M -scrcpy -b 2M # short version -``` - -#### Limit frame rate - -The capture frame rate can be limited: - -```bash -scrcpy --max-fps=15 -``` - -This is officially supported since Android 10, but may work on earlier versions. - -The actual capture framerate may be printed to the console: - -``` -scrcpy --print-fps -``` - -It may also be enabled or disabled at any time with MOD+i. - - -#### Crop - -The device screen may be cropped to mirror only part of the screen. - -This is useful, for example, to mirror only one eye of the Oculus Go: - -```bash -scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0) -``` - -If `--max-size` is also specified, resizing is applied after cropping. - - -#### Lock video orientation - -To lock the orientation of the mirroring: - -```bash -scrcpy --lock-video-orientation # initial (current) orientation -scrcpy --lock-video-orientation=0 # natural orientation -scrcpy --lock-video-orientation=1 # 90° counterclockwise -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90° clockwise -``` - -This affects recording orientation. - -The [window may also be rotated](#rotation) independently. - - -#### Codec - -The video codec can be selected. The possible values are `h264` (default), -`h265` and `av1`: - -```bash -scrcpy --video-codec=h264 # default -scrcpy --video-codec=h265 -scrcpy --video-codec=av1 -``` - - -##### Encoder - -Some devices have more than one encoder for a specific codec, and some of them -may cause issues or crash. It is possible to select a different encoder: - -```bash -scrcpy --video-encoder=OMX.qcom.video.encoder.avc -``` - -To list the available encoders: - -```bash -scrcpy --list-encoders -``` - -### Capture - -#### Recording - -It is possible to record the screen while mirroring: - -```bash -scrcpy --record=file.mp4 -scrcpy -r file.mkv -``` - -To disable mirroring while recording: - -```bash -scrcpy --no-display --record=file.mp4 -scrcpy -Nr file.mkv -# interrupt recording with Ctrl+C -``` - -"Skipped frames" are recorded, even if they are not displayed in real time (for -performance reasons). Frames are _timestamped_ on the device, so [packet delay -variation] does not impact the recorded file. - -[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation - - -#### v4l2loopback - -On Linux, it is possible to send the video stream to a v4l2 loopback device, so -that the Android device can be opened like a webcam by any v4l2-capable tool. - -The module `v4l2loopback` must be installed: - -```bash -sudo apt install v4l2loopback-dkms -``` - -To create a v4l2 device: - -```bash -sudo modprobe v4l2loopback -``` - -This will create a new video device in `/dev/videoN`, where `N` is an integer -(more [options](https://github.com/umlaeute/v4l2loopback#options) are available -to create several devices or devices with specific IDs). - -To list the enabled devices: - -```bash -# requires v4l-utils package -v4l2-ctl --list-devices - -# simple but might be sufficient -ls /dev/video* -``` - -To start `scrcpy` using a v4l2 sink: - -```bash -scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window -scrcpy --v4l2-sink=/dev/videoN -N # short version -``` - -(replace `N` with the device ID, check with `ls /dev/video*`) - -Once enabled, you can open your video stream with a v4l2-capable tool: - -```bash -ffplay -i /dev/videoN -vlc v4l2:///dev/videoN # VLC might add some buffering delay -``` - -For example, you could capture the video within [OBS]. - -[OBS]: https://obsproject.com/ - - -#### Buffering - -It is possible to add buffering. This increases latency, but reduces jitter (see -[#2464]). - -[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 - -The option is available for display buffering: - -```bash -scrcpy --display-buffer=50 # add 50 ms buffering for display -``` - -and V4L2 sink: - -```bash -scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink -``` - - -### Connection - -#### TCP/IP (wireless) - -_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a -device over TCP/IP. The device must be connected on the same network as the -computer. - -##### Automatic - -An option `--tcpip` allows to configure the connection automatically. There are -two variants. - -If the device (accessible at 192.168.1.1 in this example) already listens on a -port (typically 5555) for incoming _adb_ connections, then run: - -```bash -scrcpy --tcpip=192.168.1.1 # default port is 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP -address), connect the device over USB, then run: - -```bash -scrcpy --tcpip # without arguments -``` - -It will automatically find the device IP address and adb port, enable TCP/IP -mode if necessary, then connect to the device before starting. - -##### Manual - -Alternatively, it is possible to enable the TCP/IP connection manually using -`adb`: - -1. Plug the device into a USB port on your computer. -2. Connect the device to the same Wi-Fi network as your computer. -3. Get your device IP address, in Settings → About phone → Status, or by - executing this command: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. -5. Unplug your device. -6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` -with the device IP address you found)_. -7. Run `scrcpy` as usual. - -Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass -having to physically connect your device directly to your computer. - -[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ - -If the connection randomly drops, run your `scrcpy` command to reconnect. If it -says there are no devices/emulators found, try running `adb connect -DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are -none found, try running `adb disconnect`, and then run those two commands again. - -It may be useful to decrease the bit-rate and the resolution: - -```bash -scrcpy --video-bit-rate=2M --max-size=800 -scrcpy -b2M -m800 # short version -``` - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - - -#### Multi-devices - -If several devices are listed in `adb devices`, you can specify the _serial_: - -```bash -scrcpy --serial=0123456789abcdef -scrcpy -s 0123456789abcdef # short version -``` - -The serial may also be provided via the environment variable `ANDROID_SERIAL` -(also used by `adb`). - -If the device is connected over TCP/IP: - -```bash -scrcpy --serial=192.168.0.1:5555 -scrcpy -s 192.168.0.1:5555 # short version -``` - -If only one device is connected via either USB or TCP/IP, it is possible to -select it automatically: - -```bash -# Select the only device connected via USB -scrcpy -d # like adb -d -scrcpy --select-usb # long version - -# Select the only device connected via TCP/IP -scrcpy -e # like adb -e -scrcpy --select-tcpip # long version -``` - -You can start several instances of _scrcpy_ for several devices. - -#### Autostart on device connection - -You could use [AutoAdb]: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - -#### Tunnels - -To connect to a remote device, it is possible to connect a local `adb` client to -a remote `adb` server (provided they use the same version of the _adb_ -protocol). - -##### Remote ADB server - -To connect to a remote _adb server_, make the server listen on all interfaces: - -```bash -adb kill-server -adb -a nodaemon server start -# keep this open -``` - -**Warning: all communications between clients and the _adb server_ are -unencrypted.** - -Suppose that this server is accessible at 192.168.1.2. Then, from another -terminal, run `scrcpy`: - -```bash -# in bash -export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -```cmd -:: in cmd -set ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 -scrcpy --tunnel-host=192.168.1.2 -``` - -```powershell -# in PowerShell -$env:ADB_SERVER_SOCKET = 'tcp:192.168.1.2:5037' -scrcpy --tunnel-host=192.168.1.2 -``` - -By default, `scrcpy` uses the local port used for `adb forward` tunnel -establishment (typically `27183`, see `--port`). It is also possible to force a -different tunnel port (it may be useful in more complex situations, when more -redirections are involved): - -``` -scrcpy --tunnel-port=1234 -``` - - -##### SSH tunnel - -To communicate with a remote _adb server_ securely, it is preferable to use an -SSH tunnel. - -First, make sure the _adb server_ is running on the remote computer: - -```bash -adb start-server -``` - -Then, establish an SSH tunnel: - -```bash -# local 5038 --> remote 5037 -# local 27183 <-- remote 27183 -ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer -# keep this open -``` - -From another terminal, run `scrcpy`: - -```bash -# in bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -```cmd -:: in cmd -set ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy -``` - -```powershell -# in PowerShell -$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' -scrcpy -``` - -To avoid enabling remote port forwarding, you could force a forward connection -instead (notice the `-L` instead of `-R`): - -```bash -# local 5038 --> remote 5037 -# local 27183 --> remote 27183 -ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer -# keep this open -``` - -From another terminal, run `scrcpy`: - -```bash -# in bash -export ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - -```cmd -:: in cmd -set ADB_SERVER_SOCKET=tcp:localhost:5038 -scrcpy --force-adb-forward -``` - -```powershell -# in PowerShell -$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' -scrcpy --force-adb-forward -``` - - -Like for wireless connections, it may be useful to reduce quality: - -``` -scrcpy -b2M -m800 --max-fps=15 -``` - -### Window configuration - -#### Title - -By default, the window title is the device model. It can be changed: - -```bash -scrcpy --window-title='My device' -``` + - [Linux](doc/linux.md) + - [Windows](doc/windows.md) + - [macOS](doc/macos.md) -#### Position and size -The initial window position and size may be specified: +## User documentation -```bash -scrcpy --window-x=100 --window-y=100 --window-width=800 --window-height=600 -``` +The application provides a lot of features and configuration options. They are +documented in the following pages: -#### Borderless + - [Device](doc/device.md) + - [Video](doc/video.md) + - [Audio](doc/audio.md) + - [Control](doc/control.md) + - [Window](doc/window.md) + - [Recording](doc/recording.md) + - [Tunnels](doc/tunnels.md) + - [HID/OTG](doc/hid-otg.md) + - [Video4Linux](doc/v4l2.md) + - [Shortcuts](doc/shortcuts.md) -To disable window decorations: -```bash -scrcpy --window-borderless -``` +## Resources -#### Always on top + - [FAQ](FAQ.md) + - [Translations][wiki] (not necessarily up to date) + - [Build instructions](doc/build.md) + - [Developers](doc/develop.md) -To keep the _scrcpy_ window always on top: - -```bash -scrcpy --always-on-top -``` - -#### Fullscreen - -The app may be started directly in fullscreen: - -```bash -scrcpy --fullscreen -scrcpy -f # short version -``` - -Fullscreen can then be toggled dynamically with MOD+f. - -#### Rotation - -The window may be rotated: - -```bash -scrcpy --rotation=1 -``` - -Possible values: - - `0`: no rotation - - `1`: 90 degrees counterclockwise - - `2`: 180 degrees - - `3`: 90 degrees clockwise - -The rotation can also be changed dynamically with MOD+ -_(left)_ and MOD+ _(right)_. - -Note that _scrcpy_ manages 3 different rotations: - - MOD+r requests the device to switch between portrait - and landscape (the current running app may refuse, if it does not support the - requested orientation). - - [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring - orientation (the orientation of the video sent from the device to the - computer). This affects the recording. - - `--rotation` (or MOD+/MOD+) - rotates only the window content. This affects only the display, not the - recording. - - -### Other mirroring options - -#### Read-only - -To disable controls (everything which can interact with the device: input keys, -mouse events, drag&drop files): - -```bash -scrcpy --no-control -scrcpy -n -``` - -#### Display - -If several displays are available, it is possible to select the display to -mirror: - -```bash -scrcpy --display=1 -``` - -The list of display ids can be retrieved by: - -```bash -scrcpy --list-displays -``` - -The secondary display may only be controlled if the device runs at least Android -10 (otherwise it is mirrored as read-only). - - -#### Stay awake - -To prevent the device from sleeping after a delay when the device is plugged in: - -```bash -scrcpy --stay-awake -scrcpy -w -``` - -The initial state is restored when _scrcpy_ is closed. - - -#### Turn screen off - -It is possible to turn the device screen off while mirroring on start with a -command-line option: - -```bash -scrcpy --turn-screen-off -scrcpy -S -``` - -Or by pressing MOD+o at any time. - -To turn it back on, press MOD+Shift+o. - -On Android, the `POWER` button always turns the screen on. For convenience, if -`POWER` is sent via _scrcpy_ (via right-click or MOD+p), -it will force to turn the screen off after a small delay (on a best effort -basis). The physical `POWER` button will still cause the screen to be turned -on. - -It can also be useful to prevent the device from sleeping: - -```bash -scrcpy --turn-screen-off --stay-awake -scrcpy -Sw -``` - -#### Power off on close - -To turn the device screen off when closing _scrcpy_: - -```bash -scrcpy --power-off-on-close -``` - -#### Power on on start - -By default, on start, the device is powered on. - -To prevent this behavior: - -```bash -scrcpy --no-power-on -``` - - -#### Show touches - -For presentations, it may be useful to show physical touches (on the physical -device). - -Android provides this feature in _Developers options_. - -_Scrcpy_ provides an option to enable this feature on start and restore the -initial value on exit: - -```bash -scrcpy --show-touches -scrcpy -t -``` - -Note that it only shows _physical_ touches (by a finger on the device). - - -#### Disable screensaver - -By default, _scrcpy_ does not prevent the screensaver from running on the -computer. - -To disable it: - -```bash -scrcpy --disable-screensaver -``` - - -### Input control - -#### Rotate device screen - -Press MOD+r to switch between portrait and landscape -modes. - -Note that it rotates only if the application in foreground supports the -requested orientation. - -#### Copy-paste - -Any time the Android clipboard changes, it is automatically synchronized to the -computer clipboard. - -Any Ctrl shortcut is forwarded to the device. In particular: - - Ctrl+c typically copies - - Ctrl+x typically cuts - - Ctrl+v typically pastes (after computer-to-device - clipboard synchronization) - -This typically works as you expect. - -The actual behavior depends on the active application though. For example, -_Termux_ sends SIGINT on Ctrl+c instead, and _K-9 Mail_ -composes a new message. - -To copy, cut and paste in such cases (but only supported on Android >= 7): - - MOD+c injects `COPY` - - MOD+x injects `CUT` - - MOD+v injects `PASTE` (after computer-to-device - clipboard synchronization) - -In addition, MOD+Shift+v injects the computer -clipboard text as a sequence of key events. This is useful when the component -does not accept text pasting (for example in _Termux_), but it can break -non-ASCII content. - -**WARNING:** Pasting the computer clipboard to the device (either via -Ctrl+v or MOD+v) copies the content -into the Android clipboard. As a consequence, any Android application could read -its content. You should avoid pasting sensitive content (like passwords) that -way. - -Some Android devices do not behave as expected when setting the device clipboard -programmatically. An option `--legacy-paste` is provided to change the behavior -of Ctrl+v and MOD+v so that they -also inject the computer clipboard text as a sequence of key events (the same -way as MOD+Shift+v). - -To disable automatic clipboard synchronization, use -`--no-clipboard-autosync`. - -#### Pinch-to-zoom - -To simulate "pinch-to-zoom": Ctrl+_click-and-move_. - -More precisely, hold down Ctrl while pressing the left-click button. -Until the left-click button is released, all mouse movements scale and rotate -the content (if supported by the app) relative to the center of the screen. - -Technically, _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. - -Alternatively, `scrcpy` can simulate a physical USB 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 via USB. - -Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it -is not possible to open a USB device if it is already open by another process -like the _adb daemon_). - -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 using 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]. - -This settings page can be started directly: - -```bash -adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -``` - -However, the option is only available when the HID keyboard is enabled (or when -a physical keyboard is connected). - -[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - -#### Physical mouse simulation (HID) - -Similarly to the physical keyboard simulation, it is possible to simulate a -physical mouse. Likewise, it only works if the device is connected by USB. - -By default, _scrcpy_ uses Android mouse events injection with absolute -coordinates. By simulating a physical mouse, a mouse pointer appears on the -Android device, and relative mouse motion, clicks and scrolls are injected. - -To enable this mode: - -```bash -scrcpy --hid-mouse -scrcpy -M # short version -``` - -You can also add `--forward-all-clicks` to [forward all mouse -buttons][forward_all_clicks]. - -[forward_all_clicks]: #right-click-and-middle-click - -When this mode is enabled, the computer mouse is "captured" (the mouse pointer -disappears from the computer and appears on the Android device instead). - -Special capture keys, either Alt or Super, toggle -(disable or enable) the mouse capture. Use one of them to give the control of -the mouse back to the computer. - - -#### OTG - -It is possible to run _scrcpy_ with only physical keyboard and mouse simulation -(HID), as if the computer keyboard and mouse were plugged directly to the device -via an OTG cable. - -In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. - -To enable OTG mode: - -```bash -scrcpy --otg -# Pass the serial if several USB devices are available -scrcpy --otg -s 0123456789abcdef -``` - -It is possible to enable only HID keyboard or HID mouse: - -```bash -scrcpy --otg --hid-keyboard # keyboard only -scrcpy --otg --hid-mouse # mouse only -scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse -# for convenience, enable both by default -scrcpy --otg # keyboard and mouse -``` - -Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is -connected by USB. - - -#### Text injection preference - -Two kinds of [events][textevents] are generated when typing text: - - _key events_, signaling that a key is pressed or released; - - _text events_, signaling that a text has been entered. - -By default, letters are injected using key events, so that the keyboard behaves -as expected in games (typically for WASD keys). - -But this may [cause issues][prefertext]. If you encounter such a problem, you -can avoid it by: - -```bash -scrcpy --prefer-text -``` - -(but this will break keyboard behavior in games) - -On the contrary, you could force to always inject raw key events: - -```bash -scrcpy --raw-key-events -``` - -These options have 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 - - -#### Key repeat - -By default, holding a key down generates repeated key events. This can cause -performance problems in some games, where these events are useless anyway. - -To avoid forwarding repeated key events: - -```bash -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 - -By default, right-click triggers BACK (or POWER on) and middle-click triggers -HOME. To disable these shortcuts and forward the clicks to the device instead: - -```bash -scrcpy --forward-all-clicks -``` - - -### File drop - -#### Install APK - -To install an APK, drag & drop an APK file (ending with `.apk`) to the _scrcpy_ -window. - -There is no visual feedback, a log is printed to the console. - - -#### Push file to device - -To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK) -file to the _scrcpy_ window. - -There is no visual feedback, a log is printed to the console. - -The target directory can be changed on start: - -```bash -scrcpy --push-target=/sdcard/Movies/ -``` - - -### Audio forwarding - -Audio is not forwarded by _scrcpy_. Use [sndcpy]. - -Also see [issue #14]. - -[sndcpy]: https://github.com/rom1v/sndcpy -[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 - - -## Shortcuts - -In the following list, MOD is the shortcut modifier. By default, it's -(left) Alt or (left) Super. - -It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, -`lalt`, `ralt`, `lsuper` and `rsuper`. For example: - -```bash -# use RCtrl for shortcuts -scrcpy --shortcut-mod=rctrl - -# use either LCtrl+LAlt or LSuper for shortcuts -scrcpy --shortcut-mod=lctrl+lalt,lsuper -``` - -_[Super] is typically the Windows or Cmd key._ - -[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) - - | Action | Shortcut - | ------------------------------------------- |:----------------------------- - | Switch fullscreen mode | MOD+f - | Rotate display left | MOD+ _(left)_ - | Rotate display right | MOD+ _(right)_ - | Resize window to 1:1 (pixel-perfect) | MOD+g - | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ - | Click on `HOME` | MOD+h \| _Middle-click_ - | Click on `BACK` | MOD+b \| _Right-click²_ - | Click on `APP_SWITCH` | MOD+s \| _4th-click³_ - | Click on `MENU` (unlock screen)⁴ | MOD+m - | Click on `VOLUME_UP` | MOD+ _(up)_ - | Click on `VOLUME_DOWN` | MOD+ _(down)_ - | Click on `POWER` | MOD+p - | Power on | _Right-click²_ - | Turn device screen off (keep mirroring) | MOD+o - | Turn device screen on | MOD+Shift+o - | Rotate device screen | MOD+r - | Expand notification panel | MOD+n \| _5th-click³_ - | Expand settings panel | MOD+n+n \| _Double-5th-click³_ - | Collapse panels | MOD+Shift+n - | Copy to clipboard⁵ | MOD+c - | Cut to clipboard⁵ | MOD+x - | Synchronize clipboards and paste⁵ | MOD+v - | Inject computer clipboard text | MOD+Shift+v - | Enable/disable FPS counter (on stdout) | MOD+i - | Pinch-to-zoom | Ctrl+_click-and-move_ - | Drag & drop APK file | Install APK from computer - | Drag & drop non-APK file | [Push file to device](#push-file-to-device) - -_¹Double-click on black borders to remove them._ -_²Right-click turns the screen on if it was off, presses BACK otherwise._ -_³4th and 5th mouse buttons, if your mouse has them._ -_⁴For react-native apps in development, `MENU` triggers development menu._ -_⁵Only on Android >= 7._ - -Shortcuts with repeated keys are executed by releasing and pressing the key a -second time. For example, to execute "Expand settings panel": - - 1. Press and keep pressing MOD. - 2. Then double-press n. - 3. Finally, release MOD. - -All Ctrl+_key_ shortcuts are forwarded to the device, so they are -handled by the active application. - - -## Custom paths - -To use a specific `adb` binary, configure its path in the environment variable -`ADB`: - -```bash -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`. - - -## Why the name _scrcpy_? +[wiki]: https://github.com/Genymobile/scrcpy/wiki -A colleague challenged me to find a name as unpronounceable as [gnirehtet]. -[`strcpy`] copies a **str**ing; `scrcpy` copies a **scr**een. +## Articles -[gnirehtet]: https://github.com/Genymobile/gnirehtet -[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html +- [Introducing scrcpy][article-intro] +- [Scrcpy now works wirelessly][article-tcpip] +[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ +[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ -## How to build? -See [BUILD]. +## Contact +If you encounter a bug, please read the [FAQ](FAQ.md) first, then open an [issue]. -## Common issues +[issue]: https://github.com/Genymobile/scrcpy/issues -See the [FAQ]. +For general questions or discussions, you can also use: -[FAQ]: FAQ.md + - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) + - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) -## Developers +## Donate -Read the [developers page]. +I'm [@rom1v](https://github.com/rom1v), the author and maintainer of _scrcpy_. -[developers page]: DEVELOP.md +If you appreciate this application, you can [support my open source +work][donate]. +[donate]: https://blog.rom1v.com/about/#support-my-open-source-work ## Licence @@ -1198,30 +132,3 @@ Read the [developers page]. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -## Articles - -- [Introducing scrcpy][article-intro] -- [Scrcpy now works wirelessly][article-tcpip] - -[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ -[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ - -## Contact - -If you encounter a bug, please read the [FAQ] first, then open an [issue]. - -[issue]: https://github.com/Genymobile/scrcpy/issues - -For general questions or discussions, you can also use: - - - Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy) - - Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app) - -## Translations - -Translations of this README in other languages are available in the [wiki]. - -[wiki]: https://github.com/Genymobile/scrcpy/wiki - -Only this README file is guaranteed to be up-to-date. diff --git a/doc/audio.md b/doc/audio.md new file mode 100644 index 00000000..3755fe37 --- /dev/null +++ b/doc/audio.md @@ -0,0 +1,90 @@ +# Audio + +Audio forwarding is supported for devices with Android 11 or higher, and it is +enabled by default: + + - For **Android 12 or newer**, it works out-of-the-box. + - For **Android 11**, you'll need to ensure that the device screen is unlocked + when starting scrcpy. A fake popup will briefly appear to make the system + think that the shell app is in the foreground. Without this, audio capture + will fail. + - For **Android 10 or earlier**, audio cannot be captured and is automatically + disabled. + +If audio capture fails, then mirroring continues with video only (since audio is +enabled by default, it is not acceptable to make scrcpy fail if it is not +available), unless `--require-audio` is set. + + +## No audio + +To disable audio: + +``` +scrcpy --no-audio +``` + +## Codec + +The audio codec can be selected. The possible values are `opus` (default), `aac` +and `raw` (uncompressed PCM 16-bit LE): + +```bash +scrcpy --audio-codec=opus # default +scrcpy --audio-codec=aac +scrcpy --audio-codec=raw +``` + +Several encoders may be available on the device. They can be listed by: + +```bash +scrcpy --list-encoders +``` + +To select a specific encoder: + +``` +scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder' +``` + +For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], +check `--audio-codec-options` in the manpage or in `scrcpy --help`. + +[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat + + +## Bit rate + +The default video bit-rate is 128Kbps. To change it: + +```bash +scrcpy --audio-bit-rate=64K +scrcpy --audio-bit-rate=64000 # equivalent +``` + +_This parameter does not apply to RAW audio codec (`--audio-codec=raw`)._ + + +## Buffering + +Audio buffering is unavoidable. It must be kept small enough so that the latency +is acceptable, but large enough to minimize buffer underrun (causing audio +glitches). + +The default buffer size is set to 50ms. It can be adjusted: + +```bash +scrcpy --audio-buffer=40 # smaller than default +scrcpy --audio-buffer=100 # higher than default +``` + +Note that this option changes the _target_ buffering. It is possible that this +target buffering might not be reached (on frequent buffer underflow typically). + +If you don't interact with the device (to watch a video for example), a higher +latency (for both [video](video.md#buffering) and audio) might be preferable to +avoid glitches and smooth the playback: + +``` +scrcpy --display-buffer=200 --audio-buffer=200 +``` diff --git a/BUILD.md b/doc/build.md similarity index 85% rename from BUILD.md rename to doc/build.md index 51f8141e..31a04cfb 100644 --- a/BUILD.md +++ b/doc/build.md @@ -2,57 +2,16 @@ Here are the instructions to build _scrcpy_ (client and server). - -## Simple - -If you just want to install the latest release from `master`, follow this -simplified process. - -First, you need to install the required packages: - -```bash -# 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 \ - libswresample-dev libusb-1.0-0 libusb-1.0-0-dev -``` - -Then clone the repo and execute the installation script -([source](install_release.sh)): - -```bash -git clone https://github.com/Genymobile/scrcpy -cd scrcpy -./install_release.sh -``` - -When a new release is out, update the repo and reinstall: - -```bash -git pull -./install_release.sh -``` - -To uninstall: - -```bash -sudo ninja -Cbuild-auto uninstall -``` - +If you just want to build and install the latest release, follow the simplified +process described in [doc/linux.md](linux.md). ## Branches -### `master` - -The `master` branch concerns the latest release, and is the home page of the -project on GitHub. - - -### `dev` - -`dev` is the current development branch. Every commit present in `dev` will be -in the next release. +There are two main branches: + - `master`: contains the latest release. It is the home page of the project on + GitHub. + - `dev`: the current development branch. Every commit present in `dev` will be + in the next release. If you want to contribute code, please base your commits on the latest `dev` branch. @@ -69,6 +28,8 @@ the following files to a directory accessible from your `PATH`: - `AdbWinApi.dll` - `AdbWinUsbApi.dll` +It is also available in scrcpy releases. + The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions. [adb]: https://developer.android.com/studio/command-line/adb.html @@ -314,7 +275,8 @@ This installs several files: - `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion) - `/usr/local/share/bash-completion/completions/scrcpy` (bash completion) -You can then [run](README.md#run) `scrcpy`. +You can then run `scrcpy`. + ### Uninstall diff --git a/doc/control.md b/doc/control.md new file mode 100644 index 00000000..0b060775 --- /dev/null +++ b/doc/control.md @@ -0,0 +1,149 @@ +# Control + +## Read-only + +To disable controls (everything which can interact with the device: input keys, +mouse events, drag&drop files): + +```bash +scrcpy --no-control +scrcpy -n # short version +``` + + +## Text injection preference + +Two kinds of [events][textevents] are generated when typing text: + - _key events_, signaling that a key is pressed or released; + - _text events_, signaling that a text has been entered. + +By default, letters are injected using key events, so that the keyboard behaves +as expected in games (typically for WASD keys). + +But this may [cause issues][prefertext]. If you encounter such a problem, you +can avoid it by: + +```bash +scrcpy --prefer-text +``` + +(but this will break keyboard behavior in games) + +On the contrary, you could force to always inject raw key events: + +```bash +scrcpy --raw-key-events +``` + +These options have 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 + + +## Copy-paste + +Any time the Android clipboard changes, it is automatically synchronized to the +computer clipboard. + +Any Ctrl shortcut is forwarded to the device. In particular: + - Ctrl+c typically copies + - Ctrl+x typically cuts + - Ctrl+v typically pastes (after computer-to-device + clipboard synchronization) + +This typically works as you expect. + +The actual behavior depends on the active application though. For example, +_Termux_ sends SIGINT on Ctrl+c instead, and _K-9 Mail_ +composes a new message. + +To copy, cut and paste in such cases (but only supported on Android >= 7): + - MOD+c injects `COPY` + - MOD+x injects `CUT` + - MOD+v injects `PASTE` (after computer-to-device + clipboard synchronization) + +In addition, MOD+Shift+v injects the computer +clipboard text as a sequence of key events. This is useful when the component +does not accept text pasting (for example in _Termux_), but it can break +non-ASCII content. + +**WARNING:** Pasting the computer clipboard to the device (either via +Ctrl+v or MOD+v) copies the content +into the Android clipboard. As a consequence, any Android application could read +its content. You should avoid pasting sensitive content (like passwords) that +way. + +Some Android devices do not behave as expected when setting the device clipboard +programmatically. An option `--legacy-paste` is provided to change the behavior +of Ctrl+v and MOD+v so that they +also inject the computer clipboard text as a sequence of key events (the same +way as MOD+Shift+v). + +To disable automatic clipboard synchronization, use +`--no-clipboard-autosync`. + +## Pinch-to-zoom + +To simulate "pinch-to-zoom": Ctrl+_click-and-move_. + +More precisely, hold down Ctrl while pressing the left-click button. +Until the left-click button is released, all mouse movements scale and rotate +the content (if supported by the app) relative to the center of the screen. + +Technically, _scrcpy_ generates additional touch events from a "virtual finger" +at a location inverted through the center of the screen. + + +## Key repeat + +By default, holding a key down generates repeated key events. This can cause +performance problems in some games, where these events are useless anyway. + +To avoid forwarding repeated key events: + +```bash +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 + +By default, right-click triggers BACK (or POWER on) and middle-click triggers +HOME. To disable these shortcuts and forward the clicks to the device instead: + +```bash +scrcpy --forward-all-clicks +``` + +## File drop + +### Install APK + +To install an APK, drag & drop an APK file (ending with `.apk`) to the _scrcpy_ +window. + +There is no visual feedback, a log is printed to the console. + + +### Push file to device + +To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK) +file to the _scrcpy_ window. + +There is no visual feedback, a log is printed to the console. + +The target directory can be changed on start: + +```bash +scrcpy --push-target=/sdcard/Movies/ +``` + +## Physical keyboard and mouse simulation + +See the dedicated [HID/OTG](hid-otg.md) page. diff --git a/DEVELOP.md b/doc/develop.md similarity index 100% rename from DEVELOP.md rename to doc/develop.md diff --git a/doc/device.md b/doc/device.md new file mode 100644 index 00000000..c7e1ec04 --- /dev/null +++ b/doc/device.md @@ -0,0 +1,228 @@ +# Device + +## Selection + +If exactly one device is connected (i.e. listed by `adb devices`), then it is +automatically selected. + +However, if there are multiple devices connected, you must specify the one to +use in one of 4 ways: + - by its serial: + ```bash + scrcpy --serial=0123456789abcdef + scrcpy -s 0123456789abcdef # short version + + # the serial is the ip:port if connected over TCP/IP (same behavior as adb) + scrcpy --serial=192.168.1.1:5555 + ``` + - the one connected over USB (if there is exactly one): + ```bash + scrcpy --select-usb + scrcpy -d # short version + ``` + - the one connected over TCP/IP (if there is exactly one): + ```bash + scrcpy --select-tcpip + scrcpy -e # short version + ``` + - a device already listening on TCP/IP (see [below](#tcpip-wireless)): + ```bash + scrcpy --tcpip=192.168.1.1:5555 + scrcpy --tcpip=192.168.1.1 # default port is 5555 + ``` + +The serial may also be provided via the environment variable `ANDROID_SERIAL` +(also used by `adb`): + +```bash +# in bash +export ANDROID_SERIAL=0123456789abcdef +scrcpy +``` + +```cmd +:: in cmd +set ANDROID_SERIAL=0123456789abcdef +scrcpy +``` + +```powershell +# in PowerShell +$env:ANDROID_SERIAL = '0123456789abcdef' +scrcpy +``` + + +## TCP/IP (wireless) + +_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a +device over TCP/IP. The device must be connected on the same network as the +computer. + +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless + + +### Automatic + +An option `--tcpip` allows to configure the connection automatically. There are +two variants. + +If the device (accessible at 192.168.1.1 in this example) already listens on a +port (typically 5555) for incoming _adb_ connections, then run: + +```bash +scrcpy --tcpip=192.168.1.1 # default port is 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP +address), connect the device over USB, then run: + +```bash +scrcpy --tcpip # without arguments +``` + +It will automatically find the device IP address and adb port, enable TCP/IP +mode if necessary, then connect to the device before starting. + + +### Manual + +Alternatively, it is possible to enable the TCP/IP connection manually using +`adb`: + +1. Plug the device into a USB port on your computer. +2. Connect the device to the same Wi-Fi network as your computer. +3. Get your device IP address, in Settings → About phone → Status, or by + executing this command: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. +5. Unplug your device. +6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` +with the device IP address you found)_. +7. Run `scrcpy` as usual. +8. Run `adb disconnect` once you're done. + +Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass +having to physically connect your device directly to your computer. + +[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ + + +## Autostart + +A small tool (by the scrcpy author) allows to run arbitrary commands whenever a +new Android device is connected: [AutoAdb]. It can be used to start scrcpy: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb + + +## Display + +If several displays are available on the Android device, it is possible to +select the display to mirror: + +```bash +scrcpy --display=1 +``` + +The list of display ids can be retrieved by: + +```bash +scrcpy --list-displays +``` + +A secondary display may only be controlled if the device runs at least Android +10 (otherwise it is mirrored as read-only). + + +## Actions + +Some command line arguments perform actions on the device itself while scrcpy is +running. + + +### Stay awake + +To prevent the device from sleeping after a delay **when the device is plugged +in**: + +```bash +scrcpy --stay-awake +scrcpy -w +``` + +The initial state is restored when _scrcpy_ is closed. + +If the device is not plugged in (i.e. only connected over TCP/IP), +`--stay-awake` has no effect (this is the Android behavior). + + +### Turn screen off + +It is possible to turn the device screen off while mirroring on start with a +command-line option: + +```bash +scrcpy --turn-screen-off +scrcpy -S # short version +``` + +Or by pressing MOD+o at any time (see +[shortcuts](shortcuts.md)). + +To turn it back on, press MOD+Shift+o. + +On Android, the `POWER` button always turns the screen on. For convenience, if +`POWER` is sent via _scrcpy_ (via right-click or MOD+p), +it will force to turn the screen off after a small delay (on a best effort +basis). The physical `POWER` button will still cause the screen to be turned on. + +It can also be useful to prevent the device from sleeping: + +```bash +scrcpy --turn-screen-off --stay-awake +scrcpy -Sw # short version +``` + + +### Show touches + +For presentations, it may be useful to show physical touches (on the physical +device). Android exposes this feature in _Developers options_. + +_Scrcpy_ provides an option to enable this feature on start and restore the +initial value on exit: + +```bash +scrcpy --show-touches +scrcpy -t # short version +``` + +Note that it only shows _physical_ touches (by a finger on the device). + + +### Power off on close + +To turn the device screen off when closing _scrcpy_: + +```bash +scrcpy --power-off-on-close +``` + +### Power on on start + +By default, on start, the device is powered on. To prevent this behavior: + +```bash +scrcpy --no-power-on +``` + diff --git a/doc/hid-otg.md b/doc/hid-otg.md new file mode 100644 index 00000000..c64af752 --- /dev/null +++ b/doc/hid-otg.md @@ -0,0 +1,108 @@ +# HID/OTG + +By default, _scrcpy_ injects input events at the Android API level. As an +alternative, when connected over USB, it is possible to send HID events, so that +scrcpy behaves as if it was a physical keyboard and/or mouse connected to the +Android device. + +A special [OTG](#otg) mode allows to control the device without mirroring (and +without USB debugging). + + +## Physical keyboard simulation + +By default, _scrcpy_ uses Android key or text injection. It works everywhere, +but is limited to ASCII. + +Instead, it can simulate a physical USB 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 via USB. + +Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it +is not possible to open a USB device if it is already open by another process +like the _adb daemon_). + +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 using 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]. + +This settings page can be started directly: + +```bash +adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS +``` + +However, the option is only available when the HID keyboard is enabled (or when +a physical keyboard is connected). + +[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 + + +## Physical mouse simulation + +By default, _scrcpy_ uses Android mouse events injection with absolute +coordinates. By simulating a physical mouse, a mouse pointer appears on the +Android device, and relative mouse motion, clicks and scrolls are injected. + +To enable this mode: + +```bash +scrcpy --hid-mouse +scrcpy -M # short version +``` + +When this mode is enabled, the computer mouse is "captured" (the mouse pointer +disappears from the computer and appears on the Android device instead). + +Special capture keys, either Alt or Super, toggle +(disable or enable) the mouse capture. Use one of them to give the control of +the mouse back to the computer. + + +## OTG + +It is possible to run _scrcpy_ with only physical keyboard and mouse simulation +(HID), as if the computer keyboard and mouse were plugged directly to the device +via an OTG cable. + +In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. + +This is similar to `--hid-keyboard --hid-mouse`, but without mirroring. + +To enable OTG mode: + +```bash +scrcpy --otg +# Pass the serial if several USB devices are available +scrcpy --otg -s 0123456789abcdef +``` + +It is possible to enable only HID keyboard or HID mouse: + +```bash +scrcpy --otg --hid-keyboard # keyboard only +scrcpy --otg --hid-mouse # mouse only +scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse +# for convenience, enable both by default +scrcpy --otg # keyboard and mouse +``` + +Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is +connected over USB. diff --git a/doc/linux.md b/doc/linux.md new file mode 100644 index 00000000..3b0c560d --- /dev/null +++ b/doc/linux.md @@ -0,0 +1,79 @@ +# On Linux + +## Install + +Packaging status + +Scrcpy is packaged in several distributions and package managers: + + - Debian/Ubuntu: `apt install scrcpy` + - Arch Linux: `pacman -S scrcpy` + - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` + - Gentoo: [ebuild][ebuild-link] file + - Snap: `snap install scrcpy` + - … (see [repology](https://repology.org/project/scrcpy/versions)) + +[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy + +### Latest version + +However, the packaged version is not always the latest release. To install the +latest release from `master`, follow this simplified process. + +First, you need to install the required packages: + +```bash +# 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 \ + libswresample-dev libusb-1.0-0 libusb-1.0-0-dev +``` + +Then clone the repo and execute the installation script +([source](/install_release.sh)): + +```bash +git clone https://github.com/Genymobile/scrcpy +cd scrcpy +./install_release.sh +``` + +When a new release is out, update the repo and reinstall: + +```bash +git pull +./install_release.sh +``` + +To uninstall: + +```bash +sudo ninja -Cbuild-auto uninstall +``` + +_Note that this simplified process only works for released versions (it +downloads a prebuilt server binary), so for example you can't use it for testing +the development branch (`dev`)._ + +_See [build.md](build.md) to build and install the app manually._ + + +## Run + +Once installed, run from a terminal: + +```bash +scrcpy +``` + +or with arguments (here to disable audio and record to `file.mkv`): + +```bash +scrcpy --no-audio --record=file.mkv +``` + +Documentation for command line arguments is available: + - `man scrcpy` + - `scrcpy --help` + - on [github](/README.md) diff --git a/doc/macos.md b/doc/macos.md new file mode 100644 index 00000000..3092dbdc --- /dev/null +++ b/doc/macos.md @@ -0,0 +1,47 @@ +# On macOS + +## Install + +Scrcpy is available in [Homebrew]: + +```bash +brew install scrcpy +``` + +[Homebrew]: https://brew.sh/ + +You need `adb`, accessible from your `PATH`. If you don't have it yet: + +```bash +brew install android-platform-tools +``` + +Alternatively, Scrcpy is also available in [MacPorts], which sets up `adb` for you: + +```bash +sudo port install scrcpy +``` + +[MacPorts]: https://www.macports.org/ + +_See [build.md](build.md) to build and install the app manually._ + + +## Run + +Once installed, run from a terminal: + +```bash +scrcpy +``` + +or with arguments (here to disable audio and record to `file.mkv`): + +```bash +scrcpy --no-audio --record=file.mkv +``` + +Documentation for command line arguments is available: + - `man scrcpy` + - `scrcpy --help` + - on [github](/README.md) diff --git a/doc/recording.md b/doc/recording.md new file mode 100644 index 00000000..4aad088c --- /dev/null +++ b/doc/recording.md @@ -0,0 +1,44 @@ +# Recording + +To record video and audio streams while mirroring: + +```bash +scrcpy --record=file.mp4 +scrcpy -r file.mkv +``` + +To record only the video: + +```bash +scrcpy --no-audio --record=file.mp4 +``` + +_It is currently not possible to record only the audio._ + +To disable mirroring while recording: + +```bash +scrcpy --no-display --record=file.mp4 +scrcpy -Nr file.mkv +# interrupt recording with Ctrl+C +``` + +Timestamps are captured on the device, so [packet delay variation] does not +impact the recorded file, which is always clean (only if you use `--record` of +course, not if you capture your scrcpy window and audio output on the computer). + +[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation + +The video and audio streams are encoded on the device, but are muxed on the +client side. Two formats (containers) are supported: + - Matroska (`.mkv`) + - MP4 (`.mp4`) + +The container is automatically selected based on the filename. + +It is also possible to explicitly select a container (in that case the filename +needs not end with `.mkv` or `.mp4`): + +``` +scrcpy --record=file --record-format=mkv +``` diff --git a/doc/shortcuts.md b/doc/shortcuts.md new file mode 100644 index 00000000..06cae428 --- /dev/null +++ b/doc/shortcuts.md @@ -0,0 +1,68 @@ +# Shortcuts + +Actions can be performed on the scrcpy window using keyboard and mouse +shortcuts. + +In the following list, MOD is the shortcut modifier. By default, it's +(left) Alt or (left) Super. + +It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, +`lalt`, `ralt`, `lsuper` and `rsuper`. For example: + +```bash +# use RCtrl for shortcuts +scrcpy --shortcut-mod=rctrl + +# use either LCtrl+LAlt or LSuper for shortcuts +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] is typically the Windows or Cmd key._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + + | Action | Shortcut + | ------------------------------------------- |:----------------------------- + | Switch fullscreen mode | MOD+f + | Rotate display left | MOD+ _(left)_ + | Rotate display right | MOD+ _(right)_ + | Resize window to 1:1 (pixel-perfect) | MOD+g + | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ + | Click on `HOME` | MOD+h \| _Middle-click_ + | Click on `BACK` | MOD+b \| _Right-click²_ + | Click on `APP_SWITCH` | MOD+s \| _4th-click³_ + | Click on `MENU` (unlock screen)⁴ | MOD+m + | Click on `VOLUME_UP` | MOD+ _(up)_ + | Click on `VOLUME_DOWN` | MOD+ _(down)_ + | Click on `POWER` | MOD+p + | Power on | _Right-click²_ + | Turn device screen off (keep mirroring) | MOD+o + | Turn device screen on | MOD+Shift+o + | Rotate device screen | MOD+r + | Expand notification panel | MOD+n \| _5th-click³_ + | Expand settings panel | MOD+n+n \| _Double-5th-click³_ + | Collapse panels | MOD+Shift+n + | Copy to clipboard⁵ | MOD+c + | Cut to clipboard⁵ | MOD+x + | Synchronize clipboards and paste⁵ | MOD+v + | Inject computer clipboard text | MOD+Shift+v + | Enable/disable FPS counter (on stdout) | MOD+i + | Pinch-to-zoom | Ctrl+_click-and-move_ + | Drag & drop APK file | Install APK from computer + | Drag & drop non-APK file | [Push file to device](#push-file-to-device) + +_¹Double-click on black borders to remove them._ +_²Right-click turns the screen on if it was off, presses BACK otherwise._ +_³4th and 5th mouse buttons, if your mouse has them._ +_⁴For react-native apps in development, `MENU` triggers development menu._ +_⁵Only on Android >= 7._ + +Shortcuts with repeated keys are executed by releasing and pressing the key a +second time. For example, to execute "Expand settings panel": + + 1. Press and keep pressing MOD. + 2. Then double-press n. + 3. Finally, release MOD. + +All Ctrl+_key_ shortcuts are forwarded to the device, so they are +handled by the active application. diff --git a/doc/tunnels.md b/doc/tunnels.md new file mode 100644 index 00000000..987a0293 --- /dev/null +++ b/doc/tunnels.md @@ -0,0 +1,123 @@ +# Tunnels + +Scrcpy is designed to mirror local Android devices. Tunnels allow to connect to +a remote device (e.g. over the Internet). + +To connect to a remote device, it is possible to connect a local `adb` client to +a remote `adb` server (provided they use the same version of the _adb_ +protocol). + + +## Remote ADB server + +To connect to a remote _adb server_, make the server listen on all interfaces: + +```bash +adb kill-server +adb -a nodaemon server start +# keep this open +``` + +**Warning: all communications between clients and the _adb server_ are +unencrypted.** + +Suppose that this server is accessible at 192.168.1.2. Then, from another +terminal, run `scrcpy`: + +```bash +# in bash +export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:192.168.1.2:5037 +scrcpy --tunnel-host=192.168.1.2 +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:192.168.1.2:5037' +scrcpy --tunnel-host=192.168.1.2 +``` + +By default, `scrcpy` uses the local port used for `adb forward` tunnel +establishment (typically `27183`, see `--port`). It is also possible to force a +different tunnel port (it may be useful in more complex situations, when more +redirections are involved): + +``` +scrcpy --tunnel-port=1234 +``` + + +## SSH tunnel + +To communicate with a remote _adb server_ securely, it is preferable to use an +SSH tunnel. + +First, make sure the _adb server_ is running on the remote computer: + +```bash +adb start-server +``` + +Then, establish an SSH tunnel: + +```bash +# local 5038 --> remote 5037 +# local 27183 <-- remote 27183 +ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer +# keep this open +``` + +From another terminal, run `scrcpy`: + +```bash +# in bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy +``` + +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' +scrcpy +``` + +To avoid enabling remote port forwarding, you could force a forward connection +instead (notice the `-L` instead of `-R`): + +```bash +# local 5038 --> remote 5037 +# local 27183 --> remote 27183 +ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer +# keep this open +``` + +From another terminal, run `scrcpy`: + +```bash +# in bash +export ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy --force-adb-forward +``` + +```cmd +:: in cmd +set ADB_SERVER_SOCKET=tcp:localhost:5038 +scrcpy --force-adb-forward +``` + +```powershell +# in PowerShell +$env:ADB_SERVER_SOCKET = 'tcp:localhost:5038' +scrcpy --force-adb-forward +``` diff --git a/doc/v4l2.md b/doc/v4l2.md new file mode 100644 index 00000000..ea8c0eed --- /dev/null +++ b/doc/v4l2.md @@ -0,0 +1,65 @@ +# Video4Linux + +On Linux, it is possible to send the video stream to a [v4l2] loopback device, +so that the Android device can be opened like a webcam by any v4l2-capable tool. + +[v4l2]: https://en.wikipedia.org/wiki/Video4Linux + +The module `v4l2loopback` must be installed: + +```bash +sudo apt install v4l2loopback-dkms +``` + +To create a v4l2 device: + +```bash +sudo modprobe v4l2loopback +``` + +This will create a new video device in `/dev/videoN`, where `N` is an integer +(more [options](https://github.com/umlaeute/v4l2loopback#options) are available +to create several devices or devices with specific IDs). + +To list the enabled devices: + +```bash +# requires v4l-utils package +v4l2-ctl --list-devices + +# simple but might be sufficient +ls /dev/video* +``` + +To start `scrcpy` using a v4l2 sink: + +```bash +scrcpy --v4l2-sink=/dev/videoN +scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window +``` + +(replace `N` with the device ID, check with `ls /dev/video*`) + +Once enabled, you can open your video stream with a v4l2-capable tool: + +```bash +ffplay -i /dev/videoN +vlc v4l2:///dev/videoN # VLC might add some buffering delay +``` + +For example, you could capture the video within [OBS] or within your video +conference tool. + +[OBS]: https://obsproject.com/ + + +## Buffering + +By default, there is no video buffering, to get the lowest possible latency. + +As for the [video display](video.md#buffering), it is possible to add +buffering to delay the v4l2 stream: + +```bash +scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink +``` diff --git a/doc/video.md b/doc/video.md new file mode 100644 index 00000000..a2e9d106 --- /dev/null +++ b/doc/video.md @@ -0,0 +1,175 @@ +# Video + +## Size + +By default, scrcpy attempts to mirror at the Android device resolution. + +It might be useful to mirror at a lower definition to increase performance. To +limit both width and height to some maximum value (here 1024): + +```bash +scrcpy --max-size=1024 +scrcpy -m 1024 # short version +``` + +The other dimension is computed so that the Android device aspect ratio is +preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. + +If encoding fails, scrcpy automatically tries again with a lower definition +(unless `--no-downsize-on-error` is enabled). + + +## Bit rate + +The default video bit-rate is 8 Mbps. To change it: + +```bash +scrcpy --video-bit-rate=2M +scrcpy --video-bit-rate=2000000 # equivalent +scrcpy -b 2M # short version +``` + + +## Frame rate + +The capture frame rate can be limited: + +```bash +scrcpy --max-fps=15 +``` + +The actual capture frame rate may be printed to the console: + +``` +scrcpy --print-fps +``` + +It may also be enabled or disabled at anytime with MOD+i +(see [shortcuts](shortcuts.md)). + +The frame rate is intrinsically variable: a new frame is produced only when the +screen content changes. For example, if you play a fullscreen video at 24fps on +your device, you should not get more than 24 frames per second in scrcpy. + + +## Codec + +The video codec can be selected. The possible values are `h264` (default), +`h265` and `av1`: + +```bash +scrcpy --video-codec=h264 # default +scrcpy --video-codec=h265 +scrcpy --video-codec=av1 +``` + +H265 may provide better quality, but H264 should provide lower latency. +AV1 encoders are not common on current Android devices. + +Several encoders may be available on the device. They can be listed by: + +```bash +scrcpy --list-encoders +``` + +Sometimes, the default encoder may have issues or even crash, so it is useful to +try another one: + +```bash +scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc' +``` + +For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], +check `--video-codec-options` in the manpage or in `scrcpy --help`. + +[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat + + +## Rotation + +The rotation may be applied at 3 different levels: + - The [shortcut](shortcuts.md) MOD+r requests the + device to switch between portrait and landscape (the current running app may + refuse, if it does not support the requested orientation). + - `--lock-video-orientation` changes the mirroring orientation (the orientation + of the video sent from the device to the computer). This affects the + recording. + - `--rotation` rotates only the window content. This only affects the display, + not the recording. It may be changed dynamically at any time using the + [shortcuts](shortcuts.md) MOD+ and + MOD+. + +To lock the mirroring orientation: + +```bash +scrcpy --lock-video-orientation # initial (current) orientation +scrcpy --lock-video-orientation=0 # natural orientation +scrcpy --lock-video-orientation=1 # 90° counterclockwise +scrcpy --lock-video-orientation=2 # 180° +scrcpy --lock-video-orientation=3 # 90° clockwise +``` + +To set an initial window rotation: + +```bash +scrcpy --rotation=0 # no rotation +scrcpy --rotation=1 # 90 degrees counterclockwise +scrcpy --rotation=2 # 180 degrees +scrcpy --rotation=3 # 90 degrees clockwise +``` + +## Crop + +The device screen may be cropped to mirror only part of the screen. + +This is useful, for example, to mirror only one eye of the Oculus Go: + +```bash +scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0) +``` + +The values are expressed in the device natural orientation (portrait for a +phone, landscape for a tablet). + +If `--max-size` is also specified, resizing is applied after cropping. + + +## Buffering + +By default, there is no video buffering, to get the lowest possible latency. + +Buffering can be added to delay the video stream and compensate for jitter to +get a smoother playback (see [#2464]). + +[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 + +The configuration is available independently for the display, +[v4l2 sinks](video.md#video4linux) and [audio](audio.md#buffering) playback. + +```bash +scrcpy --display-buffer=50 # add 50ms buffering for display +scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink +scrcpy --audio-buffer=200 # set 200ms buffering for audio playback +``` + +They can be applied simultaneously: + +```bash +scrcpy --display-buffer=50 --v4l2-buffer=300 +``` + + +## No display + +It is possible to capture an Android device without displaying a mirroring +window. This option is available if either [recording](recording.md) or +[v4l2](#video4linux) is enabled: + +```bash +scrcpy --v4l2-sink=/dev/video2 --no-display +scrcpy --record=file.mkv --no-display +``` + +## Video4Linux + +See the dedicated [Video4Linux](v4l2.md) page. diff --git a/doc/window.md b/doc/window.md new file mode 100644 index 00000000..b5b73921 --- /dev/null +++ b/doc/window.md @@ -0,0 +1,55 @@ +# Window + +## Title + +By default, the window title is the device model. It can be changed: + +```bash +scrcpy --window-title='My device' +``` + +## Position and size + +The initial window position and size may be specified: + +```bash +scrcpy --window-x=100 --window-y=100 --window-width=800 --window-height=600 +``` + +## Borderless + +To disable window decorations: + +```bash +scrcpy --window-borderless +``` + +## Always on top + +To keep the window always on top: + +```bash +scrcpy --always-on-top +``` + +## Fullscreen + +The app may be started directly in fullscreen: + +```bash +scrcpy --fullscreen +scrcpy -f # short version +``` + +Fullscreen mode can then be toggled dynamically with MOD+f +(see [shortcuts](shortcuts.md)). + + +## Disable screensaver + +By default, _scrcpy_ does not prevent the screensaver from running on the +computer. To disable it: + +```bash +scrcpy --disable-screensaver +``` diff --git a/doc/windows.md b/doc/windows.md new file mode 100644 index 00000000..fd15a5a7 --- /dev/null +++ b/doc/windows.md @@ -0,0 +1,84 @@ +# On Windows + +## Install + +Download the [latest release]: + + - [`scrcpy-win64-v1.25.zip`][direct-win64] + SHA-256: `db65125e9c65acd00359efb7cea9c05f63cc7ccd5833000cd243cc92f5053028` + +[release]: https://github.com/Genymobile/scrcpy/releases/latest +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-win64-v1.25.zip + +and extract it. + +Alternatively, you could install it from packages manager, like [Chocolatey]: + +```bash +choco install scrcpy +choco install adb # if you don't have it yet +``` + +or [Scoop]: + + +```bash +scoop install scrcpy +scoop install adb # if you don't have it yet +``` + +[Chocolatey]: https://chocolatey.org/ +[Scoop]: https://scoop.sh + +_See [build.md](build.md) to build and install the app manually._ + + +## Run + +Scrcpy is a command line application: it is mainly intended to be executed from +a terminal with command line arguments. + +To open a terminal at the expected location, double-click on +`open_a_terminal_here.bat` in your scrcpy directory, then type your command. For +example, without arguments: + +```bash +scrcpy +``` + +or with arguments (here to disable audio and record to `file.mkv`): + +``` +scrcpy --no-audio --record=file.mkv +``` + +Documentation for command line arguments is available: + - `scrcpy --help` + - on [github](/README.md) + +To start scrcpy directly without opening a terminal, double-click on one of +these files: + - `scrcpy-console.bat`: start with a terminal open (it will close when scrcpy + terminates, unless an error occurs); + - `scrcpy-noconsole.vbs`: start without a terminal (but you won't see any error + message). + +_Avoid double-clicking on `scrcpy.exe` directly: on error, the terminal would +close immediately and you won't have time to read any error message (this +executable is intended to be run from the terminal). Use `scrcpy-console.bat` +instead._ + +If you plan to always use the same arguments, create a file `myscrcpy.bat` +(enable [show file extensions] to avoid confusion) containing your command, For +example: + +```bash +scrcpy --prefer-text --turn-screen-off --stay-awake +``` + +[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ + +Then just double-click on that file. + +You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs` +to add some arguments. From f1b2d6bbbb5afd1f95913a49b94eda7cfb85226b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 01:45:49 +0100 Subject: [PATCH 0815/1133] Bump version to 2.0 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index e20453c1..e9eb1903 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "1.25" + VALUE "ProductVersion", "2.0" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 0d25085a..ac16c23b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '1.25', + version: '2.0', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 1d80d15f..ce234d10 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 33 - versionCode 12500 - versionName "1.25" + versionCode 20000 + versionName "2.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 6677844c..3201034f 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.25 +SCRCPY_VERSION_NAME=2.0 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} From abc1be4872ee1869b307405b091d736b7cd2e0ff Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 02:37:49 +0100 Subject: [PATCH 0816/1133] Update links to v2.0 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 9 ++++++--- install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bc6f3bd3..a97822a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v1.25) +# scrcpy (v2.0) scrcpy diff --git a/doc/build.md b/doc/build.md index 31a04cfb..86d9436a 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v1.25`][direct-scrcpy-server] - SHA-256: `ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb` + - [`scrcpy-server-v2.0`][direct-scrcpy-server] + SHA-256: `9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index fd15a5a7..814cda9d 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,11 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v1.25.zip`][direct-win64] - SHA-256: `db65125e9c65acd00359efb7cea9c05f63cc7ccd5833000cd243cc92f5053028` + - [`scrcpy-win64-v2.0.zip`][direct-win64] (64-bit) + SHA-256: `ae4c8d37a496b43f8974ba8f07f708e22a9570ba0cddc3dc3a36edbccd4d2a20` + - [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit) + SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c` [release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-win64-v1.25.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 319aaa4f..609c9556 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25 -PREBUILT_SERVER_SHA256=ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0 +PREBUILT_SERVER_SHA256=9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From cbc638c6baa31b989ea40ac22dc109ae6c1cce60 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 02:44:42 +0100 Subject: [PATCH 0817/1133] Fix broken link in shortcuts documentation --- doc/shortcuts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 06cae428..6528e7b4 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -49,7 +49,7 @@ _[Super] is typically the Windows or Cmd key._ | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom | Ctrl+_click-and-move_ | Drag & drop APK file | Install APK from computer - | Drag & drop non-APK file | [Push file to device](#push-file-to-device) + | Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device) _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ From e5aa2ce01f638ebe083c821fd204a45d9d8f317d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 02:59:44 +0100 Subject: [PATCH 0818/1133] Fix broken link in Windows download page --- doc/windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/windows.md b/doc/windows.md index 814cda9d..1f0e3b81 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -9,7 +9,7 @@ Download the [latest release]: - [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit) SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c` -[release]: https://github.com/Genymobile/scrcpy/releases/latest +[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip From 6b769675fa68e60c9765022e43c4d7b1e329353a Mon Sep 17 00:00:00 2001 From: Ruoyu Zhong Date: Sun, 12 Mar 2023 14:23:35 +0800 Subject: [PATCH 0819/1133] Fix an "expected expression" error In C, a label can only be followed by a statement, not a declaration. An error in `app/src/screen.c` violated this, and led to a build error with an error message similar to the one below: ../app/src/screen.c:821:13: error: expected expression bool ok = sc_screen_init_size(screen); ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/13.0.0/include/stdbool.h:15:14: note: expanded from macro 'bool' #define bool _Bool ^ ../app/src/screen.c:822:18: error: use of undeclared identifier 'ok' if (!ok) { ^ 2 errors generated. This could be fixed by introducing a new block (or compound statement; as is already being done in the next `case`). That is a statement. Fixes #3785 PR #3787 Signed-off-by: Ruoyu Zhong Signed-off-by: Romain Vimont --- app/src/screen.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index f74fd8a5..b00b0d05 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -816,7 +816,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { bool relative_mode = sc_screen_is_relative_mode(screen); switch (event->type) { - case SC_EVENT_SCREEN_INIT_SIZE: + case SC_EVENT_SCREEN_INIT_SIZE: { // The initial size is passed via screen->frame_size bool ok = sc_screen_init_size(screen); if (!ok) { @@ -824,6 +824,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { return false; } return true; + } case SC_EVENT_NEW_FRAME: { bool ok = sc_screen_update_frame(screen); if (!ok) { From 80a6fa7a01f019e59e95bfcca47de1a80fe17c6a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 08:37:08 +0100 Subject: [PATCH 0820/1133] Fix comparison warning An int was compared with an unsigned: ../app/src/audio_player.c:290:27: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] if (abs(diff) < ap->sample_rate / 1000) { ~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~ --- app/src/audio_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 5abc9088..bba39acb 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -287,7 +287,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, float avg = sc_average_get(&ap->avg_buffering); int diff = ap->target_buffering - avg; - if (abs(diff) < ap->sample_rate / 1000) { + if (abs(diff) < (int) ap->sample_rate / 1000) { // Do not compensate for less than 1ms, the error is just noise diff = 0; } else if (diff < 0 && buffered_samples < ap->target_buffering) { From 02586cf21f18d52160a982e9b55e19d8b0b9993f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 08:54:42 +0100 Subject: [PATCH 0821/1133] Fix build issue on FFmpeg < 5.1 An include was missing. Fixes #3783 --- app/src/demuxer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 96303155..5a613505 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -1,6 +1,7 @@ #include "demuxer.h" #include +#include #include #include From cbca79b95baa2b2cf39b72d0a0b2b3de0725959a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 12:39:05 +0100 Subject: [PATCH 0822/1133] Fix v4l2 sink The codec id to write as codec parameters is the one from the v4l2 encoder, not from the decoder. Regression introduced by be985b8242e0626288674b84c5039725170f8f0c. Fixes #3795 --- app/src/v4l2_sink.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 717d2bd5..3b3eb8d0 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -210,6 +210,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { goto error_avformat_free_context; } + // The codec is from the v4l2 encoder, not from the decoder + ostream->codecpar->codec_id = encoder->id; + int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE); if (ret < 0) { LOGE("Failed to open output device: %s", vs->device_name); From 5899af6a2fda6c326912a7d49bae268c0c60b71c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Mar 2023 21:08:30 +0100 Subject: [PATCH 0823/1133] Add blogpost link about scrcpy 2.0 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a97822a5..858ba843 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,11 @@ documented in the following pages: - [Introducing scrcpy][article-intro] - [Scrcpy now works wirelessly][article-tcpip] +- [Scrcpy 2.0, with audio][article-scrcpy2] [article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ [article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/ - +[article-scrcpy2]: https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio/ ## Contact From fb61b779a6da299b1f93ec598b2332af9d387416 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Mar 2023 08:40:31 +0100 Subject: [PATCH 0824/1133] Add references to prerequisites Users sometimes only read the OS-specific instructions, they must be aware of the prerequisites. --- README.md | 2 +- doc/linux.md | 2 ++ doc/macos.md | 2 ++ doc/windows.md | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 858ba843..2dfb6686 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Its features include: - [OTG mode](doc/hid-otg.md#otg) - and more… -## Requirements +## Prerequisites The Android device requires at least API 21 (Android 5.0). diff --git a/doc/linux.md b/doc/linux.md index 3b0c560d..bc354959 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -61,6 +61,8 @@ _See [build.md](build.md) to build and install the app manually._ ## Run +_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._ + Once installed, run from a terminal: ```bash diff --git a/doc/macos.md b/doc/macos.md index 3092dbdc..35d90e9d 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -29,6 +29,8 @@ _See [build.md](build.md) to build and install the app manually._ ## Run +_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._ + Once installed, run from a terminal: ```bash diff --git a/doc/windows.md b/doc/windows.md index 1f0e3b81..521ad45e 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -38,6 +38,8 @@ _See [build.md](build.md) to build and install the app manually._ ## Run +_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._ + Scrcpy is a command line application: it is mainly intended to be executed from a terminal with command line arguments. From 1a80333747de602b03ca7ae6b8e58790eeefc3dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Mar 2023 10:17:08 +0100 Subject: [PATCH 0825/1133] Replace link to enable USB debugging in README Link to a more relevant page in the official documentation to enable USB debugging. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dfb6686..8b9e4d46 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ The Android device requires at least API 21 (Android 5.0). [Audio forwarding](doc/audio.md) is supported from API 30 (Android 11). -Make sure you [enabled adb debugging][enable-adb] on your device(s). +Make sure you [enabled USB debugging][enable-adb] on your device(s). -[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling +[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable On some devices, you also need to enable [an additional option][control] `USB debugging (Security Settings)` (this is an item different from `USB debugging`) From 2eced46a37e5d73c23f37e031aab4bfcc1f589b0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Mar 2023 19:21:43 +0100 Subject: [PATCH 0826/1133] Update broken link in documentation The Android documentation has been updated. --- doc/device.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/device.md b/doc/device.md index c7e1ec04..b6ae2338 100644 --- a/doc/device.md +++ b/doc/device.md @@ -107,10 +107,10 @@ with the device IP address you found)_. 7. Run `scrcpy` as usual. 8. Run `adb disconnect` once you're done. -Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass +Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass having to physically connect your device directly to your computer. -[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+ +[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line ## Autostart From 337d6c2fd351226ede8b7b2b164ffd3d67a6953f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Mar 2023 19:26:37 +0100 Subject: [PATCH 0827/1133] Fail on empty AudioRecord read() If read() returns 0, then there is no data. According to the documentation, it happens if the buffer is not a direct buffer: Refs #3812 --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 2 +- server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 9228e3d7..6bb3ce23 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -111,7 +111,7 @@ public final class AudioCapture { @TargetApi(Build.VERSION_CODES.N) public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) { int r = recorder.read(directBuffer, size); - if (r < 0) { + if (r <= 0) { return r; } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 24d685c5..d3459831 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -92,7 +92,7 @@ public final class AudioEncoder implements AsyncProcessor { InputTask task = inputTasks.take(); ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); int r = capture.read(buffer, READ_SIZE, bufferInfo); - if (r < 0) { + if (r <= 0) { throw new IOException("Could not read audio: " + r); } From 6ad037ea043debb1fb240912a8a4b639a5e2d96e Mon Sep 17 00:00:00 2001 From: Bernard Cafarelli Date: Tue, 14 Mar 2023 21:51:54 +0100 Subject: [PATCH 0828/1133] Update Gentoo instructions scrcpy is available directly in the distro, drop link to the overlay (which only contains older versions). PR #3816 Signed-off-by: Romain Vimont --- doc/linux.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/linux.md b/doc/linux.md index bc354959..68b4ee10 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -9,12 +9,10 @@ Scrcpy is packaged in several distributions and package managers: - Debian/Ubuntu: `apt install scrcpy` - Arch Linux: `pacman -S scrcpy` - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` - - Gentoo: [ebuild][ebuild-link] file + - Gentoo: `emerge scrcpy` - Snap: `snap install scrcpy` - … (see [repology](https://repology.org/project/scrcpy/versions)) -[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy - ### Latest version However, the packaged version is not always the latest release. To install the From d2b7315ba693e11bd9b9a0421a75b7df8a31c5cc Mon Sep 17 00:00:00 2001 From: Bernard Cafarelli Date: Tue, 14 Mar 2023 21:48:23 +0100 Subject: [PATCH 0829/1133] Fix linux desktop files validation Follow quoting rules from: PR #3817 Fixes #3633 Signed-off-by: Romain Vimont --- app/data/scrcpy-console.desktop | 2 +- app/data/scrcpy.desktop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index 47a63ec9..f9921782 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."' +Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press any key to quit...'" Icon=scrcpy Terminal=true Type=Application diff --git a/app/data/scrcpy.desktop b/app/data/scrcpy.desktop index 082b75e0..1be86a2b 100644 --- a/app/data/scrcpy.desktop +++ b/app/data/scrcpy.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c '"$SHELL" -i -c scrcpy' +Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy" Icon=scrcpy Terminal=false Type=Application From 6ba99a62ff51c0ce209db78a9ed17f1633170027 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Mar 2023 22:51:31 +0100 Subject: [PATCH 0830/1133] Split workarounds to fix audio on some devices There were several workarounds applied in a single method. Some of them are specific to Meizu phones, but cause issues on other devices. Split the method to be able to only fill the app context for audio capture without applying the Meizu workarounds. Fixes #3801 --- .../java/com/genymobile/scrcpy/Server.java | 12 +++---- .../com/genymobile/scrcpy/Workarounds.java | 31 ++++++++++++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5800487d..244913cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -81,15 +81,15 @@ public final class Server { // But only apply when strictly necessary, since workarounds can cause other issues: // - // - - boolean mustFillAppInfo = Build.BRAND.equalsIgnoreCase("meizu"); + if (Build.BRAND.equalsIgnoreCase("meizu")) { + Workarounds.fillAppInfo(); + } // Before Android 11, audio is not supported. // Since Android 12, we can properly set a context on the AudioRecord. - // Only on Android 11 we must fill app info for the AudioRecord to work. - mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R; - - if (mustFillAppInfo) { - Workarounds.fillAppInfo(); + // Only on Android 11 we must fill the application context for the AudioRecord to work. + if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Workarounds.fillAppContext(); } List asyncProcessors = new ArrayList<>(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 64cc1272..89380ece 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -10,6 +10,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; public final class Workarounds { + + private static Class activityThreadClass; + private static Object activityThread; + private Workarounds() { // not instantiable } @@ -28,18 +32,25 @@ public final class Workarounds { } @SuppressLint("PrivateApi,DiscouragedPrivateApi") - public static void fillAppInfo() { - try { + private static void fillActivityThread() throws Exception { + if (activityThread == null) { // ActivityThread activityThread = new ActivityThread(); - Class activityThreadClass = Class.forName("android.app.ActivityThread"); + activityThreadClass = Class.forName("android.app.ActivityThread"); Constructor activityThreadConstructor = activityThreadClass.getDeclaredConstructor(); activityThreadConstructor.setAccessible(true); - Object activityThread = activityThreadConstructor.newInstance(); + activityThread = activityThreadConstructor.newInstance(); // ActivityThread.sCurrentActivityThread = activityThread; Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThreadField.setAccessible(true); sCurrentActivityThreadField.set(null, activityThread); + } + } + + @SuppressLint("PrivateApi,DiscouragedPrivateApi") + public static void fillAppInfo() { + try { + fillActivityThread(); // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); Class appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); @@ -59,6 +70,16 @@ public final class Workarounds { Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); mBoundApplicationField.setAccessible(true); mBoundApplicationField.set(activityThread, appBindData); + } catch (Throwable throwable) { + // this is a workaround, so failing is not an error + Ln.d("Could not fill app info: " + throwable.getMessage()); + } + } + + @SuppressLint("PrivateApi,DiscouragedPrivateApi") + public static void fillAppContext() { + try { + fillActivityThread(); Application app = Application.class.newInstance(); Field baseField = ContextWrapper.class.getDeclaredField("mBase"); @@ -71,7 +92,7 @@ public final class Workarounds { mInitialApplicationField.set(activityThread, app); } catch (Throwable throwable) { // this is a workaround, so failing is not an error - Ln.d("Could not fill app info: " + throwable.getMessage()); + Ln.d("Could not fill app context: " + throwable.getMessage()); } } } From cba2501254209073d5bb09b6d43e973a4013f6b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Mar 2023 23:40:48 +0100 Subject: [PATCH 0831/1133] Add missing auto-completion for --audio-buffer --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + 2 files changed, 2 insertions(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index c3649364..dd9c9520 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -3,6 +3,7 @@ _scrcpy() { local opts=" --always-on-top --audio-bit-rate= + --audio-buffer= --audio-codec= --audio-codec-options= --audio-encoder= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index d713761c..e6a3bc2a 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -10,6 +10,7 @@ local arguments arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' + '--audio-buffer=[Configure the audio buffering delay (in milliseconds)]' '--audio-codec=[Select the audio codec]:codec:(opus aac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]' From 4755b97908bdf8e7d5b9ac1f3557da7ae083f076 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Mar 2023 23:45:08 +0100 Subject: [PATCH 0832/1133] Fix bash auto-completion handling Options having an argument impossible to auto-complete must be handled separately. --- app/data/bash-completion/scrcpy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index dd9c9520..3aa991b8 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -116,20 +116,25 @@ _scrcpy() { COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur})) return ;; - -b|--video-bit-rate \ - |--codec-options \ + --audio-bit-rate \ + |--audio-buffer \ + |-b|--video-bit-rate \ + |--audio-codec-options \ + |--audio-encoder \ |--crop \ |--display \ |--display-buffer \ - |--encoder \ |--max-fps \ |-m|--max-size \ |-p|--port \ |--push-target \ + |--rotation \ |--tunnel-host \ |--tunnel-port \ |--v4l2-buffer \ |--v4l2-sink \ + |--video-codec-options \ + |--video-encoder \ |--tcpip \ |--window-*) # Option accepting an argument, but nothing to auto-complete From 39544f34b4d63a6cdb91802a9c6136b3d6a6003a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Mar 2023 09:23:02 +0100 Subject: [PATCH 0833/1133] Add --audio-output-buffer On some systems, the SDL audio callback is not called frequently enough (for example it requests 5ms of samples every 10ms), because the output buffer is too small. By default, we want to use a small value (5ms) to minimize latency and buffer underrun, but if it does not work well, users need a way to increase it. Refs #3793 --- app/data/bash-completion/scrcpy | 2 ++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 8 ++++++ app/src/audio_player.c | 43 ++++++++++++++++++--------------- app/src/audio_player.h | 7 +++++- app/src/cli.c | 30 +++++++++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 3 ++- doc/audio.md | 11 +++++++++ 10 files changed, 86 insertions(+), 21 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 3aa991b8..ae516c34 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -7,6 +7,7 @@ _scrcpy() { --audio-codec= --audio-codec-options= --audio-encoder= + --audio-output-buffer= -b --video-bit-rate= --crop= -d --select-usb @@ -121,6 +122,7 @@ _scrcpy() { |-b|--video-bit-rate \ |--audio-codec-options \ |--audio-encoder \ + |--audio-output-buffer \ |--crop \ |--display \ |--display-buffer \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index e6a3bc2a..97bf4f3e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -14,6 +14,7 @@ arguments=( '--audio-codec=[Select the audio codec]:codec:(opus aac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]' + '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 65357686..97a15d1d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -33,6 +33,14 @@ Lower values decrease the latency, but increase the likelyhood of buffer underru Default is 50. +.TP +.BI "\-\-audio\-output\-buffer ms +Configure the size of the SDL audio output buffer (in milliseconds). + +If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise. + +Default is 5. + .TP .BI "\-\-audio\-codec " name Select an audio codec (opus, aac or raw). diff --git a/app/src/audio_player.c b/app/src/audio_player.c index bba39acb..a0c52c62 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -59,8 +59,6 @@ #define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT #define SC_SDL_SAMPLE_FMT AUDIO_F32 -#define SC_AUDIO_OUTPUT_BUFFER_MS 5 - #define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES)) #define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES)) @@ -230,8 +228,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, if (played) { uint32_t max_buffered_samples = ap->target_buffering - + 12 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000 - + ap->target_buffering / 10; + + 12 * ap->output_buffer + + ap->target_buffering / 10; if (buffered_samples > max_buffered_samples) { uint32_t skip_samples = buffered_samples - max_buffered_samples; sc_audiobuf_skip(&ap->buf, skip_samples); @@ -246,7 +244,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // max_initial_buffering samples, this would cause unnecessary delay // (and glitches to compensate) on start. uint32_t max_initial_buffering = ap->target_buffering - + 2 * SC_AUDIO_OUTPUT_BUFFER_MS * ap->sample_rate / 1000; + + 2 * ap->output_buffer; if (buffered_samples > max_initial_buffering) { uint32_t skip_samples = buffered_samples - max_initial_buffering; sc_audiobuf_skip(&ap->buf, skip_samples); @@ -333,11 +331,28 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, unsigned nb_channels = tmp; #endif + assert(ctx->sample_rate > 0); + assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT)); + int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT); + assert(out_bytes_per_sample > 0); + + ap->sample_rate = ctx->sample_rate; + ap->nb_channels = nb_channels; + ap->out_bytes_per_sample = out_bytes_per_sample; + + ap->target_buffering = ap->target_buffering_delay * ap->sample_rate + / SC_TICK_FREQ; + + uint64_t aout_samples = ap->output_buffer_duration * ap->sample_rate + / SC_TICK_FREQ; + assert(aout_samples <= 0xFFFF); + ap->output_buffer = (uint16_t) aout_samples; + SDL_AudioSpec desired = { .freq = ctx->sample_rate, .format = SC_SDL_SAMPLE_FMT, .channels = nb_channels, - .samples = SC_AUDIO_OUTPUT_BUFFER_MS * ctx->sample_rate / 1000, + .samples = aout_samples, .callback = sc_audio_player_sdl_callback, .userdata = ap, }; @@ -356,11 +371,6 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, } ap->swr_ctx = swr_ctx; - assert(ctx->sample_rate > 0); - assert(!av_sample_fmt_is_planar(SC_AV_SAMPLE_FMT)); - int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT); - assert(out_bytes_per_sample > 0); - #ifdef SCRCPY_LAVU_HAS_CHLAYOUT av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0); av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0); @@ -383,13 +393,6 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, goto error_free_swr_ctx; } - ap->sample_rate = ctx->sample_rate; - ap->nb_channels = nb_channels; - ap->out_bytes_per_sample = out_bytes_per_sample; - - ap->target_buffering = ap->target_buffering_delay * ap->sample_rate - / SC_TICK_FREQ; - // Use a ring-buffer of the target buffering size plus 1 second between the // producer and the consumer. It's too big on purpose, to guarantee that // the producer and the consumer will be able to access it in parallel @@ -458,8 +461,10 @@ sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) { } void -sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering) { +sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering, + sc_tick output_buffer_duration) { ap->target_buffering_delay = target_buffering; + ap->output_buffer_duration = output_buffer_duration; static const struct sc_frame_sink_ops ops = { .open = sc_audio_player_frame_sink_open, diff --git a/app/src/audio_player.h b/app/src/audio_player.h index 4dd9c4dc..a03e9e35 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -27,6 +27,10 @@ struct sc_audio_player { sc_tick target_buffering_delay; uint32_t target_buffering; // in samples + // SDL audio output buffer size. + sc_tick output_buffer_duration; + uint16_t output_buffer; + // Audio buffer to communicate between the receiver and the SDL audio // callback (protected by SDL_AudioDeviceLock()) struct sc_audiobuf buf; @@ -80,6 +84,7 @@ struct sc_audio_player_callbacks { }; void -sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering); +sc_audio_player_init(struct sc_audio_player *ap, sc_tick target_buffering, + sc_tick audio_output_buffer); #endif diff --git a/app/src/cli.c b/app/src/cli.c index cb101a51..d6d9f41d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -71,6 +71,7 @@ enum { OPT_LIST_DISPLAYS, OPT_REQUIRE_AUDIO, OPT_AUDIO_BUFFER, + OPT_AUDIO_OUTPUT_BUFFER, }; struct sc_option { @@ -129,6 +130,16 @@ static const struct sc_option options[] = { "likelyhood of buffer underrun (causing audio glitches).\n" "Default is 50.", }, + { + .longopt_id = OPT_AUDIO_OUTPUT_BUFFER, + .longopt = "audio-output-buffer", + .argdesc = "ms", + .text = "Configure the size of the SDL audio output buffer (in " + "milliseconds).\n" + "If you get \"robotic\" audio playback, you should test with " + "a higher value (10). Do not change this setting otherwise.\n" + "Default is 5.", + }, { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", @@ -1204,6 +1215,19 @@ parse_buffering_time(const char *s, sc_tick *tick) { return true; } +static bool +parse_audio_output_buffer(const char *s, sc_tick *tick) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 1000, + "audio output buffer"); + if (!ok) { + return false; + } + + *tick = SC_TICK_FROM_MS(value); + return true; +} + static bool parse_lock_video_orientation(const char *s, enum sc_lock_video_orientation *lock_mode) { @@ -1831,6 +1855,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_OUTPUT_BUFFER: + if (!parse_audio_output_buffer(optarg, + &opts->audio_output_buffer)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 68c16d53..8b99f6f3 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -44,6 +44,7 @@ const struct scrcpy_options scrcpy_options_default = { .display_buffer = 0, .v4l2_buffer = 0, .audio_buffer = SC_TICK_FROM_MS(50), + .audio_output_buffer = SC_TICK_FROM_MS(5), #ifdef HAVE_USB .otg = false, #endif diff --git a/app/src/options.h b/app/src/options.h index 06b4ddfa..c41e2757 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -127,6 +127,7 @@ struct scrcpy_options { sc_tick display_buffer; sc_tick v4l2_buffer; sc_tick audio_buffer; + sc_tick audio_output_buffer; #ifdef HAVE_USB bool otg; #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9e5ec6f0..efa69d31 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -688,7 +688,8 @@ aoa_hid_end: sc_frame_source_add_sink(src, &s->screen.frame_sink); if (options->audio) { - sc_audio_player_init(&s->audio_player, options->audio_buffer); + sc_audio_player_init(&s->audio_player, options->audio_buffer, + options->audio_output_buffer); sc_frame_source_add_sink(&s->audio_decoder.frame_source, &s->audio_player.frame_sink); } diff --git a/doc/audio.md b/doc/audio.md index 3755fe37..6e97b103 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -88,3 +88,14 @@ avoid glitches and smooth the playback: ``` scrcpy --display-buffer=200 --audio-buffer=200 ``` + +It is also possible to configure another audio buffer (the audio output buffer), +by default set to 5ms. Don't change it, unless you get some [robotic and glitchy +sound][#3793]: + +```bash +# Only if absolutely necessary +scrcpy --audio-output-buffer=10 +``` + +[#3793]: https://github.com/Genymobile/scrcpy/issues/3793 From 45717733a10b8e73ae70b25722b2ddf5922c1c07 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Mar 2023 00:36:13 +0100 Subject: [PATCH 0834/1133] Document missing Opus encoder error And how to solve it. --- doc/audio.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/audio.md b/doc/audio.md index 3755fe37..c436eb56 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -35,6 +35,13 @@ scrcpy --audio-codec=aac scrcpy --audio-codec=raw ``` +In particular, if you get the following error: + +> Failed to initialize audio/opus, error 0xfffffffe + +then your device has no Opus encoder: try `scrcpy --audio-codec=aac`. + + Several encoders may be available on the device. They can be listed by: ```bash From d9a644df9c060ae844cee0bf451af608e5284c66 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Mar 2023 10:36:07 +0100 Subject: [PATCH 0835/1133] Clarify V4L2 feature in README The previous formulation could suggest that the device camera could be used as a webcam. This is not the case (yet?). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b9e4d46..5bafbe2b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Its features include: - mirroring with [Android device screen off](doc/device.md#turn-screen-off) - [copy-paste](doc/control.md#copy-paste) in both directions - [configurable quality](doc/video.md) - - Android device [as a webcam (V4L2)](doc/v4l2.md) (Linux-only) + - Android device screen [as a webcam (V4L2)](doc/v4l2.md) (Linux-only) - [physical keyboard/mouse simulation (HID)](doc/hid-otg.md) - [OTG mode](doc/hid-otg.md#otg) - and more… From d7841664f4569b24823df1d153665828a3138e05 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Mar 2023 19:53:40 +0100 Subject: [PATCH 0836/1133] Simplify logic in setScreenPowerMode() Refs Suggested-by: brunoais --- server/src/main/java/com/genymobile/scrcpy/Device.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index b66474b7..3d83f73e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -288,10 +288,7 @@ public final class Device { boolean allOk = true; for (long physicalDisplayId : physicalDisplayIds) { IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); - boolean ok = SurfaceControl.setDisplayPowerMode(binder, mode); - if (!ok) { - allOk = false; - } + allOk &= SurfaceControl.setDisplayPowerMode(binder, mode); } return allOk; } From 53cb5635cfbe022061e44a39a3f9f6044b0da5e4 Mon Sep 17 00:00:00 2001 From: sixg0000d Date: Thu, 16 Mar 2023 18:22:53 +0800 Subject: [PATCH 0837/1133] Fix pause message The pause terminates only once the Enter key is pressed, not any key. PR #3826 Signed-off-by: Romain Vimont --- app/data/scrcpy-console.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index f9921782..0e2f9ab0 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press any key to quit...'" +Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press Enter to quit...'" Icon=scrcpy Terminal=true Type=Application From a3871130cc540e1391e6576ae55fd0a5902e4b07 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Mar 2023 20:18:52 +0100 Subject: [PATCH 0838/1133] List available encoders on failure When the creation of an encoder fails, log an explicit error message with the list of available encoders. --- .../com/genymobile/scrcpy/AudioEncoder.java | 17 +++++++++++++---- .../com/genymobile/scrcpy/ScreenEncoder.java | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index d3459831..f2bba772 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -271,13 +271,22 @@ public final class AudioEncoder implements AsyncProcessor { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage()); + Ln.e("Audio encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); + } catch (IOException e) { + Ln.e("Could not create audio encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage()); + throw e; } } - MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); - Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'"); - return mediaCodec; + + try { + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } catch (IOException | IllegalArgumentException e) { + Ln.e("Could not create default audio encoder for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage()); + throw e; + } } private class EncoderCallback extends MediaCodec.Callback { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 015cc993..528cd327 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -202,13 +202,22 @@ public class ScreenEncoder implements Device.RotationListener { try { return MediaCodec.createByCodecName(encoderName); } catch (IllegalArgumentException e) { - Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage()); + Ln.e("Video encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage()); throw new ConfigurationException("Unknown encoder: " + encoderName); + } catch (IOException e) { + Ln.e("Could not create video encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage()); + throw e; } } - MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); - Ln.d("Using encoder: '" + mediaCodec.getName() + "'"); - return mediaCodec; + + try { + MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType()); + Ln.d("Using video encoder: '" + mediaCodec.getName() + "'"); + return mediaCodec; + } catch (IOException | IllegalArgumentException e) { + Ln.e("Could not create default video encoder for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage()); + throw e; + } } private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List codecOptions) { From 02f4ff7534649153d6f87b05a0757431a2d0ee5f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Mar 2023 21:54:06 +0100 Subject: [PATCH 0839/1133] Make 3 attempts to start AudioRecord On Android 11, a fake popup must be briefly opened to make the system think that the shell app is in the foreground so that audio may be recorded. Making the shell app foreground may take some time depending on the device, so make 3 attempts, waiting 100ms before each. Fixes #3796 --- .../com/genymobile/scrcpy/AudioCapture.java | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 6bb3ce23..e15c8285 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -72,8 +72,6 @@ public final class AudioCapture { intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); - // Wait for activity to start - SystemClock.sleep(150); } } } @@ -84,18 +82,35 @@ public final class AudioCapture { } } + private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException { + while (attempts-- > 0) { + // Wait for activity to start + SystemClock.sleep(delayMs); + try { + recorder = createAudioRecord(); + recorder.startRecording(); + return; // it worked + } catch (UnsupportedOperationException e) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + if (attempts == 0) { + Ln.e("Failed to start audio capture"); + Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " + + "scrcpy."); + throw new AudioCaptureForegroundException(); + } else { + Ln.d("Failed to start audio capture, retrying..."); + } + } else { + throw e; + } + } + } + } + public void start() throws AudioCaptureForegroundException { startWorkaroundAndroid11(); try { - recorder = createAudioRecord(); - recorder.startRecording(); - } catch (UnsupportedOperationException e) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - Ln.e("Failed to start audio capture"); - Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy."); - throw new AudioCaptureForegroundException(); - } - throw e; + tryStartRecording(3, 100); } finally { stopWorkaroundAndroid11(); } From 3626d90004c9946320152564a375e56f9c5030f4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Mar 2023 22:19:21 +0100 Subject: [PATCH 0840/1133] Use separate audio capture code for Android 11 The code to start audio capture is more complicated for Android 11 (launch a fake popup, wait, make several attempts, close the shell package). Use a distinct code path specific to Android 11. --- .../com/genymobile/scrcpy/AudioCapture.java | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index e15c8285..c940db16 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -59,27 +59,21 @@ public final class AudioCapture { } private static void startWorkaroundAndroid11() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - // Android 11 requires Apps to be at foreground to record audio. - // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. - // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android - // shell ("com.android.shell"). - // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the - // foreground. - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); - ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); - } - } + // Android 11 requires Apps to be at foreground to record audio. + // Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground. + // But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android + // shell ("com.android.shell"). + // If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the + // foreground. + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); + ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); } private static void stopWorkaroundAndroid11() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); - } + ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME); } private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException { @@ -87,32 +81,36 @@ public final class AudioCapture { // Wait for activity to start SystemClock.sleep(delayMs); try { - recorder = createAudioRecord(); - recorder.startRecording(); + startRecording(); return; // it worked } catch (UnsupportedOperationException e) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - if (attempts == 0) { - Ln.e("Failed to start audio capture"); - Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " - + "scrcpy."); - throw new AudioCaptureForegroundException(); - } else { - Ln.d("Failed to start audio capture, retrying..."); - } + if (attempts == 0) { + Ln.e("Failed to start audio capture"); + Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " + + "scrcpy."); + throw new AudioCaptureForegroundException(); } else { - throw e; + Ln.d("Failed to start audio capture, retrying..."); } } } } + private void startRecording() { + recorder = createAudioRecord(); + recorder.startRecording(); + } + public void start() throws AudioCaptureForegroundException { - startWorkaroundAndroid11(); - try { - tryStartRecording(3, 100); - } finally { - stopWorkaroundAndroid11(); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + startWorkaroundAndroid11(); + try { + tryStartRecording(3, 100); + } finally { + stopWorkaroundAndroid11(); + } + } else { + startRecording(); } } From 55899c091e0b4940516cd811a248bad1a9c8cff4 Mon Sep 17 00:00:00 2001 From: NextDev65 <12612637+NextDev65@users.noreply.github.com> Date: Sun, 19 Mar 2023 21:26:45 -0500 Subject: [PATCH 0841/1133] Fix typo in doc/audio.md The documentation is about audio bit rate, not video bit rate. PR #3839 Signed-off-by: Romain Vimont --- doc/audio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/audio.md b/doc/audio.md index c436eb56..bd324465 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -62,7 +62,7 @@ check `--audio-codec-options` in the manpage or in `scrcpy --help`. ## Bit rate -The default video bit-rate is 128Kbps. To change it: +The default audio bit-rate is 128Kbps. To change it: ```bash scrcpy --audio-bit-rate=64K From 478aece68f2b24def8f9be198a733a106b25616f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Mar 2023 08:35:13 +0100 Subject: [PATCH 0842/1133] Replace "bit-rate" with "bit rate" --- app/scrcpy.1 | 4 ++-- app/src/cli.c | 4 ++-- doc/audio.md | 2 +- doc/video.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 65357686..1ea65796 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -21,7 +21,7 @@ Make scrcpy window always on top (above other windows). .TP .BI "\-\-audio\-bit\-rate " value -Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). +Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Default is 128K (128000). @@ -57,7 +57,7 @@ The available encoders can be listed by \-\-list\-encoders. .TP .BI "\-b, \-\-video\-bit\-rate " value -Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). +Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Default is 8M (8000000). diff --git a/app/src/cli.c b/app/src/cli.c index cb101a51..69559afc 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -116,7 +116,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_AUDIO_BIT_RATE, .longopt = "audio-bit-rate", .argdesc = "value", - .text = "Encode the audio at the given bit-rate, expressed in bits/s. " + .text = "Encode the audio at the given bit rate, expressed in bits/s. " "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 128K (128000).", }, @@ -160,7 +160,7 @@ static const struct sc_option options[] = { .shortopt = 'b', .longopt = "video-bit-rate", .argdesc = "value", - .text = "Encode the video at the given bit-rate, expressed in bits/s. " + .text = "Encode the video at the given bit rate, expressed in bits/s. " "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Default is 8M (8000000).", }, diff --git a/doc/audio.md b/doc/audio.md index bd324465..bf286809 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -62,7 +62,7 @@ check `--audio-codec-options` in the manpage or in `scrcpy --help`. ## Bit rate -The default audio bit-rate is 128Kbps. To change it: +The default audio bit rate is 128Kbps. To change it: ```bash scrcpy --audio-bit-rate=64K diff --git a/doc/video.md b/doc/video.md index a2e9d106..a0dbf7dd 100644 --- a/doc/video.md +++ b/doc/video.md @@ -21,7 +21,7 @@ If encoding fails, scrcpy automatically tries again with a lower definition ## Bit rate -The default video bit-rate is 8 Mbps. To change it: +The default video bit rate is 8 Mbps. To change it: ```bash scrcpy --video-bit-rate=2M From 57f879d68a72c0d84f17d71ddf49c3b564bc614c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Mar 2023 22:06:58 +0100 Subject: [PATCH 0843/1133] Adapt clipboard wrappers to Android 14 A new deviceId parameter has been added. Fixes #3784 --- .../scrcpy/wrappers/ClipboardManager.java | 105 +++++++++++++----- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 0c1777ec..cb176cc3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -16,9 +16,9 @@ public class ClipboardManager { private Method getPrimaryClipMethod; private Method setPrimaryClipMethod; private Method addPrimaryClipChangedListener; - private boolean alternativeGetMethod; - private boolean alternativeSetMethod; - private boolean alternativeAddListenerMethod; + private int getMethodVersion; + private int setMethodVersion; + private int addListenerMethodVersion; public ClipboardManager(IInterface manager) { this.manager = manager; @@ -31,9 +31,15 @@ public class ClipboardManager { } else { try { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class); - } catch (NoSuchMethodException e) { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class); - alternativeGetMethod = true; + getMethodVersion = 0; + } catch (NoSuchMethodException e1) { + try { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class); + getMethodVersion = 1; + } catch (NoSuchMethodException e2) { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); + getMethodVersion = 2; + } } } } @@ -47,41 +53,62 @@ public class ClipboardManager { } else { try { setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class); - } catch (NoSuchMethodException e) { - setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class); - alternativeSetMethod = true; + setMethodVersion = 0; + } catch (NoSuchMethodException e1) { + try { + setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class); + setMethodVersion = 1; + } catch (NoSuchMethodException e2) { + setPrimaryClipMethod = manager.getClass() + .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class); + setMethodVersion = 2; + } } } } return setPrimaryClipMethod; } - private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager) + private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME); } - if (alternativeMethod) { - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); + + switch (methodVersion) { + case 0: + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); + case 1: + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); + default: + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); } - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); } - private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData) + private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, clipData, FakeContext.PACKAGE_NAME); - } else if (alternativeMethod) { - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - } else { - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); + return; + } + + switch (methodVersion) { + case 0: + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); + break; + case 1: + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); + break; + default: + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); + break; } } public CharSequence getText() { try { Method method = getGetPrimaryClipMethod(); - ClipData clipData = getPrimaryClip(method, alternativeGetMethod, manager); + ClipData clipData = getPrimaryClip(method, getMethodVersion, manager); if (clipData == null || clipData.getItemCount() == 0) { return null; } @@ -96,7 +123,7 @@ public class ClipboardManager { try { Method method = getSetPrimaryClipMethod(); ClipData clipData = ClipData.newPlainText(null, text); - setPrimaryClip(method, alternativeSetMethod, manager, clipData); + setPrimaryClip(method, setMethodVersion, manager, clipData); return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); @@ -104,14 +131,23 @@ public class ClipboardManager { } } - private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager, + private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, listener, FakeContext.PACKAGE_NAME); - } else if (alternativeMethod) { - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - } else { - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); + return; + } + + switch (methodVersion) { + case 0: + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); + break; + case 1: + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); + break; + default: + method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); + break; } } @@ -124,10 +160,19 @@ public class ClipboardManager { try { addPrimaryClipChangedListener = manager.getClass() .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); - } catch (NoSuchMethodException e) { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, int.class); - alternativeAddListenerMethod = true; + addListenerMethodVersion = 0; + } catch (NoSuchMethodException e1) { + try { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, + int.class); + addListenerMethodVersion = 1; + } catch (NoSuchMethodException e2) { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, + int.class, int.class); + addListenerMethodVersion = 2; + } } } } @@ -137,7 +182,7 @@ public class ClipboardManager { public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { try { Method method = getAddPrimaryClipChangedListener(); - addPrimaryClipChangedListener(method, alternativeAddListenerMethod, manager, listener); + addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener); return true; } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { Ln.e("Could not invoke method", e); From 2fff9b9edf749dd7a8ccf36fe2df3c3587f535ab Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Mar 2023 22:10:38 +0100 Subject: [PATCH 0844/1133] Adapt FakeContext for Android 14 This fixes audio for Android 14 developer preview 2. Fixes #3784 Suggested-by: Namelesswonder --- server/src/main/java/com/genymobile/scrcpy/FakeContext.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 844d6bd8..738203de 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -38,4 +38,10 @@ public final class FakeContext extends ContextWrapper { builder.setPackageName(PACKAGE_NAME); return builder.build(); } + + // @Override to be added on SDK upgrade for Android 14 + @SuppressWarnings("unused") + public int getDeviceId() { + return 0; + } } From 2d3059e1ab687a18123636784f1e98ac653d3469 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Mar 2023 19:40:35 +0100 Subject: [PATCH 0845/1133] Reference FAQ from HID/OTG documentation Reference the FAQ section about "HID/OTG issues on Windows" from the HID/OTG documentation. --- doc/hid-otg.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/hid-otg.md b/doc/hid-otg.md index c64af752..7dfc60fc 100644 --- a/doc/hid-otg.md +++ b/doc/hid-otg.md @@ -106,3 +106,7 @@ scrcpy --otg # keyboard and mouse Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is connected over USB. + +## HID/OTG issues on Windows + +See [FAQ](/FAQ.md#hidotg-issues-on-windows). From 21df2c240e544b1c1eba7775e1474c1c772be04b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Mar 2023 19:02:14 +0100 Subject: [PATCH 0846/1133] Mention necessary reboot After setting "USB debugging (security settings)", a reboot is necessary. --- FAQ.md | 2 ++ README.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 25bae5d5..484e9c50 100644 --- a/FAQ.md +++ b/FAQ.md @@ -159,6 +159,8 @@ In developer options, enable: > **USB debugging (Security settings)** > _Allow granting permissions and simulating input via USB debugging_ +Rebooting the device is necessary once this option is set. + [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 diff --git a/README.md b/README.md index 5bafbe2b..ddcc565a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s). On some devices, you also need to enable [an additional option][control] `USB debugging (Security Settings)` (this is an item different from `USB debugging`) -to control it using a keyboard and mouse. +to control it using a keyboard and mouse. Rebooting the device is necessary once +this option is set. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 From 0ebb3df69cb5e7c977c12a7be31abde5511e4d13 Mon Sep 17 00:00:00 2001 From: Yan Date: Mon, 27 Mar 2023 14:59:09 +0200 Subject: [PATCH 0847/1133] Fix debug build by adding compat.c to tests Linking of tests that needed something from compat.c failed. PR #3865 Signed-off-by: Romain Vimont --- app/meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/meson.build b/app/meson.build index 723274c9..b1cfb2b6 100644 --- a/app/meson.build +++ b/app/meson.build @@ -310,7 +310,8 @@ if get_option('buildtype') == 'debug' ] foreach t : tests - exe = executable(t[0], t[1], + sources = t[1] + ['src/compat.c'] + exe = executable(t[0], sources, include_directories: src_dir, dependencies: dependencies, c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST']) From 00534b0b2d06a2b31a94e2bbbfe35961a9b8879a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Mar 2023 08:30:43 +0200 Subject: [PATCH 0848/1133] Fix typo in FAQ --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 484e9c50..30155808 100644 --- a/FAQ.md +++ b/FAQ.md @@ -231,4 +231,4 @@ Translations of this FAQ in other languages are available in the [wiki]. [wiki]: https://github.com/Genymobile/scrcpy/wiki -Only this README file is guaranteed to be up-to-date. +Only this FAQ file is guaranteed to be up-to-date. From a1e8a340016875cfea4b42b326026889d684f221 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 28 Mar 2023 08:29:41 +0200 Subject: [PATCH 0849/1133] Fix documentation link in FAQ --- FAQ.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FAQ.md b/FAQ.md index 30155808..a6eaeefa 100644 --- a/FAQ.md +++ b/FAQ.md @@ -170,12 +170,12 @@ 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, it is possible to simulate a [physical keyboard][hid] (HID). +It is also possible to simulate a [physical keyboard][hid] (HID). [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 +[hid]: doc/hid-otg.md ## Client issues From 2f9396e24a5beb76f8754bca37a858aa95536abc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 27 Mar 2023 02:12:59 +0200 Subject: [PATCH 0850/1133] Simplify clock estimation The slope encodes the drift between the device clock and the computer clock. Its real value is expected very close to 1. To estimate it, just assume it is exactly 1. Since the clock is used to estimate very close points in the future, the error caused by clock drift is totally negligible, and in practice it is way lower than the slope estimation error. Therefore, only estimate the offset. --- app/meson.build | 4 -- app/src/clock.c | 108 ++++++----------------------------------- app/src/clock.h | 43 +++------------- app/src/delay_buffer.c | 2 +- app/tests/test_clock.c | 79 ------------------------------ 5 files changed, 23 insertions(+), 213 deletions(-) delete mode 100644 app/tests/test_clock.c diff --git a/app/meson.build b/app/meson.build index b1cfb2b6..05c463e6 100644 --- a/app/meson.build +++ b/app/meson.build @@ -277,10 +277,6 @@ if get_option('buildtype') == 'debug' 'src/util/strbuf.c', 'src/util/term.c', ]], - ['test_clock', [ - 'tests/test_clock.c', - 'src/clock.c', - ]], ['test_control_msg_serialize', [ 'tests/test_control_msg_serialize.c', 'src/control_msg.c', diff --git a/app/src/clock.c b/app/src/clock.c index 3e1a794d..92989bfe 100644 --- a/app/src/clock.c +++ b/app/src/clock.c @@ -1,116 +1,36 @@ #include "clock.h" +#include + #include "util/log.h" #define SC_CLOCK_NDEBUG // comment to debug +#define SC_CLOCK_RANGE 32 + void sc_clock_init(struct sc_clock *clock) { - clock->count = 0; - clock->head = 0; - clock->left_sum.system = 0; - clock->left_sum.stream = 0; - clock->right_sum.system = 0; - clock->right_sum.stream = 0; -} - -// Estimate the affine function f(stream) = slope * stream + offset -static void -sc_clock_estimate(struct sc_clock *clock, - double *out_slope, sc_tick *out_offset) { - assert(clock->count); - - if (clock->count == 1) { - // If there is only 1 point, we can't compute a slope. Assume it is 1. - struct sc_clock_point *single_point = &clock->right_sum; - *out_slope = 1; - *out_offset = single_point->system - single_point->stream; - return; - } - - struct sc_clock_point left_avg = { - .system = clock->left_sum.system / (clock->count / 2), - .stream = clock->left_sum.stream / (clock->count / 2), - }; - struct sc_clock_point right_avg = { - .system = clock->right_sum.system / ((clock->count + 1) / 2), - .stream = clock->right_sum.stream / ((clock->count + 1) / 2), - }; - - double slope = (double) (right_avg.system - left_avg.system) - / (right_avg.stream - left_avg.stream); - - if (clock->count < SC_CLOCK_RANGE) { - /* The first frames are typically received and decoded with more delay - * than the others, causing a wrong slope estimation on start. To - * compensate, assume an initial slope of 1, then progressively use the - * estimated slope. */ - slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count)) - / SC_CLOCK_RANGE; - } - - struct sc_clock_point global_avg = { - .system = (clock->left_sum.system + clock->right_sum.system) - / clock->count, - .stream = (clock->left_sum.stream + clock->right_sum.stream) - / clock->count, - }; - - sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope); - - *out_slope = slope; - *out_offset = offset; + clock->range = 0; + clock->offset = 0; } void sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) { - struct sc_clock_point *point = &clock->points[clock->head]; - - if (clock->count == SC_CLOCK_RANGE || clock->count & 1) { - // One point passes from the right sum to the left sum - - unsigned mid; - if (clock->count == SC_CLOCK_RANGE) { - mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE; - } else { - // Only for the first frames - mid = clock->count / 2; - } - - struct sc_clock_point *mid_point = &clock->points[mid]; - clock->left_sum.system += mid_point->system; - clock->left_sum.stream += mid_point->stream; - clock->right_sum.system -= mid_point->system; - clock->right_sum.stream -= mid_point->stream; + if (clock->range < SC_CLOCK_RANGE) { + ++clock->range; } - if (clock->count == SC_CLOCK_RANGE) { - // The current point overwrites the previous value in the circular - // array, update the left sum accordingly - clock->left_sum.system -= point->system; - clock->left_sum.stream -= point->stream; - } else { - ++clock->count; - } - - point->system = system; - point->stream = stream; - - clock->right_sum.system += system; - clock->right_sum.stream += stream; - - clock->head = (clock->head + 1) % SC_CLOCK_RANGE; - - // Update estimation - sc_clock_estimate(clock, &clock->slope, &clock->offset); + sc_tick offset = system - stream; + clock->offset = ((clock->range - 1) * clock->offset + offset) + / clock->range; #ifndef SC_CLOCK_NDEBUG - LOGD("Clock estimation: %f * pts + %" PRItick, clock->slope, clock->offset); + LOGD("Clock estimation: pts + %" PRItick, clock->offset); #endif } sc_tick sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) { - assert(clock->count); // sc_clock_update() must have been called - return (sc_tick) (stream * clock->slope) + clock->offset; + assert(clock->range); // sc_clock_update() must have been called + return stream + clock->offset; } diff --git a/app/src/clock.h b/app/src/clock.h index 886d1f4d..0d34ab99 100644 --- a/app/src/clock.h +++ b/app/src/clock.h @@ -3,13 +3,8 @@ #include "common.h" -#include - #include "util/tick.h" -#define SC_CLOCK_RANGE 32 -static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even"); - struct sc_clock_point { sc_tick system; sc_tick stream; @@ -21,40 +16,18 @@ struct sc_clock_point { * * f(stream) = slope * stream + offset * - * To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps - * of a frame expressed both in stream time and system time) in a circular - * array. + * Theoretically, the slope encodes the drift between the device clock and the + * computer clock. It is expected to be very close to 1. * - * 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 - * point"). The slope of the estimated affine function is that of the line - * passing through these two points. + * Since the clock is used to estimate very close points in the future (which + * are reestimated on every clock update, see delay_buffer), the error caused + * by clock drift is totally negligible, so it is better to assume that the + * slope is 1 than to estimate it (the estimation error would be larger). * - * To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE - * points. The resulting affine function passes by this centroid. - * - * With a circular array, the rolling sums (and average) are quick to compute. - * In practice, the estimation is stable and the evolution is smooth. + * Therefore, only the offset is estimated. */ struct sc_clock { - // Circular array - struct sc_clock_point points[SC_CLOCK_RANGE]; - - // Number of points in the array (count <= SC_CLOCK_RANGE) - unsigned count; - - // Index of the next point to write - unsigned head; - - // Sum of the first count/2 points - struct sc_clock_point left_sum; - - // Sum of the last (count+1)/2 points - struct sc_clock_point right_sum; - - // Estimated slope and offset - // (computed on sc_clock_update(), used by sc_clock_to_system_time()) - double slope; + unsigned range; sc_tick offset; }; diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 9d4690a2..f6141b35 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -194,7 +194,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, sc_clock_update(&db->clock, sc_tick_now(), pts); sc_cond_signal(&db->wait_cond); - if (db->first_frame_asap && db->clock.count == 1) { + if (db->first_frame_asap && db->clock.range == 1) { sc_mutex_unlock(&db->mutex); return sc_frame_source_sinks_push(&db->frame_source, frame); } diff --git a/app/tests/test_clock.c b/app/tests/test_clock.c deleted file mode 100644 index a88d5800..00000000 --- a/app/tests/test_clock.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "common.h" - -#include - -#include "clock.h" - -void test_small_rolling_sum(void) { - struct sc_clock clock; - sc_clock_init(&clock); - - assert(clock.count == 0); - assert(clock.left_sum.system == 0); - assert(clock.left_sum.stream == 0); - assert(clock.right_sum.system == 0); - assert(clock.right_sum.stream == 0); - - sc_clock_update(&clock, 2, 3); - assert(clock.count == 1); - assert(clock.left_sum.system == 0); - assert(clock.left_sum.stream == 0); - assert(clock.right_sum.system == 2); - assert(clock.right_sum.stream == 3); - - sc_clock_update(&clock, 10, 20); - assert(clock.count == 2); - assert(clock.left_sum.system == 2); - assert(clock.left_sum.stream == 3); - assert(clock.right_sum.system == 10); - assert(clock.right_sum.stream == 20); - - sc_clock_update(&clock, 40, 80); - assert(clock.count == 3); - assert(clock.left_sum.system == 2); - assert(clock.left_sum.stream == 3); - assert(clock.right_sum.system == 50); - assert(clock.right_sum.stream == 100); - - sc_clock_update(&clock, 400, 800); - assert(clock.count == 4); - assert(clock.left_sum.system == 12); - assert(clock.left_sum.stream == 23); - assert(clock.right_sum.system == 440); - assert(clock.right_sum.stream == 880); -} - -void test_large_rolling_sum(void) { - const unsigned half_range = SC_CLOCK_RANGE / 2; - - struct sc_clock clock1; - sc_clock_init(&clock1); - for (unsigned i = 0; i < 5 * half_range; ++i) { - sc_clock_update(&clock1, i, 2 * i + 1); - } - - struct sc_clock clock2; - sc_clock_init(&clock2); - for (unsigned i = 3 * half_range; i < 5 * half_range; ++i) { - sc_clock_update(&clock2, i, 2 * i + 1); - } - - assert(clock1.count == SC_CLOCK_RANGE); - assert(clock2.count == SC_CLOCK_RANGE); - - // The values before the last SC_CLOCK_RANGE points in clock1 should have - // no impact - assert(clock1.left_sum.system == clock2.left_sum.system); - assert(clock1.left_sum.stream == clock2.left_sum.stream); - assert(clock1.right_sum.system == clock2.right_sum.system); - assert(clock1.right_sum.stream == clock2.right_sum.stream); -} - -int main(int argc, char *argv[]) { - (void) argc; - (void) argv; - - test_small_rolling_sum(); - test_large_rolling_sum(); - return 0; -}; From 8f0b38cc4f503ea16dd57c705ae0ede81efc8630 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Mar 2023 07:51:34 +0200 Subject: [PATCH 0851/1133] Specify in README that OTG does not require adb --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ddcc565a..9969dc51 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,9 @@ this option is set. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 +Note that USB debugging is not required to run scrcpy in [OTG +mode](doc/hid-otg.md#otg). + ## Get the app From f77e1c474edfafcd46f692452413f35a18c70949 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Apr 2023 11:50:21 +0200 Subject: [PATCH 0852/1133] Fix copy-paste for some devices On Honor Magic 5 Pro, the method to get the clipboard content has been modified in the framework. Adapt the call to make it work also on this device. Fixes #3885 --- .../scrcpy/wrappers/ClipboardManager.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index cb176cc3..7b750975 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -37,8 +37,13 @@ public class ClipboardManager { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class); getMethodVersion = 1; } catch (NoSuchMethodException e2) { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); - getMethodVersion = 2; + try { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); + getMethodVersion = 2; + } catch (NoSuchMethodException e3) { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); + getMethodVersion = 3; + } } } } @@ -80,8 +85,10 @@ public class ClipboardManager { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); case 1: return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - default: + case 2: return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); + default: + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null); } } From 669e9a8d1ecf3bc94195c6d57c0fec59c51a8367 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 2 Apr 2023 17:45:46 +0200 Subject: [PATCH 0853/1133] Fix "ip route" parsing If a line did not end with '\r', then the final `\n' was replaced by '\0' for parsing the current line. This `\0` was then mistakenly considered as the end of the whole "ip route" output, so the remaining lines were not parsed, causing "scrcpy --tcpip" to fail in some cases. To fix the issue, read the final character of the current line before it is (possibly) overwritten by '\0'. --- app/src/adb/adb_parser.c | 11 ++++++----- app/tests/test_adb_parser.c | 13 +++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index ab121347..e7358403 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -204,6 +204,7 @@ sc_adb_parse_device_ip(char *str) { while (str[idx_line] != '\0') { char *line = &str[idx_line]; size_t len = strcspn(line, "\n"); + bool is_last_line = line[len] == '\0'; // The same, but without any trailing '\r' size_t line_len = sc_str_remove_trailing_cr(line, len); @@ -215,12 +216,12 @@ sc_adb_parse_device_ip(char *str) { return ip; } - idx_line += len; - - if (str[idx_line] != '\0') { - // The next line starts after the '\n' - ++idx_line; + if (is_last_line) { + break; } + + // The next line starts after the '\n' + idx_line += len + 1; } return NULL; diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index d95e7ef2..362b254f 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -217,6 +217,18 @@ static void test_get_ip_multiline_second_ok(void) { free(ip); } +static void test_get_ip_multiline_second_ok_without_cr(void) { + char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src " + "10.0.0.3\n" + "192.168.1.0/24 dev wlan0 proto kernel scope link src " + "192.168.1.3\n"; + + char *ip = sc_adb_parse_device_ip(ip_route); + assert(ip); + assert(!strcmp(ip, "192.168.1.3")); + free(ip); +} + static void test_get_ip_no_wlan(void) { char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " "192.168.12.34\r\r\n"; @@ -259,6 +271,7 @@ int main(int argc, char *argv[]) { test_get_ip_single_line_with_trailing_space(); test_get_ip_multiline_first_ok(); test_get_ip_multiline_second_ok(); + test_get_ip_multiline_second_ok_without_cr(); test_get_ip_no_wlan(); test_get_ip_no_wlan_without_eol(); test_get_ip_truncated(); From fdf465851c786f153255d044c621c1ac4766e5fd Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 4 Apr 2023 22:33:39 +0800 Subject: [PATCH 0854/1133] Add Android version check in raw audio recorder Do not attempt to capture audio below Android 11, this may cause a segfault on the device. PR #3889 Signed-off-by: Romain Vimont --- .../main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 4b1b5bd0..f98440de 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy; import android.media.MediaCodec; +import android.os.Build; import java.io.IOException; import java.nio.ByteBuffer; @@ -19,6 +20,12 @@ public final class AudioRawRecorder implements AsyncProcessor { } private void record() throws IOException, AudioCaptureForegroundException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + Ln.w("Audio disabled: it is not supported before Android 11"); + streamer.writeDisableStream(false); + return; + } + final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); From 2e532afd2baf63096a156251220044409be42802 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 3 Apr 2023 21:41:54 +0200 Subject: [PATCH 0855/1133] Pass const pointers to events SDL_Events are only read. --- app/src/input_manager.c | 3 ++- app/src/input_manager.h | 3 ++- app/src/screen.c | 2 +- app/src/screen.h | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c8098ee7..c9e83d48 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -797,7 +797,8 @@ sc_input_manager_process_file(struct sc_input_manager *im, } void -sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) { +sc_input_manager_handle_event(struct sc_input_manager *im, + const SDL_Event *event) { bool control = im->controller; switch (event->type) { case SDL_TEXTINPUT: diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 46b1160e..b5a762eb 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -61,6 +61,7 @@ sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params); void -sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event); +sc_input_manager_handle_event(struct sc_input_manager *im, + const SDL_Event *event); #endif diff --git a/app/src/screen.c b/app/src/screen.c index b00b0d05..65a4047d 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -812,7 +812,7 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) { } bool -sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) { +sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { bool relative_mode = sc_screen_is_relative_mode(screen); switch (event->type) { diff --git a/app/src/screen.h b/app/src/screen.h index 4fca04d8..ffb896a7 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -137,7 +137,7 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); // react to SDL events // If this function returns false, scrcpy must exit with an error. bool -sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event); +sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event); // convert point from window coordinates to frame coordinates // x and y are expressed in pixels From 051b74c88338702508d98bd532057b343e0a177c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 31 Mar 2023 20:20:27 +0200 Subject: [PATCH 0856/1133] Extract sc_display from sc_screen Move the display code to a separate component. --- app/meson.build | 1 + app/src/display.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++ app/src/display.h | 37 +++++++++++ app/src/screen.c | 160 +++++++------------------------------------- app/src/screen.h | 6 +- 5 files changed, 231 insertions(+), 139 deletions(-) create mode 100644 app/src/display.c create mode 100644 app/src/display.h diff --git a/app/meson.build b/app/meson.build index 05c463e6..061fdcab 100644 --- a/app/meson.build +++ b/app/meson.build @@ -14,6 +14,7 @@ src = [ 'src/delay_buffer.c', 'src/demuxer.c', 'src/device_msg.c', + 'src/display.c', 'src/icon.c', 'src/file_pusher.c', 'src/fps_counter.c', diff --git a/app/src/display.c b/app/src/display.c new file mode 100644 index 00000000..96ff9e06 --- /dev/null +++ b/app/src/display.c @@ -0,0 +1,166 @@ +#include "display.h" + +#include + +#include "util/log.h" + +bool +sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { + display->renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (!display->renderer) { + LOGE("Could not create renderer: %s", SDL_GetError()); + return false; + } + + SDL_RendererInfo renderer_info; + int r = SDL_GetRendererInfo(display->renderer, &renderer_info); + const char *renderer_name = r ? NULL : renderer_info.name; + LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); + + display->mipmaps = false; + + // starts with "opengl" + bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); + if (use_opengl) { + struct sc_opengl *gl = &display->gl; + sc_opengl_init(gl); + + LOGI("OpenGL version: %s", gl->version); + + if (mipmaps) { + bool supports_mipmaps = + sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ + 2, 0 /* OpenGL ES 2.0+ */); + if (supports_mipmaps) { + LOGI("Trilinear filtering enabled"); + display->mipmaps = true; + } else { + LOGW("Trilinear filtering disabled " + "(OpenGL 3.0+ or ES 2.0+ required"); + } + } else { + LOGI("Trilinear filtering disabled"); + } + } else if (mipmaps) { + LOGD("Trilinear filtering disabled (not an OpenGL renderer"); + } + + return true; +} + +void +sc_display_destroy(struct sc_display *display) { + if (display->texture) { + SDL_DestroyTexture(display->texture); + } + SDL_DestroyRenderer(display->renderer); +} + +static SDL_Texture * +sc_display_create_texture(struct sc_display *display, + struct sc_size size) { + SDL_Renderer *renderer = display->renderer; + SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, + SDL_TEXTUREACCESS_STREAMING, + size.width, size.height); + if (!texture) { + LOGE("Could not create texture: %s", SDL_GetError()); + return NULL; + } + + if (display->mipmaps) { + struct sc_opengl *gl = &display->gl; + + SDL_GL_BindTexture(texture, NULL, NULL); + + // Enable trilinear filtering for downscaling + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); + gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f); + + SDL_GL_UnbindTexture(texture); + } + + return texture; +} + +bool +sc_display_set_texture_size(struct sc_display *display, struct sc_size size) { + if (display->texture) { + SDL_DestroyTexture(display->texture); + } + + display->texture = sc_display_create_texture(display, size); + if (!display->texture) { + return false; + } + + LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height); + return true; +} + +bool +sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { + int ret = SDL_UpdateYUVTexture(display->texture, NULL, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); + if (ret) { + LOGE("Could not update texture: %s", SDL_GetError()); + return false; + } + + if (display->mipmaps) { + SDL_GL_BindTexture(display->texture, NULL, NULL); + display->gl.GenerateMipmap(GL_TEXTURE_2D); + SDL_GL_UnbindTexture(display->texture); + } + + return true; +} + +bool +sc_display_render(struct sc_display *display, const SDL_Rect *geometry, + unsigned rotation) { + SDL_RenderClear(display->renderer); + + SDL_Renderer *renderer = display->renderer; + SDL_Texture *texture = display->texture; + + if (rotation == 0) { + int ret = SDL_RenderCopy(renderer, texture, NULL, geometry); + if (ret) { + LOGE("Could not render texture: %s", SDL_GetError()); + return false; + } + } else { + // rotation in RenderCopyEx() is clockwise, while screen->rotation is + // counterclockwise (to be consistent with --lock-video-orientation) + int cw_rotation = (4 - rotation) % 4; + double angle = 90 * cw_rotation; + + const SDL_Rect *dstrect = NULL; + SDL_Rect rect; + if (rotation & 1) { + rect.x = geometry->x + (geometry->w - geometry->h) / 2; + rect.y = geometry->y + (geometry->h - geometry->w) / 2; + rect.w = geometry->h; + rect.h = geometry->w; + dstrect = ▭ + } else { + assert(rotation == 2); + dstrect = geometry; + } + + int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle, + NULL, 0); + if (ret) { + LOGE("Could not render texture: %s", SDL_GetError()); + return false; + } + } + + SDL_RenderPresent(display->renderer); + return true; +} diff --git a/app/src/display.h b/app/src/display.h new file mode 100644 index 00000000..8856afcd --- /dev/null +++ b/app/src/display.h @@ -0,0 +1,37 @@ +#ifndef SC_DISPLAY_H +#define SC_DISPLAY_H + +#include "common.h" + +#include +#include +#include + +#include "coords.h" +#include "opengl.h" + +struct sc_display { + SDL_Renderer *renderer; + SDL_Texture *texture; + + struct sc_opengl gl; + bool mipmaps; +}; + +bool +sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps); + +void +sc_display_destroy(struct sc_display *display); + +bool +sc_display_set_texture_size(struct sc_display *display, struct sc_size size); + +bool +sc_display_update_texture(struct sc_display *display, const AVFrame *frame); + +bool +sc_display_render(struct sc_display *display, const SDL_Rect *geometry, + unsigned rotation); + +#endif diff --git a/app/src/screen.c b/app/src/screen.c index 65a4047d..70665ed6 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -239,35 +239,6 @@ sc_screen_update_content_rect(struct sc_screen *screen) { } } -static bool -create_texture(struct sc_screen *screen) { - SDL_Renderer *renderer = screen->renderer; - struct sc_size size = screen->frame_size; - SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, - SDL_TEXTUREACCESS_STREAMING, - size.width, size.height); - if (!texture) { - LOGE("Could not create texture: %s", SDL_GetError()); - return false; - } - - if (screen->mipmaps) { - struct sc_opengl *gl = &screen->gl; - - SDL_GL_BindTexture(texture, NULL, NULL); - - // Enable trilinear filtering for downscaling - gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_LINEAR_MIPMAP_LINEAR); - gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f); - - SDL_GL_UnbindTexture(texture); - } - - screen->texture = texture; - return true; -} - // render the texture to the renderer // // Set the update_content_rect flag if the window or content size may have @@ -278,35 +249,11 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { sc_screen_update_content_rect(screen); } - SDL_RenderClear(screen->renderer); - if (screen->rotation == 0) { - SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); - } else { - // rotation in RenderCopyEx() is clockwise, while screen->rotation is - // counterclockwise (to be consistent with --lock-video-orientation) - int cw_rotation = (4 - screen->rotation) % 4; - double angle = 90 * cw_rotation; - - SDL_Rect *dstrect = NULL; - SDL_Rect rect; - if (screen->rotation & 1) { - rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2; - rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2; - rect.w = screen->rect.h; - rect.h = screen->rect.w; - dstrect = ▭ - } else { - assert(screen->rotation == 2); - dstrect = &screen->rect; - } - - SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, - angle, NULL, 0); - } - SDL_RenderPresent(screen->renderer); + bool ok = sc_display_render(&screen->display, &screen->rect, + screen->rotation); + (void) ok; // error already logged } - #if defined(__APPLE__) || defined(__WINDOWS__) # define CONTINUOUS_RESIZING_WORKAROUND #endif @@ -453,46 +400,11 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_fps_counter; } - screen->renderer = SDL_CreateRenderer(screen->window, -1, - SDL_RENDERER_ACCELERATED); - if (!screen->renderer) { - LOGE("Could not create renderer: %s", SDL_GetError()); + ok = sc_display_init(&screen->display, screen->window, params->mipmaps); + if (!ok) { goto error_destroy_window; } - SDL_RendererInfo renderer_info; - int r = SDL_GetRendererInfo(screen->renderer, &renderer_info); - const char *renderer_name = r ? NULL : renderer_info.name; - LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); - - screen->mipmaps = false; - - // starts with "opengl" - bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); - if (use_opengl) { - struct sc_opengl *gl = &screen->gl; - sc_opengl_init(gl); - - LOGI("OpenGL version: %s", gl->version); - - if (params->mipmaps) { - bool supports_mipmaps = - sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ - 2, 0 /* OpenGL ES 2.0+ */); - if (supports_mipmaps) { - LOGI("Trilinear filtering enabled"); - screen->mipmaps = true; - } else { - LOGW("Trilinear filtering disabled " - "(OpenGL 3.0+ or ES 2.0+ required)"); - } - } else { - LOGI("Trilinear filtering disabled"); - } - } else if (params->mipmaps) { - LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); - } - SDL_Surface *icon = scrcpy_icon_load(); if (icon) { SDL_SetWindowIcon(screen->window, icon); @@ -504,7 +416,7 @@ sc_screen_init(struct sc_screen *screen, screen->frame = av_frame_alloc(); if (!screen->frame) { LOG_OOM(); - goto error_destroy_renderer; + goto error_destroy_display; } struct sc_input_manager_params im_params = { @@ -539,8 +451,8 @@ sc_screen_init(struct sc_screen *screen, return true; -error_destroy_renderer: - SDL_DestroyRenderer(screen->renderer); +error_destroy_display: + sc_display_destroy(&screen->display); error_destroy_window: SDL_DestroyWindow(screen->window); error_destroy_fps_counter: @@ -596,11 +508,8 @@ sc_screen_destroy(struct sc_screen *screen) { #ifndef NDEBUG assert(!screen->open); #endif + sc_display_destroy(&screen->display); av_frame_free(&screen->frame); - if (screen->texture) { - SDL_DestroyTexture(screen->texture); - } - SDL_DestroyRenderer(screen->renderer); SDL_DestroyWindow(screen->window); sc_fps_counter_destroy(&screen->fps_counter); sc_frame_buffer_destroy(&screen->fb); @@ -667,7 +576,6 @@ static bool sc_screen_init_size(struct sc_screen *screen) { // Before first frame assert(!screen->has_frame); - assert(!screen->texture); // The requested size is passed via screen->frame_size @@ -675,48 +583,27 @@ sc_screen_init_size(struct sc_screen *screen) { get_rotated_size(screen->frame_size, screen->rotation); screen->content_size = content_size; - LOGI("Initial texture: %" PRIu16 "x%" PRIu16, - screen->frame_size.width, screen->frame_size.height); - return create_texture(screen); + return sc_display_set_texture_size(&screen->display, screen->frame_size); } // recreate the texture and resize the window if the frame size has changed static bool prepare_for_frame(struct sc_screen *screen, struct sc_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 - SDL_DestroyTexture(screen->texture); - - screen->frame_size = new_frame_size; - - struct sc_size new_content_size = - get_rotated_size(new_frame_size, screen->rotation); - set_content_size(screen, new_content_size); - - sc_screen_update_content_rect(screen); - - LOGI("New texture: %" PRIu16 "x%" PRIu16, - screen->frame_size.width, screen->frame_size.height); - return create_texture(screen); + if (screen->frame_size.width == new_frame_size.width + && screen->frame_size.height == new_frame_size.height) { + return true; } - return true; -} + // frame dimension changed + screen->frame_size = new_frame_size; -// write the frame into the texture -static void -update_texture(struct sc_screen *screen, const AVFrame *frame) { - SDL_UpdateYUVTexture(screen->texture, NULL, - frame->data[0], frame->linesize[0], - frame->data[1], frame->linesize[1], - frame->data[2], frame->linesize[2]); + struct sc_size new_content_size = + get_rotated_size(new_frame_size, screen->rotation); + set_content_size(screen, new_content_size); - if (screen->mipmaps) { - SDL_GL_BindTexture(screen->texture, NULL, NULL); - screen->gl.GenerateMipmap(GL_TEXTURE_2D); - SDL_GL_UnbindTexture(screen->texture); - } + sc_screen_update_content_rect(screen); + + return sc_display_set_texture_size(&screen->display, screen->frame_size); } static bool @@ -731,7 +618,10 @@ sc_screen_update_frame(struct sc_screen *screen) { if (!prepare_for_frame(screen, new_frame_size)) { return false; } - update_texture(screen, frame); + + if (!sc_display_update_texture(&screen->display, frame)) { + return false; + } if (!screen->has_frame) { screen->has_frame = true; diff --git a/app/src/screen.h b/app/src/screen.h index ffb896a7..2c032119 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -9,6 +9,7 @@ #include "controller.h" #include "coords.h" +#include "display.h" #include "fps_counter.h" #include "frame_buffer.h" #include "input_manager.h" @@ -24,6 +25,7 @@ struct sc_screen { bool open; // track the open/close state to assert correct behavior #endif + struct sc_display display; struct sc_input_manager im; struct sc_frame_buffer fb; struct sc_fps_counter fps_counter; @@ -39,9 +41,6 @@ struct sc_screen { } req; SDL_Window *window; - SDL_Renderer *renderer; - SDL_Texture *texture; - struct sc_opengl gl; struct sc_size frame_size; struct sc_size content_size; // rotated frame_size @@ -57,7 +56,6 @@ struct sc_screen { bool has_frame; bool fullscreen; bool maximized; - bool mipmaps; // To enable/disable mouse capture, a mouse capture key (LALT, LGUI or // RGUI) must be pressed. This variable tracks the pressed capture key. From afcdfc7fd7440782a9924cd8ccb8a40933b4ba90 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 20:01:58 +0200 Subject: [PATCH 0857/1133] Fix checkstyle violation Checkstyle reported this error: [ant:checkstyle] [ERROR] AudioCapture.java:89:145: '+' should be on a new line. [OperatorWrap] --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index c940db16..dbb38dd2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -86,8 +86,8 @@ public final class AudioCapture { } catch (UnsupportedOperationException e) { if (attempts == 0) { Ln.e("Failed to start audio capture"); - Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " + - "scrcpy."); + Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " + + "scrcpy."); throw new AudioCaptureForegroundException(); } else { Ln.d("Failed to start audio capture, retrying..."); From ce064fb5e00f6999d9fe9f66e932d6dee71dfe7d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 19:52:01 +0200 Subject: [PATCH 0858/1133] Move options parsing to Options class --- .../java/com/genymobile/scrcpy/Options.java | 192 +++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 195 +----------------- 2 files changed, 193 insertions(+), 194 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 2a3de757..0ebb790f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy; import android.graphics.Rect; import java.util.List; +import java.util.Locale; public class Options { @@ -289,4 +290,195 @@ public class Options { public void setSendCodecMeta(boolean sendCodecMeta) { this.sendCodecMeta = sendCodecMeta; } + + @SuppressWarnings("MethodLength") + public static Options parse(String... args) { + if (args.length < 1) { + throw new IllegalArgumentException("Missing client version"); + } + + String clientVersion = args[0]; + if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { + throw new IllegalArgumentException( + "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); + } + + Options options = new Options(); + + for (int i = 1; i < args.length; ++i) { + String arg = args[i]; + int equalIndex = arg.indexOf('='); + if (equalIndex == -1) { + throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\""); + } + String key = arg.substring(0, equalIndex); + String value = arg.substring(equalIndex + 1); + switch (key) { + case "scid": + int scid = Integer.parseInt(value, 0x10); + if (scid < -1) { + throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid); + } + options.setScid(scid); + break; + case "log_level": + Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); + options.setLogLevel(level); + break; + case "audio": + boolean audio = Boolean.parseBoolean(value); + options.setAudio(audio); + break; + case "video_codec": + VideoCodec videoCodec = VideoCodec.findByName(value); + if (videoCodec == null) { + throw new IllegalArgumentException("Video codec " + value + " not supported"); + } + options.setVideoCodec(videoCodec); + break; + case "audio_codec": + AudioCodec audioCodec = AudioCodec.findByName(value); + if (audioCodec == null) { + throw new IllegalArgumentException("Audio codec " + value + " not supported"); + } + options.setAudioCodec(audioCodec); + break; + case "max_size": + int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 + options.setMaxSize(maxSize); + break; + case "video_bit_rate": + int videoBitRate = Integer.parseInt(value); + options.setVideoBitRate(videoBitRate); + break; + case "audio_bit_rate": + int audioBitRate = Integer.parseInt(value); + options.setAudioBitRate(audioBitRate); + break; + case "max_fps": + int maxFps = Integer.parseInt(value); + options.setMaxFps(maxFps); + break; + case "lock_video_orientation": + int lockVideoOrientation = Integer.parseInt(value); + options.setLockVideoOrientation(lockVideoOrientation); + break; + case "tunnel_forward": + boolean tunnelForward = Boolean.parseBoolean(value); + options.setTunnelForward(tunnelForward); + break; + case "crop": + Rect crop = parseCrop(value); + options.setCrop(crop); + break; + case "control": + boolean control = Boolean.parseBoolean(value); + options.setControl(control); + break; + case "display_id": + int displayId = Integer.parseInt(value); + options.setDisplayId(displayId); + break; + case "show_touches": + boolean showTouches = Boolean.parseBoolean(value); + options.setShowTouches(showTouches); + break; + case "stay_awake": + boolean stayAwake = Boolean.parseBoolean(value); + options.setStayAwake(stayAwake); + break; + case "video_codec_options": + List videoCodecOptions = CodecOption.parse(value); + options.setVideoCodecOptions(videoCodecOptions); + break; + case "audio_codec_options": + List audioCodecOptions = CodecOption.parse(value); + options.setAudioCodecOptions(audioCodecOptions); + break; + case "video_encoder": + if (!value.isEmpty()) { + options.setVideoEncoder(value); + } + break; + case "audio_encoder": + if (!value.isEmpty()) { + options.setAudioEncoder(value); + } + case "power_off_on_close": + boolean powerOffScreenOnClose = Boolean.parseBoolean(value); + options.setPowerOffScreenOnClose(powerOffScreenOnClose); + break; + case "clipboard_autosync": + boolean clipboardAutosync = Boolean.parseBoolean(value); + options.setClipboardAutosync(clipboardAutosync); + break; + case "downsize_on_error": + boolean downsizeOnError = Boolean.parseBoolean(value); + options.setDownsizeOnError(downsizeOnError); + break; + case "cleanup": + boolean cleanup = Boolean.parseBoolean(value); + options.setCleanup(cleanup); + break; + case "power_on": + boolean powerOn = Boolean.parseBoolean(value); + options.setPowerOn(powerOn); + break; + case "list_encoders": + boolean listEncoders = Boolean.parseBoolean(value); + options.setListEncoders(listEncoders); + break; + case "list_displays": + boolean listDisplays = Boolean.parseBoolean(value); + options.setListDisplays(listDisplays); + break; + case "send_device_meta": + boolean sendDeviceMeta = Boolean.parseBoolean(value); + options.setSendDeviceMeta(sendDeviceMeta); + break; + case "send_frame_meta": + boolean sendFrameMeta = Boolean.parseBoolean(value); + options.setSendFrameMeta(sendFrameMeta); + break; + case "send_dummy_byte": + boolean sendDummyByte = Boolean.parseBoolean(value); + options.setSendDummyByte(sendDummyByte); + break; + case "send_codec_meta": + boolean sendCodecMeta = Boolean.parseBoolean(value); + options.setSendCodecMeta(sendCodecMeta); + break; + case "raw_video_stream": + boolean rawVideoStream = Boolean.parseBoolean(value); + if (rawVideoStream) { + options.setSendDeviceMeta(false); + options.setSendFrameMeta(false); + options.setSendDummyByte(false); + options.setSendCodecMeta(false); + } + break; + default: + Ln.w("Unknown server option: " + key); + break; + } + } + + return options; + } + + private static Rect parseCrop(String crop) { + if (crop.isEmpty()) { + return null; + } + // input format: "width:height:x:y" + String[] tokens = crop.split(":"); + if (tokens.length != 4) { + throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\""); + } + int width = Integer.parseInt(tokens[0]); + int height = Integer.parseInt(tokens[1]); + int x = Integer.parseInt(tokens[2]); + int y = Integer.parseInt(tokens[3]); + return new Rect(x, y, x + width, y + height); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 244913cf..067b1670 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,13 +1,11 @@ package com.genymobile.scrcpy; -import android.graphics.Rect; import android.os.BatteryManager; import android.os.Build; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Locale; public final class Server { @@ -161,203 +159,12 @@ public final class Server { return thread; } - @SuppressWarnings("MethodLength") - private static Options createOptions(String... args) { - if (args.length < 1) { - throw new IllegalArgumentException("Missing client version"); - } - - String clientVersion = args[0]; - if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { - throw new IllegalArgumentException( - "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); - } - - Options options = new Options(); - - for (int i = 1; i < args.length; ++i) { - String arg = args[i]; - int equalIndex = arg.indexOf('='); - if (equalIndex == -1) { - throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\""); - } - String key = arg.substring(0, equalIndex); - String value = arg.substring(equalIndex + 1); - switch (key) { - case "scid": - int scid = Integer.parseInt(value, 0x10); - if (scid < -1) { - throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid); - } - options.setScid(scid); - break; - case "log_level": - Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); - options.setLogLevel(level); - break; - case "audio": - boolean audio = Boolean.parseBoolean(value); - options.setAudio(audio); - break; - case "video_codec": - VideoCodec videoCodec = VideoCodec.findByName(value); - if (videoCodec == null) { - throw new IllegalArgumentException("Video codec " + value + " not supported"); - } - options.setVideoCodec(videoCodec); - break; - case "audio_codec": - AudioCodec audioCodec = AudioCodec.findByName(value); - if (audioCodec == null) { - throw new IllegalArgumentException("Audio codec " + value + " not supported"); - } - options.setAudioCodec(audioCodec); - break; - case "max_size": - int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 - options.setMaxSize(maxSize); - break; - case "video_bit_rate": - int videoBitRate = Integer.parseInt(value); - options.setVideoBitRate(videoBitRate); - break; - case "audio_bit_rate": - int audioBitRate = Integer.parseInt(value); - options.setAudioBitRate(audioBitRate); - break; - case "max_fps": - int maxFps = Integer.parseInt(value); - options.setMaxFps(maxFps); - break; - case "lock_video_orientation": - int lockVideoOrientation = Integer.parseInt(value); - options.setLockVideoOrientation(lockVideoOrientation); - break; - case "tunnel_forward": - boolean tunnelForward = Boolean.parseBoolean(value); - options.setTunnelForward(tunnelForward); - break; - case "crop": - Rect crop = parseCrop(value); - options.setCrop(crop); - break; - case "control": - boolean control = Boolean.parseBoolean(value); - options.setControl(control); - break; - case "display_id": - int displayId = Integer.parseInt(value); - options.setDisplayId(displayId); - break; - case "show_touches": - boolean showTouches = Boolean.parseBoolean(value); - options.setShowTouches(showTouches); - break; - case "stay_awake": - boolean stayAwake = Boolean.parseBoolean(value); - options.setStayAwake(stayAwake); - break; - case "video_codec_options": - List videoCodecOptions = CodecOption.parse(value); - options.setVideoCodecOptions(videoCodecOptions); - break; - case "audio_codec_options": - List audioCodecOptions = CodecOption.parse(value); - options.setAudioCodecOptions(audioCodecOptions); - break; - case "video_encoder": - if (!value.isEmpty()) { - options.setVideoEncoder(value); - } - break; - case "audio_encoder": - if (!value.isEmpty()) { - options.setAudioEncoder(value); - } - case "power_off_on_close": - boolean powerOffScreenOnClose = Boolean.parseBoolean(value); - options.setPowerOffScreenOnClose(powerOffScreenOnClose); - break; - case "clipboard_autosync": - boolean clipboardAutosync = Boolean.parseBoolean(value); - options.setClipboardAutosync(clipboardAutosync); - break; - case "downsize_on_error": - boolean downsizeOnError = Boolean.parseBoolean(value); - options.setDownsizeOnError(downsizeOnError); - break; - case "cleanup": - boolean cleanup = Boolean.parseBoolean(value); - options.setCleanup(cleanup); - break; - case "power_on": - boolean powerOn = Boolean.parseBoolean(value); - options.setPowerOn(powerOn); - break; - case "list_encoders": - boolean listEncoders = Boolean.parseBoolean(value); - options.setListEncoders(listEncoders); - break; - case "list_displays": - boolean listDisplays = Boolean.parseBoolean(value); - options.setListDisplays(listDisplays); - break; - case "send_device_meta": - boolean sendDeviceMeta = Boolean.parseBoolean(value); - options.setSendDeviceMeta(sendDeviceMeta); - break; - case "send_frame_meta": - boolean sendFrameMeta = Boolean.parseBoolean(value); - options.setSendFrameMeta(sendFrameMeta); - break; - case "send_dummy_byte": - boolean sendDummyByte = Boolean.parseBoolean(value); - options.setSendDummyByte(sendDummyByte); - break; - case "send_codec_meta": - boolean sendCodecMeta = Boolean.parseBoolean(value); - options.setSendCodecMeta(sendCodecMeta); - break; - case "raw_video_stream": - boolean rawVideoStream = Boolean.parseBoolean(value); - if (rawVideoStream) { - options.setSendDeviceMeta(false); - options.setSendFrameMeta(false); - options.setSendDummyByte(false); - options.setSendCodecMeta(false); - } - break; - default: - Ln.w("Unknown server option: " + key); - break; - } - } - - return options; - } - - private static Rect parseCrop(String crop) { - if (crop.isEmpty()) { - return null; - } - // input format: "width:height:x:y" - String[] tokens = crop.split(":"); - if (tokens.length != 4) { - throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\""); - } - int width = Integer.parseInt(tokens[0]); - int height = Integer.parseInt(tokens[1]); - int x = Integer.parseInt(tokens[2]); - int y = Integer.parseInt(tokens[3]); - return new Rect(x, y, x + width, y + height); - } - public static void main(String... args) throws Exception { Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Ln.e("Exception on thread " + t, e); }); - Options options = createOptions(args); + Options options = Options.parse(args); Ln.initLogLevel(options.getLogLevel()); From 9cfea347d0c4f460d2f06bb14f59363024f41c0a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 19:58:34 +0200 Subject: [PATCH 0859/1133] Remove Options setters Now that options parsing is performed from the Options class, setters are not necessary anymore. --- .../java/com/genymobile/scrcpy/Options.java | 220 +++--------------- 1 file changed, 35 insertions(+), 185 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 0ebb790f..a34eb9b5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -47,166 +47,82 @@ public class Options { return logLevel; } - public void setLogLevel(Ln.Level logLevel) { - this.logLevel = logLevel; - } - public int getScid() { return scid; } - public void setScid(int scid) { - this.scid = scid; - } - public boolean getAudio() { return audio; } - public void setAudio(boolean audio) { - this.audio = audio; - } - public int getMaxSize() { return maxSize; } - public void setMaxSize(int maxSize) { - this.maxSize = maxSize; - } - public VideoCodec getVideoCodec() { return videoCodec; } - public void setVideoCodec(VideoCodec videoCodec) { - this.videoCodec = videoCodec; - } - public AudioCodec getAudioCodec() { return audioCodec; } - public void setAudioCodec(AudioCodec audioCodec) { - this.audioCodec = audioCodec; - } - public int getVideoBitRate() { return videoBitRate; } - public void setVideoBitRate(int videoBitRate) { - this.videoBitRate = videoBitRate; - } - public int getAudioBitRate() { return audioBitRate; } - public void setAudioBitRate(int audioBitRate) { - this.audioBitRate = audioBitRate; - } - public int getMaxFps() { return maxFps; } - public void setMaxFps(int maxFps) { - this.maxFps = maxFps; - } - public int getLockVideoOrientation() { return lockVideoOrientation; } - public void setLockVideoOrientation(int lockVideoOrientation) { - this.lockVideoOrientation = lockVideoOrientation; - } - public boolean isTunnelForward() { return tunnelForward; } - public void setTunnelForward(boolean tunnelForward) { - this.tunnelForward = tunnelForward; - } - public Rect getCrop() { return crop; } - public void setCrop(Rect crop) { - this.crop = crop; - } - public boolean getControl() { return control; } - public void setControl(boolean control) { - this.control = control; - } - public int getDisplayId() { return displayId; } - public void setDisplayId(int displayId) { - this.displayId = displayId; - } - public boolean getShowTouches() { return showTouches; } - public void setShowTouches(boolean showTouches) { - this.showTouches = showTouches; - } - public boolean getStayAwake() { return stayAwake; } - public void setStayAwake(boolean stayAwake) { - this.stayAwake = stayAwake; - } - public List getVideoCodecOptions() { return videoCodecOptions; } - public void setVideoCodecOptions(List videoCodecOptions) { - this.videoCodecOptions = videoCodecOptions; - } - public List getAudioCodecOptions() { return audioCodecOptions; } - public void setAudioCodecOptions(List audioCodecOptions) { - this.audioCodecOptions = audioCodecOptions; - } - public String getVideoEncoder() { return videoEncoder; } - public void setVideoEncoder(String videoEncoder) { - this.videoEncoder = videoEncoder; - } - public String getAudioEncoder() { return audioEncoder; } - public void setAudioEncoder(String audioEncoder) { - this.audioEncoder = audioEncoder; - } - - public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) { - this.powerOffScreenOnClose = powerOffScreenOnClose; - } - public boolean getPowerOffScreenOnClose() { return this.powerOffScreenOnClose; } @@ -215,82 +131,42 @@ public class Options { return clipboardAutosync; } - public void setClipboardAutosync(boolean clipboardAutosync) { - this.clipboardAutosync = clipboardAutosync; - } - public boolean getDownsizeOnError() { return downsizeOnError; } - public void setDownsizeOnError(boolean downsizeOnError) { - this.downsizeOnError = downsizeOnError; - } - public boolean getCleanup() { return cleanup; } - public void setCleanup(boolean cleanup) { - this.cleanup = cleanup; - } - public boolean getPowerOn() { return powerOn; } - public void setPowerOn(boolean powerOn) { - this.powerOn = powerOn; - } - public boolean getListEncoders() { return listEncoders; } - public void setListEncoders(boolean listEncoders) { - this.listEncoders = listEncoders; - } - public boolean getListDisplays() { return listDisplays; } - public void setListDisplays(boolean listDisplays) { - this.listDisplays = listDisplays; - } - public boolean getSendDeviceMeta() { return sendDeviceMeta; } - public void setSendDeviceMeta(boolean sendDeviceMeta) { - this.sendDeviceMeta = sendDeviceMeta; - } - public boolean getSendFrameMeta() { return sendFrameMeta; } - public void setSendFrameMeta(boolean sendFrameMeta) { - this.sendFrameMeta = sendFrameMeta; - } - public boolean getSendDummyByte() { return sendDummyByte; } - public void setSendDummyByte(boolean sendDummyByte) { - this.sendDummyByte = sendDummyByte; - } - public boolean getSendCodecMeta() { return sendCodecMeta; } - public void setSendCodecMeta(boolean sendCodecMeta) { - this.sendCodecMeta = sendCodecMeta; - } - @SuppressWarnings("MethodLength") public static Options parse(String... args) { if (args.length < 1) { @@ -319,142 +195,116 @@ public class Options { if (scid < -1) { throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid); } - options.setScid(scid); + options.scid = scid; break; case "log_level": - Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); - options.setLogLevel(level); + options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); break; case "audio": - boolean audio = Boolean.parseBoolean(value); - options.setAudio(audio); + options.audio = Boolean.parseBoolean(value); break; case "video_codec": VideoCodec videoCodec = VideoCodec.findByName(value); if (videoCodec == null) { throw new IllegalArgumentException("Video codec " + value + " not supported"); } - options.setVideoCodec(videoCodec); + options.videoCodec = videoCodec; break; case "audio_codec": AudioCodec audioCodec = AudioCodec.findByName(value); if (audioCodec == null) { throw new IllegalArgumentException("Audio codec " + value + " not supported"); } - options.setAudioCodec(audioCodec); + options.audioCodec = audioCodec; break; case "max_size": - int maxSize = Integer.parseInt(value) & ~7; // multiple of 8 - options.setMaxSize(maxSize); + options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8 break; case "video_bit_rate": - int videoBitRate = Integer.parseInt(value); - options.setVideoBitRate(videoBitRate); + options.videoBitRate = Integer.parseInt(value); break; case "audio_bit_rate": - int audioBitRate = Integer.parseInt(value); - options.setAudioBitRate(audioBitRate); + options.audioBitRate = Integer.parseInt(value); break; case "max_fps": - int maxFps = Integer.parseInt(value); - options.setMaxFps(maxFps); + options.maxFps = Integer.parseInt(value); break; case "lock_video_orientation": - int lockVideoOrientation = Integer.parseInt(value); - options.setLockVideoOrientation(lockVideoOrientation); + options.lockVideoOrientation = Integer.parseInt(value); break; case "tunnel_forward": - boolean tunnelForward = Boolean.parseBoolean(value); - options.setTunnelForward(tunnelForward); + options.tunnelForward = Boolean.parseBoolean(value); break; case "crop": - Rect crop = parseCrop(value); - options.setCrop(crop); + options.crop = parseCrop(value); break; case "control": - boolean control = Boolean.parseBoolean(value); - options.setControl(control); + options.control = Boolean.parseBoolean(value); break; case "display_id": - int displayId = Integer.parseInt(value); - options.setDisplayId(displayId); + options.displayId = Integer.parseInt(value); break; case "show_touches": - boolean showTouches = Boolean.parseBoolean(value); - options.setShowTouches(showTouches); + options.showTouches = Boolean.parseBoolean(value); break; case "stay_awake": - boolean stayAwake = Boolean.parseBoolean(value); - options.setStayAwake(stayAwake); + options.stayAwake = Boolean.parseBoolean(value); break; case "video_codec_options": - List videoCodecOptions = CodecOption.parse(value); - options.setVideoCodecOptions(videoCodecOptions); + options.videoCodecOptions = CodecOption.parse(value); break; case "audio_codec_options": - List audioCodecOptions = CodecOption.parse(value); - options.setAudioCodecOptions(audioCodecOptions); + options.audioCodecOptions = CodecOption.parse(value); break; case "video_encoder": if (!value.isEmpty()) { - options.setVideoEncoder(value); + options.videoEncoder = value; } break; case "audio_encoder": if (!value.isEmpty()) { - options.setAudioEncoder(value); + options.audioEncoder = value; } case "power_off_on_close": - boolean powerOffScreenOnClose = Boolean.parseBoolean(value); - options.setPowerOffScreenOnClose(powerOffScreenOnClose); + options.powerOffScreenOnClose = Boolean.parseBoolean(value); break; case "clipboard_autosync": - boolean clipboardAutosync = Boolean.parseBoolean(value); - options.setClipboardAutosync(clipboardAutosync); + options.clipboardAutosync = Boolean.parseBoolean(value); break; case "downsize_on_error": - boolean downsizeOnError = Boolean.parseBoolean(value); - options.setDownsizeOnError(downsizeOnError); + options.downsizeOnError = Boolean.parseBoolean(value); break; case "cleanup": - boolean cleanup = Boolean.parseBoolean(value); - options.setCleanup(cleanup); + options.cleanup = Boolean.parseBoolean(value); break; case "power_on": - boolean powerOn = Boolean.parseBoolean(value); - options.setPowerOn(powerOn); + options.powerOn = Boolean.parseBoolean(value); break; case "list_encoders": - boolean listEncoders = Boolean.parseBoolean(value); - options.setListEncoders(listEncoders); + options.listEncoders = Boolean.parseBoolean(value); break; case "list_displays": - boolean listDisplays = Boolean.parseBoolean(value); - options.setListDisplays(listDisplays); + options.listDisplays = Boolean.parseBoolean(value); break; case "send_device_meta": - boolean sendDeviceMeta = Boolean.parseBoolean(value); - options.setSendDeviceMeta(sendDeviceMeta); + options.sendDeviceMeta = Boolean.parseBoolean(value); break; case "send_frame_meta": - boolean sendFrameMeta = Boolean.parseBoolean(value); - options.setSendFrameMeta(sendFrameMeta); + options.sendFrameMeta = Boolean.parseBoolean(value); break; case "send_dummy_byte": - boolean sendDummyByte = Boolean.parseBoolean(value); - options.setSendDummyByte(sendDummyByte); + options.sendDummyByte = Boolean.parseBoolean(value); break; case "send_codec_meta": - boolean sendCodecMeta = Boolean.parseBoolean(value); - options.setSendCodecMeta(sendCodecMeta); + options.sendCodecMeta = Boolean.parseBoolean(value); break; case "raw_video_stream": boolean rawVideoStream = Boolean.parseBoolean(value); if (rawVideoStream) { - options.setSendDeviceMeta(false); - options.setSendFrameMeta(false); - options.setSendDummyByte(false); - options.setSendCodecMeta(false); + options.sendDeviceMeta = false; + options.sendFrameMeta = false; + options.sendDummyByte = false; + options.sendCodecMeta = false; } break; default: From 9eb6591913bed3601df156a38bc6491f0360c8ce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 10 Apr 2023 19:29:09 +0200 Subject: [PATCH 0860/1133] Add missing --no-audio option in manpage --- app/scrcpy.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 97a15d1d..37497211 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -183,6 +183,10 @@ It may only work over USB. Also see \fB\-\-hid\-keyboard\fR. +.TP +.B \-\-no\-audio +Disable audio forwarding. + .TP .B \-\-no\-cleanup By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit. From c083a7cc9015b52e12b0fb78e5a488ab1f5b68f6 Mon Sep 17 00:00:00 2001 From: Yan Date: Wed, 5 Apr 2023 16:04:03 +0200 Subject: [PATCH 0861/1133] Force OpenGL Core Profile context on macOS By default, SDL creates an OpenGL 2.1 context on macOS for an OpenGL renderer. As a consequence, mipmapping is not supported. Force to use a core profile context, to get a higher version. Before: INFO: Renderer: opengl INFO: OpenGL version: 2.1 NVIDIA-14.0.32 355.11.11.10.10.143 WARN: Trilinear filtering disabled (OpenGL 3.0+ or ES 2.0+ required) After: INFO: Renderer: opengl DEBUG: Creating OpenGL Core Profile context INFO: OpenGL version: 4.1 NVIDIA-14.0.32 355.11.11.10.10.143 INFO: Trilinear filtering enabled when running with: scrcpy --verbosity=debug --render-driver=opengl Note: Since SDL_CreateRenderer() causes a fallback to OpenGL 2.1, the profile and version attributes have to be set and the context created _after_. PR #3895 Signed-off-by: Romain Vimont --- app/src/display.c | 19 +++++++++++++++++++ app/src/display.h | 8 ++++++++ 2 files changed, 27 insertions(+) diff --git a/app/src/display.c b/app/src/display.c index 96ff9e06..4852952b 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -23,6 +23,22 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { // starts with "opengl" bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); if (use_opengl) { + +#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE + // Persuade macOS to give us something better than OpenGL 2.1. + // If we create a Core Profile context, we get the best OpenGL version. + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, + SDL_GL_CONTEXT_PROFILE_CORE); + + LOGD("Creating OpenGL Core Profile context"); + display->gl_context = SDL_GL_CreateContext(window); + if (!display->gl_context) { + LOGE("Could not create OpenGL context: %s", SDL_GetError()); + SDL_DestroyRenderer(display->renderer); + return false; + } +#endif + struct sc_opengl *gl = &display->gl; sc_opengl_init(gl); @@ -51,6 +67,9 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { void sc_display_destroy(struct sc_display *display) { +#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE + SDL_GL_DeleteContext(display->gl_context); +#endif if (display->texture) { SDL_DestroyTexture(display->texture); } diff --git a/app/src/display.h b/app/src/display.h index 8856afcd..e30b4822 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -10,11 +10,19 @@ #include "coords.h" #include "opengl.h" +#ifdef __APPLE__ +# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE +#endif + struct sc_display { SDL_Renderer *renderer; SDL_Texture *texture; struct sc_opengl gl; +#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE + SDL_GLContext *gl_context; +#endif + bool mipmaps; }; From 0f3af2d20b201a07ad999699e07e59cf8066e32f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 22 Apr 2023 19:46:45 +0200 Subject: [PATCH 0862/1133] Fix build for FFmpeg < 3.3 The constant AV_CODEC_ID_AV1 was introduced in FFmpeg 3.3. Add an ifdef to support older versions. Fixes #3939 --- app/src/compat.h | 6 ++++++ app/src/demuxer.c | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 00cb7204..e80a9dd2 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -25,6 +25,12 @@ # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL #endif +// Not documented in ffmpeg/doc/APIchanges, but AV_CODEC_ID_AV1 has been added +// by FFmpeg commit d42809f9835a4e9e5c7c63210abb09ad0ef19cfb (included in tag +// n3.3). +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 89, 100) +# define SCRCPY_LAVC_HAS_AV1 +#endif // In ffmpeg/doc/APIchanges: // 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 5a613505..4fcdd9ad 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -33,7 +33,12 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { case SC_CODEC_ID_H265: return AV_CODEC_ID_HEVC; case SC_CODEC_ID_AV1: +#ifdef SCRCPY_LAVC_HAS_AV1 return AV_CODEC_ID_AV1; +#else + LOGE("AV1 not supported by this FFmpeg version"); + return AV_CODEC_ID_NONE; +#endif case SC_CODEC_ID_OPUS: return AV_CODEC_ID_OPUS; case SC_CODEC_ID_AAC: From cb20bcb16f4ca191e237c8744a7c2e6d29701d60 Mon Sep 17 00:00:00 2001 From: parknich081 <45834520+parknich081@users.noreply.github.com> Date: Wed, 26 Apr 2023 09:49:26 -0400 Subject: [PATCH 0863/1133] Clarify API versions that support Audio Forwarding Reword the supported API versions for audio forwarding sentence to clarify that it supports API >= 30 PR #3949 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9969dc51..9002031d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Its features include: The Android device requires at least API 21 (Android 5.0). -[Audio forwarding](doc/audio.md) is supported from API 30 (Android 11). +[Audio forwarding](doc/audio.md) is supported for API >= 30 (Android 11+). Make sure you [enabled USB debugging][enable-adb] on your device(s). From 6928acdeac29eee404a7c7014654965ef5128b88 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 5 May 2023 23:43:14 +0200 Subject: [PATCH 0864/1133] Rename --no-display to --no-mirror The option impacts both video and audio playback, so "no display" is not an appropriate name. PR #3978 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 4 ++-- app/src/cli.c | 27 ++++++++++++++++++--------- app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 20 +++++++++----------- app/tests/test_cli.c | 8 ++++---- doc/recording.md | 2 +- doc/v4l2.md | 2 +- doc/video.md | 6 +++--- 11 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index ae516c34..a0fca23d 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -33,7 +33,7 @@ _scrcpy() { --no-clipboard-autosync --no-downsize-on-error -n --no-control - -N --no-display + -N --no-mirror --no-key-repeat --no-mipmaps --no-power-on diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 97bf4f3e..ccb51a2c 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -39,7 +39,7 @@ arguments=( '--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' - {-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]' + {-N,--no-mirror}'[Do not mirror device \(only when recording or V4L2 sink is enabled\)]' '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 37497211..6ef01680 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -210,8 +210,8 @@ This option disables this behavior. Disable device control (mirror the device in read\-only). .TP -.B \-N, \-\-no\-display -Do not display device (only when screen recording is enabled). +.B \-N, \-\-no\-mirror +Do not mirror device video or audio on the computer (only when recording or V4L2 sink is enabled). .TP .B \-\-no\-key\-repeat diff --git a/app/src/cli.c b/app/src/cli.c index d6d9f41d..1ba7fe1f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -72,6 +72,7 @@ enum { OPT_REQUIRE_AUDIO, OPT_AUDIO_BUFFER, OPT_AUDIO_OUTPUT_BUFFER, + OPT_NO_DISPLAY, }; struct sc_option { @@ -380,9 +381,14 @@ static const struct sc_option options[] = { }, { .shortopt = 'N', + .longopt = "no-mirror", + .text = "Do not mirror device video or audio on the computer (only " + "when recording or V4L2 sink is enabled).", + }, + { + // deprecated + .longopt_id = OPT_NO_DISPLAY, .longopt = "no-display", - .text = "Do not display device (only when screen recording or V4L2 " - "sink is enabled).", }, { .longopt_id = OPT_NO_KEY_REPEAT, @@ -1642,8 +1648,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'n': opts->control = false; break; + case OPT_NO_DISPLAY: + LOGW("--no-display is deprecated, use --no-mirror instead."); + // fall through case 'N': - opts->display = false; + opts->mirror = false; break; case 'p': if (!parse_port_range(optarg, &opts->port_range)) { @@ -1890,8 +1899,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #ifdef HAVE_V4L2 - if (!opts->display && !opts->record_filename && !opts->v4l2_device) { - LOGE("-N/--no-display requires either screen recording (-r/--record)" + if (!opts->mirror && !opts->record_filename && !opts->v4l2_device) { + LOGE("-N/--no-mirror requires either screen recording (-r/--record)" " or sink to v4l2loopback device (--v4l2-sink)"); return false; } @@ -1915,14 +1924,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } #else - if (!opts->display && !opts->record_filename) { - LOGE("-N/--no-display requires screen recording (-r/--record)"); + if (!opts->mirror && !opts->record_filename) { + LOGE("-N/--no-mirror requires screen recording (-r/--record)"); return false; } #endif - if (opts->audio && !opts->display && !opts->record_filename) { - LOGI("No display and no recording: audio disabled"); + if (opts->audio && !opts->mirror && !opts->record_filename) { + LOGI("No mirror and no recording: audio disabled"); opts->audio = false; } diff --git a/app/src/options.c b/app/src/options.c index 8b99f6f3..eec81716 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -52,7 +52,7 @@ const struct scrcpy_options scrcpy_options_default = { .fullscreen = false, .always_on_top = false, .control = true, - .display = true, + .mirror = true, .turn_screen_off = false, .key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .window_borderless = false, diff --git a/app/src/options.h b/app/src/options.h index c41e2757..3bb0c91e 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -135,7 +135,7 @@ struct scrcpy_options { bool fullscreen; bool always_on_top; bool control; - bool display; + bool mirror; bool turn_screen_off; enum sc_key_inject_mode key_inject_mode; bool window_borderless; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index efa69d31..2c7fbf30 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -137,7 +137,7 @@ sdl_set_hints(const char *render_driver) { } static void -sdl_configure(bool display, bool disable_screensaver) { +sdl_configure(bool mirror, bool disable_screensaver) { #ifdef _WIN32 // Clean up properly on Ctrl+C on Windows bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); @@ -146,7 +146,7 @@ sdl_configure(bool display, bool disable_screensaver) { } #endif // _WIN32 - if (!display) { + if (!mirror) { return; } @@ -385,12 +385,10 @@ scrcpy(struct scrcpy_options *options) { goto end; } - if (options->display) { + if (options->mirror) { sdl_set_hints(options->render_driver); - } - // Initialize SDL video in addition if display is enabled - if (options->display) { + // Initialize SDL video and audio in addition if mirroring is enabled if (SDL_Init(SDL_INIT_VIDEO)) { LOGE("Could not initialize SDL video: %s", SDL_GetError()); goto end; @@ -402,7 +400,7 @@ scrcpy(struct scrcpy_options *options) { } } - sdl_configure(options->display, options->disable_screensaver); + sdl_configure(options->mirror, options->disable_screensaver); // Await for server without blocking Ctrl+C handling bool connected; @@ -428,7 +426,7 @@ scrcpy(struct scrcpy_options *options) { struct sc_file_pusher *fp = NULL; - if (options->display && options->control) { + if (options->mirror && options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { goto end; @@ -451,8 +449,8 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, options); } - bool needs_video_decoder = options->display; - bool needs_audio_decoder = options->audio && options->display; + bool needs_video_decoder = options->mirror; + bool needs_audio_decoder = options->mirror && options->audio; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; #endif @@ -646,7 +644,7 @@ aoa_hid_end: // There is a controller if and only if control is enabled assert(options->control == !!controller); - if (options->display) { + if (options->mirror) { const char *window_title = options->window_title ? options->window_title : info->device_name; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 3e9a248a..1f00cb90 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -53,7 +53,7 @@ static void test_options(void) { "--max-size", "1024", "--lock-video-orientation=2", // optional arguments require '=' // "--no-control" is not compatible with "--turn-screen-off" - // "--no-display" is not compatible with "--fulscreen" + // "--no-mirror" is not compatible with "--fulscreen" "--port", "1234:1236", "--push-target", "/sdcard/Movies", "--record", "file", @@ -108,8 +108,8 @@ static void test_options2(void) { char *argv[] = { "scrcpy", "--no-control", - "--no-display", - "--record", "file.mp4", // cannot enable --no-display without recording + "--no-mirror", + "--record", "file.mp4", // cannot enable --no-mirror without recording }; bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv); @@ -117,7 +117,7 @@ static void test_options2(void) { const struct scrcpy_options *opts = &args.opts; assert(!opts->control); - assert(!opts->display); + assert(!opts->mirror); assert(!strcmp(opts->record_filename, "file.mp4")); assert(opts->record_format == SC_RECORD_FORMAT_MP4); } diff --git a/doc/recording.md b/doc/recording.md index 4aad088c..9455a6fc 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -18,7 +18,7 @@ _It is currently not possible to record only the audio._ To disable mirroring while recording: ```bash -scrcpy --no-display --record=file.mp4 +scrcpy --no-mirror --record=file.mp4 scrcpy -Nr file.mkv # interrupt recording with Ctrl+C ``` diff --git a/doc/v4l2.md b/doc/v4l2.md index ea8c0eed..2c6e2cfc 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -35,7 +35,7 @@ To start `scrcpy` using a v4l2 sink: ```bash scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window +scrcpy --v4l2-sink=/dev/videoN --no-mirror # disable mirroring window ``` (replace `N` with the device ID, check with `ls /dev/video*`) diff --git a/doc/video.md b/doc/video.md index a2e9d106..58aa5022 100644 --- a/doc/video.md +++ b/doc/video.md @@ -159,15 +159,15 @@ scrcpy --display-buffer=50 --v4l2-buffer=300 ``` -## No display +## No mirror It is possible to capture an Android device without displaying a mirroring window. This option is available if either [recording](recording.md) or [v4l2](#video4linux) is enabled: ```bash -scrcpy --v4l2-sink=/dev/video2 --no-display -scrcpy --record=file.mkv --no-display +scrcpy --v4l2-sink=/dev/video2 --no-mirror +scrcpy --record=file.mkv --no-mirror ``` ## Video4Linux From 92483fe11b6fd6bae5ef775ccaff78fefa92aad4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 5 May 2023 23:45:55 +0200 Subject: [PATCH 0865/1133] Disable controls on --no-mirror If mirroring is disabled, control must also be disabled. PR #3978 --- app/src/cli.c | 9 +++++++++ app/src/scrcpy.c | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 1ba7fe1f..066f46fc 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2041,6 +2041,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif +#ifdef HAVE_USB + if (!opts->mirror && opts->control && !opts->otg) { +#else + if (!opts->mirror && opts->control) { +#endif + LOGD("Mirroring is disabled, force --no-control"); + opts->control = false; + } + return true; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2c7fbf30..03b643f6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -426,7 +426,8 @@ scrcpy(struct scrcpy_options *options) { struct sc_file_pusher *fp = NULL; - if (options->mirror && options->control) { + assert(!options->control || options->mirror); // control implies mirror + if (options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { goto end; From 9c08eb79cb7941848882cb908cefee9933450de5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 19:12:39 +0200 Subject: [PATCH 0866/1133] Close connection at the end of finally-block The async processors use the socket file descriptors from the connection. Therefore, the connection must not be closed before all async processor threads are joined. PR #3978 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 067b1670..fade7214 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -92,7 +92,8 @@ public final class Server { List asyncProcessors = new ArrayList<>(); - try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) { + DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte); + try { if (options.getSendDeviceMeta()) { connection.sendDeviceMeta(Device.getDeviceName()); } @@ -150,6 +151,8 @@ public final class Server { } catch (InterruptedException e) { // ignore } + + connection.close(); } } From 751a3653a0ab36b98d29da689f26d315c8426701 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 15:18:31 +0200 Subject: [PATCH 0867/1133] Add missing @Override annotations PR #3978 --- server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 3 +++ .../src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 3 +++ server/src/main/java/com/genymobile/scrcpy/Controller.java | 3 +++ 3 files changed, 9 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index f2bba772..ac2f0a31 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -114,6 +114,7 @@ public final class AudioEncoder implements AsyncProcessor { } } + @Override public void start() { thread = new Thread(() -> { try { @@ -129,6 +130,7 @@ public final class AudioEncoder implements AsyncProcessor { thread.start(); } + @Override public void stop() { if (thread != null) { // Just wake up the blocking wait from the thread, so that it properly releases all its resources and terminates @@ -136,6 +138,7 @@ public final class AudioEncoder implements AsyncProcessor { } } + @Override public void join() throws InterruptedException { if (thread != null) { thread.join(); diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index f98440de..32efc354 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -53,6 +53,7 @@ public final class AudioRawRecorder implements AsyncProcessor { } } + @Override public void start() { thread = new Thread(() -> { try { @@ -68,12 +69,14 @@ public final class AudioRawRecorder implements AsyncProcessor { thread.start(); } + @Override public void stop() { if (thread != null) { thread.interrupt(); } } + @Override public void join() throws InterruptedException { if (thread != null) { thread.join(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 59fae602..ab09c336 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -84,6 +84,7 @@ public class Controller implements AsyncProcessor { } } + @Override public void start() { thread = new Thread(() -> { try { @@ -98,6 +99,7 @@ public class Controller implements AsyncProcessor { sender.start(); } + @Override public void stop() { if (thread != null) { thread.interrupt(); @@ -105,6 +107,7 @@ public class Controller implements AsyncProcessor { sender.stop(); } + @Override public void join() throws InterruptedException { if (thread != null) { thread.join(); From feab87053abcceded41342d9d856763dedc09187 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Apr 2023 15:17:54 +0200 Subject: [PATCH 0868/1133] Convert screen encoder to async processor Contrary to the other tasks (controller and audio capture/encoding), the screen encoder was executed synchronously. As a consequence, scrcpy-server could not terminate until the screen encoder returned. Convert it to an async processor. This allows to terminate on controller error, and this paves the way to disable video mirroring. PR #3978 --- .../com/genymobile/scrcpy/AsyncProcessor.java | 11 +++- .../com/genymobile/scrcpy/AudioEncoder.java | 10 +++- .../genymobile/scrcpy/AudioRawRecorder.java | 5 +- .../com/genymobile/scrcpy/Controller.java | 3 +- .../com/genymobile/scrcpy/ScreenEncoder.java | 50 +++++++++++++++++-- .../java/com/genymobile/scrcpy/Server.java | 46 +++++++++++++---- 6 files changed, 105 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java index cbc435b0..b9b6745c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java +++ b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java @@ -1,7 +1,16 @@ package com.genymobile.scrcpy; public interface AsyncProcessor { - void start(); + interface TerminationListener { + /** + * Notify processor termination + * + * @param fatalError {@code true} if this must cause the termination of the whole scrcpy-server. + */ + void onTerminated(boolean fatalError); + } + + void start(TerminationListener listener); void stop(); void join() throws InterruptedException; } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index ac2f0a31..a1abd71b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -115,16 +115,22 @@ public final class AudioEncoder implements AsyncProcessor { } @Override - public void start() { + public void start(TerminationListener listener) { thread = new Thread(() -> { + boolean fatalError = false; try { encode(); - } catch (ConfigurationException | AudioCaptureForegroundException e) { + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + fatalError = true; + } catch (AudioCaptureForegroundException e) { // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { Ln.e("Audio encoding error", e); + fatalError = true; } finally { Ln.d("Audio encoder stopped"); + listener.onTerminated(fatalError); } }); thread.start(); diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 32efc354..685ac3bd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -54,16 +54,19 @@ public final class AudioRawRecorder implements AsyncProcessor { } @Override - public void start() { + public void start(TerminationListener listener) { thread = new Thread(() -> { + boolean fatalError = false; try { record(); } catch (AudioCaptureForegroundException e) { // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { Ln.e("Audio recording error", e); + fatalError = true; } finally { Ln.d("Audio recorder stopped"); + listener.onTerminated(fatalError); } }); thread.start(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index ab09c336..9a4e275a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -85,7 +85,7 @@ public class Controller implements AsyncProcessor { } @Override - public void start() { + public void start(TerminationListener listener) { thread = new Thread(() -> { try { control(); @@ -93,6 +93,7 @@ public class Controller implements AsyncProcessor { // this is expected on close } finally { Ln.d("Controller stopped"); + listener.onTerminated(true); } }); thread.start(); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 528cd327..901ba94c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -16,7 +16,7 @@ import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -public class ScreenEncoder implements Device.RotationListener { +public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms @@ -39,6 +39,9 @@ public class ScreenEncoder implements Device.RotationListener { private boolean firstFrameSent; private int consecutiveErrors; + private Thread thread; + private final AtomicBoolean stopped = new AtomicBoolean(); + public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { this.device = device; @@ -55,11 +58,11 @@ public class ScreenEncoder implements Device.RotationListener { rotationChanged.set(true); } - public boolean consumeRotationChange() { + private boolean consumeRotationChange() { return rotationChanged.getAndSet(false); } - public void streamScreen() throws IOException, ConfigurationException { + private void streamScreen() throws IOException, ConfigurationException { Codec codec = streamer.getCodec(); MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); @@ -163,9 +166,14 @@ public class ScreenEncoder implements Device.RotationListener { private boolean encode(MediaCodec codec, Streamer streamer) throws IOException { boolean eof = false; + boolean alive = true; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (!consumeRotationChange() && !eof) { + if (stopped.get()) { + alive = false; + break; + } int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); try { if (consumeRotationChange()) { @@ -193,7 +201,7 @@ public class ScreenEncoder implements Device.RotationListener { } } - return !eof; + return !eof && alive; } private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { @@ -267,4 +275,38 @@ public class ScreenEncoder implements Device.RotationListener { SurfaceControl.closeTransaction(); } } + + @Override + public void start(TerminationListener listener) { + thread = new Thread(() -> { + try { + streamScreen(); + } catch (ConfigurationException e) { + // Do not print stack trace, a user-friendly error-message has already been logged + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Video encoding error", e); + } + } finally { + Ln.d("Screen streaming stopped"); + listener.onTerminated(true); + } + }); + thread.start(); + } + + @Override + public void stop() { + if (thread != null) { + stopped.set(true); + } + } + + @Override + public void join() throws InterruptedException { + if (thread != null) { + thread.join(); + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index fade7214..4d72d1e8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -9,6 +9,35 @@ import java.util.List; public final class Server { + private static class Completion { + private int running; + private boolean fatalError; + + Completion(int running) { + this.running = running; + } + + synchronized void addCompleted(boolean fatalError) { + --running; + if (fatalError) { + this.fatalError = true; + } + if (running == 0 || this.fatalError) { + notify(); + } + } + + synchronized void await() { + try { + while (running > 0 && !fatalError) { + wait(); + } + } catch (InterruptedException e) { + // ignore + } + } + } + private Server() { // not instantiable } @@ -122,22 +151,17 @@ public final class Server { options.getSendFrameMeta()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); + asyncProcessors.add(screenEncoder); + Completion completion = new Completion(asyncProcessors.size()); for (AsyncProcessor asyncProcessor : asyncProcessors) { - asyncProcessor.start(); + asyncProcessor.start((fatalError) -> { + completion.addCompleted(fatalError); + }); } - try { - // synchronous - screenEncoder.streamScreen(); - } catch (IOException e) { - // Broken pipe is expected on close, because the socket is closed by the client - if (!IO.isBrokenPipe(e)) { - Ln.e("Video encoding error", e); - } - } + completion.await(); } finally { - Ln.d("Screen streaming stopped"); initThread.interrupt(); for (AsyncProcessor asyncProcessor : asyncProcessors) { asyncProcessor.stop(); From e89e772c7c3df65e33362a542cd900f46cf62baf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:02:45 +0200 Subject: [PATCH 0869/1133] Remove unnecessary 'else' Some server parameters may depend on one another. For example, audio_bit_rate is meaningless if audio is false. But it is inconsistent to disable some parameters based on these dependencies checks, but not others. Handling all dependencies between parameters would add too much complexity for no benefit. So just pass individual parameters independently. PR #3978 --- app/src/server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/server.c b/app/src/server.c index 8c4e9a95..2b35c085 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -231,7 +231,8 @@ execute_server(struct sc_server *server, } if (!params->audio) { ADD_PARAM("audio=false"); - } else if (params->audio_bit_rate) { + } + if (params->audio_bit_rate) { ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate); } if (params->video_codec != SC_CODEC_H264) { From 8c650e53cd37a53d5c3aa746c30a71c6b742a4e2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:08:50 +0200 Subject: [PATCH 0870/1133] Add --no-video Similar to --no-audio, add --no-video to play audio only. Fixes #3842 PR #3978 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 + app/src/cli.c | 21 ++++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/recorder.c | 69 ++++++++++------ app/src/recorder.h | 3 +- app/src/scrcpy.c | 54 ++++++++----- app/src/server.c | 80 ++++++++++++------- app/src/server.h | 1 + doc/audio.md | 15 ++++ doc/recording.md | 6 +- doc/video.md | 10 +++ .../genymobile/scrcpy/DesktopConnection.java | 49 +++++++++--- .../java/com/genymobile/scrcpy/Options.java | 8 ++ .../java/com/genymobile/scrcpy/Server.java | 15 ++-- 17 files changed, 243 insertions(+), 96 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a0fca23d..cdc9270f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -37,6 +37,7 @@ _scrcpy() { --no-key-repeat --no-mipmaps --no-power-on + --no-video --otg -p --port= --power-off-on-close diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index ccb51a2c..5e40b2fb 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -43,6 +43,7 @@ arguments=( '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' + '--no-video[Disable video forwarding]' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' '--power-off-on-close[Turn the device screen off when closing scrcpy]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 6ef01680..29d14b58 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -225,6 +225,10 @@ If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically .B \-\-no\-power\-on Do not power on the device on start. +.TP +.B \-\-no\-video +Disable video forwarding. + .TP .B \-\-otg Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. diff --git a/app/src/cli.c b/app/src/cli.c index 066f46fc..1558ef0c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -73,6 +73,7 @@ enum { OPT_AUDIO_BUFFER, OPT_AUDIO_OUTPUT_BUFFER, OPT_NO_DISPLAY, + OPT_NO_VIDEO, }; struct sc_option { @@ -407,6 +408,11 @@ static const struct sc_option options[] = { .longopt = "no-power-on", .text = "Do not power on the device on start.", }, + { + .longopt_id = OPT_NO_VIDEO, + .longopt = "no-video", + .text = "Disable video forwarding.", + }, { .longopt_id = OPT_OTG, .longopt = "otg", @@ -1797,6 +1803,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_DOWNSIZE_ON_ERROR: opts->downsize_on_error = false; break; + case OPT_NO_VIDEO: + opts->video = false; + break; case OPT_NO_AUDIO: opts->audio = false; break; @@ -2042,14 +2051,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], #endif #ifdef HAVE_USB - if (!opts->mirror && opts->control && !opts->otg) { + if (!(opts->mirror && opts->video) && !opts->otg) { #else - if (!opts->mirror && opts->control) { + if (!(opts->mirror && opts->video)) { #endif - LOGD("Mirroring is disabled, force --no-control"); + // If video mirroring is disabled and OTG are disabled, then there is + // no way to control the device. opts->control = false; } + if (!opts->video) { + // If video is disabled, then scrcpy must exit on audio failure. + opts->require_audio = true; + } + return true; } diff --git a/app/src/options.c b/app/src/options.c index eec81716..12283952 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -73,6 +73,7 @@ const struct scrcpy_options scrcpy_options_default = { .cleanup = true, .start_fps_counter = false, .power_on = true, + .video = true, .audio = true, .require_audio = false, .list_encoders = false, diff --git a/app/src/options.h b/app/src/options.h index 3bb0c91e..4edf3f32 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -156,6 +156,7 @@ struct scrcpy_options { bool cleanup; bool start_fps_counter; bool power_on; + bool video; bool audio; bool require_audio; bool list_encoders; diff --git a/app/src/recorder.c b/app/src/recorder.c index 2fc95eca..5cbe6873 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -152,7 +152,7 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { static inline bool sc_recorder_has_empty_queues(struct sc_recorder *recorder) { - if (sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { // The video queue is empty return true; } @@ -176,7 +176,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } - if (sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { assert(recorder->stopped); // If the recorder is stopped, don't process anything if there are not // at least video packets @@ -184,7 +184,11 @@ sc_recorder_process_header(struct sc_recorder *recorder) { return false; } - AVPacket *video_pkt = sc_vecdeque_pop(&recorder->video_queue); + AVPacket *video_pkt = NULL; + if (!sc_vecdeque_is_empty(&recorder->video_queue)) { + assert(recorder->video); + video_pkt = sc_vecdeque_pop(&recorder->video_queue); + } AVPacket *audio_pkt = NULL; if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { @@ -196,17 +200,19 @@ sc_recorder_process_header(struct sc_recorder *recorder) { int ret = false; - if (video_pkt->pts != AV_NOPTS_VALUE) { - LOGE("The first video packet is not a config packet"); - goto end; - } + if (video_pkt) { + if (video_pkt->pts != AV_NOPTS_VALUE) { + LOGE("The first video packet is not a config packet"); + goto end; + } - assert(recorder->video_stream_index >= 0); - AVStream *video_stream = - recorder->ctx->streams[recorder->video_stream_index]; - bool ok = sc_recorder_set_extradata(video_stream, video_pkt); - if (!ok) { - goto end; + assert(recorder->video_stream_index >= 0); + AVStream *video_stream = + recorder->ctx->streams[recorder->video_stream_index]; + bool ok = sc_recorder_set_extradata(video_stream, video_pkt); + if (!ok) { + goto end; + } } if (audio_pkt) { @@ -218,13 +224,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) { assert(recorder->audio_stream_index >= 0); AVStream *audio_stream = recorder->ctx->streams[recorder->audio_stream_index]; - ok = sc_recorder_set_extradata(audio_stream, audio_pkt); + bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt); if (!ok) { goto end; } } - ok = avformat_write_header(recorder->ctx, NULL) >= 0; + bool ok = avformat_write_header(recorder->ctx, NULL) >= 0; if (!ok) { LOGE("Failed to write header to %s", recorder->filename); goto end; @@ -233,7 +239,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { ret = true; end: - av_packet_free(&video_pkt); + if (video_pkt) { + av_packet_free(&video_pkt); + } if (audio_pkt) { av_packet_free(&audio_pkt); } @@ -263,7 +271,8 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); while (!recorder->stopped) { - if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && !video_pkt && + !sc_vecdeque_is_empty(&recorder->video_queue)) { // A new packet may be assigned to video_pkt and be processed break; } @@ -278,6 +287,11 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // If stopped is set, continue to process the remaining events (to // finish the recording) before actually stopping. + // If there is no video, then the video_queue will remain empty forever + // and video_pkt will always be NULL. + assert(recorder->video || (!video_pkt + && sc_vecdeque_is_empty(&recorder->video_queue))); + // If there is no audio, then the audio_queue will remain empty forever // and audio_pkt will always be NULL. assert(recorder->audio || (!audio_pkt @@ -319,6 +333,9 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { if (!recorder->audio) { assert(video_pkt); pts_origin = video_pkt->pts; + } else if (!recorder->video) { + assert(audio_pkt); + pts_origin = audio_pkt->pts; } else if (video_pkt && audio_pkt) { pts_origin = MIN(video_pkt->pts, audio_pkt->pts); } else if (recorder->stopped) { @@ -639,7 +656,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, bool audio, + enum sc_record_format format, bool video, bool audio, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { recorder->filename = strdup(filename); if (!recorder->filename) { @@ -662,6 +679,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_queue_cond_destroy; } + assert(video || audio); + recorder->video = video; recorder->audio = audio; sc_vecdeque_init(&recorder->video_queue); @@ -680,13 +699,15 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->cbs = cbs; recorder->cbs_userdata = cbs_userdata; - static const struct sc_packet_sink_ops video_ops = { - .open = sc_recorder_video_packet_sink_open, - .close = sc_recorder_video_packet_sink_close, - .push = sc_recorder_video_packet_sink_push, - }; + if (video) { + static const struct sc_packet_sink_ops video_ops = { + .open = sc_recorder_video_packet_sink_open, + .close = sc_recorder_video_packet_sink_close, + .push = sc_recorder_video_packet_sink_push, + }; - recorder->video_packet_sink.ops = &video_ops; + recorder->video_packet_sink.ops = &video_ops; + } if (audio) { static const struct sc_packet_sink_ops audio_ops = { diff --git a/app/src/recorder.h b/app/src/recorder.h index 41b8db65..9c6cd4f9 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -27,6 +27,7 @@ struct sc_recorder { * may access it without data races. */ bool audio; + bool video; char *filename; enum sc_record_format format; @@ -59,7 +60,7 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, bool audio, + enum sc_record_format format, bool video, bool audio, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 03b643f6..39a2f1a4 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -345,6 +345,7 @@ scrcpy(struct scrcpy_options *options) { .lock_video_orientation = options->lock_video_orientation, .control = options->control, .display_id = options->display_id, + .video = options->video, .audio = options->audio, .show_touches = options->show_touches, .stay_awake = options->stay_awake, @@ -389,7 +390,7 @@ scrcpy(struct scrcpy_options *options) { sdl_set_hints(options->render_driver); // Initialize SDL video and audio in addition if mirroring is enabled - if (SDL_Init(SDL_INIT_VIDEO)) { + if (options->video && SDL_Init(SDL_INIT_VIDEO)) { LOGE("Could not initialize SDL video: %s", SDL_GetError()); goto end; } @@ -436,11 +437,13 @@ scrcpy(struct scrcpy_options *options) { file_pusher_initialized = true; } - static const struct sc_demuxer_callbacks video_demuxer_cbs = { - .on_ended = sc_video_demuxer_on_ended, - }; - sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, - &video_demuxer_cbs, NULL); + if (options->video) { + static const struct sc_demuxer_callbacks video_demuxer_cbs = { + .on_ended = sc_video_demuxer_on_ended, + }; + sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, + &video_demuxer_cbs, NULL); + } if (options->audio) { static const struct sc_demuxer_callbacks audio_demuxer_cbs = { @@ -450,7 +453,7 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, options); } - bool needs_video_decoder = options->mirror; + bool needs_video_decoder = options->mirror && options->video; bool needs_audio_decoder = options->mirror && options->audio; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; @@ -471,8 +474,8 @@ scrcpy(struct scrcpy_options *options) { .on_ended = sc_recorder_on_ended, }; if (!sc_recorder_init(&s->recorder, options->record_filename, - options->record_format, options->audio, - &recorder_cbs, NULL)) { + options->record_format, options->video, + options->audio, &recorder_cbs, NULL)) { goto end; } recorder_initialized = true; @@ -482,8 +485,10 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - sc_packet_source_add_sink(&s->video_demuxer.packet_source, - &s->recorder.video_packet_sink); + if (options->video) { + sc_packet_source_add_sink(&s->video_demuxer.packet_source, + &s->recorder.video_packet_sink); + } if (options->audio) { sc_packet_source_add_sink(&s->audio_demuxer.packet_source, &s->recorder.audio_packet_sink); @@ -671,11 +676,6 @@ aoa_hid_end: .start_fps_counter = options->start_fps_counter, }; - if (!sc_screen_init(&s->screen, &screen_params)) { - goto end; - } - screen_initialized = true; - struct sc_frame_source *src = &s->video_decoder.frame_source; if (options->display_buffer) { sc_delay_buffer_init(&s->display_buffer, options->display_buffer, @@ -684,7 +684,14 @@ aoa_hid_end: src = &s->display_buffer.frame_source; } - sc_frame_source_add_sink(src, &s->screen.frame_sink); + if (options->video) { + if (!sc_screen_init(&s->screen, &screen_params)) { + goto end; + } + screen_initialized = true; + + sc_frame_source_add_sink(src, &s->screen.frame_sink); + } if (options->audio) { sc_audio_player_init(&s->audio_player, options->audio_buffer, @@ -713,12 +720,15 @@ aoa_hid_end: } #endif - // now we consumed the header values, the socket receives the video stream - // start the video demuxer - if (!sc_demuxer_start(&s->video_demuxer)) { - goto end; + // Now that the header values have been consumed, the socket(s) will + // receive the stream(s). Start the demuxer(s). + + if (options->video) { + if (!sc_demuxer_start(&s->video_demuxer)) { + goto end; + } + video_demuxer_started = true; } - video_demuxer_started = true; if (options->audio) { if (!sc_demuxer_start(&s->audio_demuxer)) { diff --git a/app/src/server.c b/app/src/server.c index 2b35c085..9c554760 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -226,6 +226,9 @@ execute_server(struct sc_server *server, ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); + if (!params->video) { + ADD_PARAM("video=false"); + } if (params->video_bit_rate) { ADD_PARAM("video_bit_rate=%" PRIu32, params->video_bit_rate); } @@ -464,6 +467,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { const char *serial = server->serial; assert(serial); + bool video = server->params.video; bool audio = server->params.audio; bool control = server->params.control; @@ -471,9 +475,12 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { sc_socket audio_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE; if (!tunnel->forward) { - video_socket = net_accept_intr(&server->intr, tunnel->server_socket); - if (video_socket == SC_SOCKET_NONE) { - goto fail; + if (video) { + video_socket = + net_accept_intr(&server->intr, tunnel->server_socket); + if (video_socket == SC_SOCKET_NONE) { + goto fail; + } } if (audio) { @@ -504,35 +511,45 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { unsigned attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); - video_socket = connect_to_server(server, attempts, delay, tunnel_host, - tunnel_port); - if (video_socket == SC_SOCKET_NONE) { + sc_socket first_socket = connect_to_server(server, attempts, delay, + tunnel_host, tunnel_port); + if (first_socket == SC_SOCKET_NONE) { goto fail; } + if (video) { + video_socket = first_socket; + } + if (audio) { - audio_socket = net_socket(); - if (audio_socket == SC_SOCKET_NONE) { - goto fail; - } - bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, - tunnel_port); - if (!ok) { - goto fail; + if (!video) { + audio_socket = first_socket; + } else { + audio_socket = net_socket(); + if (audio_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, + tunnel_port); + if (!ok) { + goto fail; + } } } if (control) { - // we know that the device is listening, we don't need several - // attempts - control_socket = net_socket(); - if (control_socket == SC_SOCKET_NONE) { - goto fail; - } - bool ok = net_connect_intr(&server->intr, control_socket, - tunnel_host, tunnel_port); - if (!ok) { - goto fail; + if (!video && !audio) { + control_socket = first_socket; + } else { + control_socket = net_socket(); + if (control_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, control_socket, + tunnel_host, tunnel_port); + if (!ok) { + goto fail; + } } } } @@ -541,13 +558,17 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { sc_adb_tunnel_close(tunnel, &server->intr, serial, server->device_socket_name); + sc_socket first_socket = video ? video_socket + : audio ? audio_socket + : control_socket; + // The sockets will be closed on stop if device_read_info() fails - bool ok = device_read_info(&server->intr, video_socket, info); + bool ok = device_read_info(&server->intr, first_socket, info); if (!ok) { goto fail; } - assert(video_socket != SC_SOCKET_NONE); + assert(!video || video_socket != SC_SOCKET_NONE); assert(!audio || audio_socket != SC_SOCKET_NONE); assert(!control || control_socket != SC_SOCKET_NONE); @@ -931,8 +952,11 @@ run_server(void *data) { sc_mutex_unlock(&server->mutex); // Interrupt sockets to wake up socket blocking calls on the server - assert(server->video_socket != SC_SOCKET_NONE); - net_interrupt(server->video_socket); + + if (server->video_socket != SC_SOCKET_NONE) { + // There is no video_socket if --no-video is set + net_interrupt(server->video_socket); + } if (server->audio_socket != SC_SOCKET_NONE) { // There is no audio_socket if --no-audio is set diff --git a/app/src/server.h b/app/src/server.h index c425856b..31648445 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -41,6 +41,7 @@ struct sc_server_params { int8_t lock_video_orientation; bool control; uint32_t display_id; + bool video; bool audio; bool show_touches; bool stay_awake; diff --git a/doc/audio.md b/doc/audio.md index 6e97b103..08868eaf 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -24,6 +24,21 @@ To disable audio: scrcpy --no-audio ``` +## Audio only + +To play audio only, disable the video: + +``` +scrcpy --no-video +``` + +Without video, the audio latency is typically not criticial, so it might be +interesting to add [buffering](#buffering) to minimize glitches: + +``` +scrcpy --no-video --audio-buffer=200 +``` + ## Codec The audio codec can be selected. The possible values are `opus` (default), `aac` diff --git a/doc/recording.md b/doc/recording.md index 9455a6fc..e3e2cf68 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -13,7 +13,11 @@ To record only the video: scrcpy --no-audio --record=file.mp4 ``` -_It is currently not possible to record only the audio._ +To record only the audio: + +```bash +scrcpy --no-video --record=file.mp4 +``` To disable mirroring while recording: diff --git a/doc/video.md b/doc/video.md index 58aa5022..303d9048 100644 --- a/doc/video.md +++ b/doc/video.md @@ -170,6 +170,16 @@ scrcpy --v4l2-sink=/dev/video2 --no-mirror scrcpy --record=file.mkv --no-mirror ``` + +## No video + +To disable video forwarding completely, so that only audio is forwarded: + +``` +scrcpy --no-video +``` + + ## Video4Linux See the dedicated [Video4Linux](v4l2.md) page. diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 4bfff726..20ab1f9c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -41,7 +41,7 @@ public final class DesktopConnection implements Closeable { controlInputStream = null; controlOutputStream = null; } - videoFd = videoSocket.getFileDescriptor(); + videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null; audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; } @@ -60,29 +60,43 @@ public final class DesktopConnection implements Closeable { return SOCKET_NAME_PREFIX + String.format("_%08x", scid); } - public static DesktopConnection open(int scid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException { + public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte) + throws IOException { String socketName = getSocketName(scid); + LocalSocket firstSocket = null; + LocalSocket videoSocket = null; LocalSocket audioSocket = null; LocalSocket controlSocket = null; try { if (tunnelForward) { try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { - videoSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); + if (video) { + videoSocket = localServerSocket.accept(); + firstSocket = videoSocket; } if (audio) { audioSocket = localServerSocket.accept(); + if (firstSocket == null) { + firstSocket = audioSocket; + } } if (control) { controlSocket = localServerSocket.accept(); + if (firstSocket == null) { + firstSocket = controlSocket; + } + } + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + firstSocket.getOutputStream().write(0); } } } else { - videoSocket = connect(socketName); + if (video) { + videoSocket = connect(socketName); + } if (audio) { audioSocket = connect(socketName); } @@ -106,10 +120,22 @@ public final class DesktopConnection implements Closeable { return new DesktopConnection(videoSocket, audioSocket, controlSocket); } + private LocalSocket getFirstSocket() { + if (videoSocket != null) { + return videoSocket; + } + if (audioSocket != null) { + return audioSocket; + } + return controlSocket; + } + public void close() throws IOException { - videoSocket.shutdownInput(); - videoSocket.shutdownOutput(); - videoSocket.close(); + if (videoSocket != null) { + videoSocket.shutdownInput(); + videoSocket.shutdownOutput(); + videoSocket.close(); + } if (audioSocket != null) { audioSocket.shutdownInput(); audioSocket.shutdownOutput(); @@ -130,7 +156,8 @@ public final class DesktopConnection implements Closeable { System.arraycopy(deviceNameBytes, 0, buffer, 0, len); // byte[] are always 0-initialized in java, no need to set '\0' explicitly - IO.writeFully(videoFd, buffer, 0, buffer.length); + FileDescriptor fd = getFirstSocket().getFileDescriptor(); + IO.writeFully(fd, buffer, 0, buffer.length); } public FileDescriptor getVideoFd() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index a34eb9b5..7bd94cb3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -9,6 +9,7 @@ public class Options { private Ln.Level logLevel = Ln.Level.DEBUG; private int scid = -1; // 31-bit non-negative value, or -1 + private boolean video = true; private boolean audio = true; private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; @@ -51,6 +52,10 @@ public class Options { return scid; } + public boolean getVideo() { + return video; + } + public boolean getAudio() { return audio; } @@ -200,6 +205,9 @@ public class Options { case "log_level": options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); break; + case "video": + options.video = Boolean.parseBoolean(value); + break; case "audio": options.audio = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4d72d1e8..db993830 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -95,6 +95,7 @@ public final class Server { int scid = options.getScid(); boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); + boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); @@ -121,7 +122,7 @@ public final class Server { List asyncProcessors = new ArrayList<>(); - DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte); + DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte); try { if (options.getSendDeviceMeta()) { connection.sendDeviceMeta(Device.getDeviceName()); @@ -147,11 +148,13 @@ public final class Server { asyncProcessors.add(audioRecorder); } - Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), - options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), - options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); - asyncProcessors.add(screenEncoder); + if (video) { + Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), + options.getSendFrameMeta()); + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), + options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); + asyncProcessors.add(screenEncoder); + } Completion completion = new Completion(asyncProcessors.size()); for (AsyncProcessor asyncProcessor : asyncProcessors) { From be86e14e051a40c62837f690282f2214f467778f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:18:27 +0200 Subject: [PATCH 0871/1133] Factorize record format parsing Convert either the filename extension or the explicit record format to a sc_record_format using the same function. PR #3978 --- app/src/cli.c | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 1558ef0c..619f372e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1479,18 +1479,27 @@ sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { } #endif +static enum sc_record_format +get_record_format(const char *name) { + if (!strcmp(name, "mp4")) { + return SC_RECORD_FORMAT_MP4; + } + if (!strcmp(name, "mkv")) { + return SC_RECORD_FORMAT_MKV; + } + return 0; +} + static bool parse_record_format(const char *optarg, enum sc_record_format *format) { - if (!strcmp(optarg, "mp4")) { - *format = SC_RECORD_FORMAT_MP4; - return true; - } - if (!strcmp(optarg, "mkv")) { - *format = SC_RECORD_FORMAT_MKV; - return true; + enum sc_record_format fmt = get_record_format(optarg); + if (!fmt) { + LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); + return false; } - LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); - return false; + + *format = fmt; + return true; } static bool @@ -1510,18 +1519,13 @@ parse_port(const char *optarg, uint16_t *port) { static enum sc_record_format guess_record_format(const char *filename) { - size_t len = strlen(filename); - if (len < 4) { + const char *dot = strrchr(filename, '.'); + if (!dot) { return 0; } - const char *ext = &filename[len - 4]; - if (!strcmp(ext, ".mp4")) { - return SC_RECORD_FORMAT_MP4; - } - if (!strcmp(ext, ".mkv")) { - return SC_RECORD_FORMAT_MKV; - } - return 0; + + const char *ext = dot + 1; + return get_record_format(ext); } static bool From 98f4f4e68a21dd072d83b1e474d71e6f28d9eb91 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:22:01 +0200 Subject: [PATCH 0872/1133] Refactor command line checks Several checks are performed when opts->record_filename is not NULL. Group them in a single block. PR #3978 --- app/src/cli.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 619f372e..4b895cd2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1959,19 +1959,21 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } - if (opts->record_filename && !opts->record_format) { - opts->record_format = guess_record_format(opts->record_filename); + if (opts->record_filename) { if (!opts->record_format) { - LOGE("No format specified for \"%s\" " - "(try with --record-format=mkv)", - opts->record_filename); - return false; + opts->record_format = guess_record_format(opts->record_filename); + if (!opts->record_format) { + LOGE("No format specified for \"%s\" " + "(try with --record-format=mkv)", + opts->record_filename); + return false; + } } - } - if (opts->record_filename && opts->audio_codec == SC_CODEC_RAW) { - LOGW("Recording does not support RAW audio codec"); - return false; + if (opts->audio_codec == SC_CODEC_RAW) { + LOGW("Recording does not support RAW audio codec"); + return false; + } } if (opts->audio_codec == SC_CODEC_RAW) { From d6bcde565f8155b6a51ce4682ada367fe62d5a18 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:23:51 +0200 Subject: [PATCH 0873/1133] Accept .m4a and .mka These are just aliases for mp4 and mkv when there is no video stream. PR #3978 --- app/src/cli.c | 12 ++++++++++++ app/src/options.h | 8 ++++++++ app/src/recorder.c | 11 ++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 4b895cd2..40fe242e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1487,6 +1487,12 @@ get_record_format(const char *name) { if (!strcmp(name, "mkv")) { return SC_RECORD_FORMAT_MKV; } + if (!strcmp(name, "m4a")) { + return SC_RECORD_FORMAT_M4A; + } + if (!strcmp(name, "mka")) { + return SC_RECORD_FORMAT_MKA; + } return 0; } @@ -1974,6 +1980,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGW("Recording does not support RAW audio codec"); return false; } + + if (opts->video + && sc_record_format_is_audio_only(opts->record_format)) { + LOGE("Audio container does not support video stream"); + return false; + } } if (opts->audio_codec == SC_CODEC_RAW) { diff --git a/app/src/options.h b/app/src/options.h index 4edf3f32..2424638f 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -21,8 +21,16 @@ enum sc_record_format { SC_RECORD_FORMAT_AUTO, SC_RECORD_FORMAT_MP4, SC_RECORD_FORMAT_MKV, + SC_RECORD_FORMAT_M4A, + SC_RECORD_FORMAT_MKA, }; +static inline bool +sc_record_format_is_audio_only(enum sc_record_format fmt) { + return fmt == SC_RECORD_FORMAT_M4A + || fmt == SC_RECORD_FORMAT_MKA; +} + enum sc_codec { SC_CODEC_H264, SC_CODEC_H265, diff --git a/app/src/recorder.c b/app/src/recorder.c index 5cbe6873..10102ec4 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -60,9 +60,14 @@ sc_recorder_queue_clear(struct sc_recorder_queue *queue) { static const char * sc_recorder_get_format_name(enum sc_record_format format) { switch (format) { - case SC_RECORD_FORMAT_MP4: return "mp4"; - case SC_RECORD_FORMAT_MKV: return "matroska"; - default: return NULL; + case SC_RECORD_FORMAT_MP4: + case SC_RECORD_FORMAT_M4A: + return "mp4"; + case SC_RECORD_FORMAT_MKV: + case SC_RECORD_FORMAT_MKA: + return "matroska"; + default: + return NULL; } } From 7321db6f28914df81e654cc4e2fbb3deec15fe2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:35:27 +0200 Subject: [PATCH 0874/1133] Add recording to opus file Use the FFmpeg opus muxer to record an opus file. PR #3978 --- app/src/cli.c | 10 ++++++++++ app/src/options.h | 4 +++- app/src/recorder.c | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 40fe242e..ee0319ee 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1493,6 +1493,9 @@ get_record_format(const char *name) { if (!strcmp(name, "mka")) { return SC_RECORD_FORMAT_MKA; } + if (!strcmp(name, "opus")) { + return SC_RECORD_FORMAT_OPUS; + } return 0; } @@ -1986,6 +1989,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("Audio container does not support video stream"); return false; } + + if (opts->record_format == SC_RECORD_FORMAT_OPUS + && opts->audio_codec != SC_CODEC_OPUS) { + LOGE("Recording to OPUS file requires an OPUS audio stream " + "(try with --audio-codec=opus)"); + return false; + } } if (opts->audio_codec == SC_CODEC_RAW) { diff --git a/app/src/options.h b/app/src/options.h index 2424638f..0547b4ec 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -23,12 +23,14 @@ enum sc_record_format { SC_RECORD_FORMAT_MKV, SC_RECORD_FORMAT_M4A, SC_RECORD_FORMAT_MKA, + SC_RECORD_FORMAT_OPUS, }; static inline bool sc_record_format_is_audio_only(enum sc_record_format fmt) { return fmt == SC_RECORD_FORMAT_M4A - || fmt == SC_RECORD_FORMAT_MKA; + || fmt == SC_RECORD_FORMAT_MKA + || fmt == SC_RECORD_FORMAT_OPUS; } enum sc_codec { diff --git a/app/src/recorder.c b/app/src/recorder.c index 10102ec4..b0c41df0 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -66,6 +66,8 @@ sc_recorder_get_format_name(enum sc_record_format format) { case SC_RECORD_FORMAT_MKV: case SC_RECORD_FORMAT_MKA: return "matroska"; + case SC_RECORD_FORMAT_OPUS: + return "opus"; default: return NULL; } From b11b363e8ec1666927ca4931e24520daa8ef7ef3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:38:32 +0200 Subject: [PATCH 0875/1133] Add recording to aac file It is just an alias for mp4. PR #3978 --- app/src/cli.c | 10 ++++++++++ app/src/options.h | 4 +++- app/src/recorder.c | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index ee0319ee..e5b18277 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1496,6 +1496,9 @@ get_record_format(const char *name) { if (!strcmp(name, "opus")) { return SC_RECORD_FORMAT_OPUS; } + if (!strcmp(name, "aac")) { + return SC_RECORD_FORMAT_AAC; + } return 0; } @@ -1996,6 +1999,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "(try with --audio-codec=opus)"); return false; } + + if (opts->record_format == SC_RECORD_FORMAT_AAC + && opts->audio_codec != SC_CODEC_AAC) { + LOGE("Recording to AAC file requires an AAC audio stream " + "(try with --audio-codec=aac)"); + return false; + } } if (opts->audio_codec == SC_CODEC_RAW) { diff --git a/app/src/options.h b/app/src/options.h index 0547b4ec..7c442149 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -24,13 +24,15 @@ enum sc_record_format { SC_RECORD_FORMAT_M4A, SC_RECORD_FORMAT_MKA, SC_RECORD_FORMAT_OPUS, + SC_RECORD_FORMAT_AAC, }; static inline bool sc_record_format_is_audio_only(enum sc_record_format fmt) { return fmt == SC_RECORD_FORMAT_M4A || fmt == SC_RECORD_FORMAT_MKA - || fmt == SC_RECORD_FORMAT_OPUS; + || fmt == SC_RECORD_FORMAT_OPUS + || fmt == SC_RECORD_FORMAT_AAC; } enum sc_codec { diff --git a/app/src/recorder.c b/app/src/recorder.c index b0c41df0..be1cbe71 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -62,6 +62,7 @@ sc_recorder_get_format_name(enum sc_record_format format) { switch (format) { case SC_RECORD_FORMAT_MP4: case SC_RECORD_FORMAT_M4A: + case SC_RECORD_FORMAT_AAC: return "mp4"; case SC_RECORD_FORMAT_MKV: case SC_RECORD_FORMAT_MKA: From a166eee909a66385dc5b00169f4f61b490e163dd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:46:44 +0200 Subject: [PATCH 0876/1133] Upgrade FFmpeg build to 6.0-scrcpy-3 Use a build which includes the opus muxer, to support recording to .opus files. Refs PR #3978 --- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index b156099a..3533ded4 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.0-scrcpy-2 +VERSION=6.0-scrcpy-3 DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=98ef97f8607c97a5c4f9c5a0a991b78f105d002a3619145011d16ffb92501b14 +SHA256SUM=36829d98ac4454d7092c72ddb92faa20b60450bc0fe8873076efb0858cdcbc2c if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index 18834af4..e8e8c7e8 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win32' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win32' prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 1c7c0875..d1de3c21 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win64' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win64' prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 0f5cbe24..9fbd67c6 100644 --- a/release.mk +++ b/release.mk @@ -94,11 +94,11 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -113,11 +113,11 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From d50055021284bf0da49a5ff7810b48fe0f7c492d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 13:04:11 +0200 Subject: [PATCH 0877/1133] Update audio recording documentation Document how to record audio-only to .opus and .aac files. PR #3978 --- doc/recording.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/recording.md b/doc/recording.md index e3e2cf68..7d66f2ff 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -16,7 +16,9 @@ scrcpy --no-audio --record=file.mp4 To record only the audio: ```bash -scrcpy --no-video --record=file.mp4 +scrcpy --no-video --record=file.opus +scrcpy --no-video --audio-codec=aac --record-file=file.aac +# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac ``` To disable mirroring while recording: From 7d33798b40bf9c371d417c4911d67141d62365ec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 May 2023 14:27:23 +0200 Subject: [PATCH 0878/1133] Upgrade FFmpeg build to 6.0-scrcpy-4 Use FFmpeg DLLs which do not depend on zlib1.dll. --- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 18 ++++++++---------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index 3533ded4..9019cc2d 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.0-scrcpy-3 +VERSION=6.0-scrcpy-4 DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=36829d98ac4454d7092c72ddb92faa20b60450bc0fe8873076efb0858cdcbc2c +SHA256SUM=39274b321491ce83e76cab5d24e7cbe3f402d3ccf382f739b13be5651c146b60 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index e8e8c7e8..f3fded40 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win32' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32' prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index d1de3c21..1b02b93f 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win64' +prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64' prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 9fbd67c6..e41a45ad 100644 --- a/release.mk +++ b/release.mk @@ -94,11 +94,10 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -113,11 +112,10 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 958f22490b3ab86677bd8b6126ddaa4de4082798 Mon Sep 17 00:00:00 2001 From: Marek Madejski Date: Tue, 16 May 2023 12:53:58 +0200 Subject: [PATCH 0879/1133] Document installation via winget on Windows PR #4005 Refs #1444 Refs #3932 Signed-off-by: Romain Vimont --- doc/windows.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/windows.md b/doc/windows.md index 521ad45e..2cbd99b6 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -15,7 +15,13 @@ Download the [latest release]: and extract it. -Alternatively, you could install it from packages manager, like [Chocolatey]: +Alternatively, you could install it from packages manager, like [Winget]: + +```bash +winget install scrcpy +``` + +or [Chocolatey]: ```bash choco install scrcpy @@ -30,6 +36,7 @@ scoop install scrcpy scoop install adb # if you don't have it yet ``` +[Winget]: https://github.com/microsoft/winget-cli [Chocolatey]: https://chocolatey.org/ [Scoop]: https://scoop.sh From 6298ef095ffa9a2cdd2b4d245e71280743b5a59e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 8 May 2023 18:16:38 +0200 Subject: [PATCH 0880/1133] Accept texture failures When the scrcpy window is minimized on Windows with D3D9, texture creation and update fail. In that case, do not terminate scrcpy. Instead, store the pending size or frame to update, to attempt again during the next update or rendering. Fixes #3947 --- app/src/display.c | 120 ++++++++++++++++++++++++++++++++++++++++++---- app/src/display.h | 20 ++++++-- app/src/screen.c | 28 +++++++---- 3 files changed, 147 insertions(+), 21 deletions(-) diff --git a/app/src/display.c b/app/src/display.c index 4852952b..dabc2cb7 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -62,11 +62,17 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { LOGD("Trilinear filtering disabled (not an OpenGL renderer"); } + display->pending.flags = 0; + display->pending.frame = NULL; + return true; } void sc_display_destroy(struct sc_display *display) { + if (display->pending.frame) { + av_frame_free(&display->pending.frame); + } #ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE SDL_GL_DeleteContext(display->gl_context); #endif @@ -84,7 +90,7 @@ sc_display_create_texture(struct sc_display *display, SDL_TEXTUREACCESS_STREAMING, size.width, size.height); if (!texture) { - LOGE("Could not create texture: %s", SDL_GetError()); + LOGD("Could not create texture: %s", SDL_GetError()); return NULL; } @@ -104,8 +110,66 @@ sc_display_create_texture(struct sc_display *display, return texture; } -bool -sc_display_set_texture_size(struct sc_display *display, struct sc_size size) { +static inline void +sc_display_set_pending_size(struct sc_display *display, struct sc_size size) { + assert(!display->texture); + display->pending.size = size; + display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE; +} + +static bool +sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) { + if (!display->pending.frame) { + display->pending.frame = av_frame_alloc(); + if (!display->pending.frame) { + LOG_OOM(); + return false; + } + } + + int r = av_frame_ref(display->pending.frame, frame); + if (r) { + LOGE("Could not ref frame: %d", r); + return false; + } + + display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME; + + return true; +} + +static bool +sc_display_apply_pending(struct sc_display *display) { + if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) { + assert(!display->texture); + display->texture = + sc_display_create_texture(display, display->pending.size); + if (!display->texture) { + return false; + } + + display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE; + } + + if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) { + assert(display->pending.frame); + bool ok = sc_display_update_texture(display, display->pending.frame); + if (!ok) { + return false; + } + + av_frame_unref(display->pending.frame); + display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME; + } + + return true; +} + +static bool +sc_display_set_texture_size_internal(struct sc_display *display, + struct sc_size size) { + assert(size.width && size.height); + if (display->texture) { SDL_DestroyTexture(display->texture); } @@ -119,14 +183,27 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) { return true; } -bool -sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { +enum sc_display_result +sc_display_set_texture_size(struct sc_display *display, struct sc_size size) { + bool ok = sc_display_set_texture_size_internal(display, size); + if (!ok) { + sc_display_set_pending_size(display, size); + return SC_DISPLAY_RESULT_PENDING; + + } + + return SC_DISPLAY_RESULT_OK; +} + +static bool +sc_display_update_texture_internal(struct sc_display *display, + const AVFrame *frame) { int ret = SDL_UpdateYUVTexture(display->texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); if (ret) { - LOGE("Could not update texture: %s", SDL_GetError()); + LOGD("Could not update texture: %s", SDL_GetError()); return false; } @@ -139,11 +216,34 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { return true; } -bool +enum sc_display_result +sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { + bool ok = sc_display_update_texture_internal(display, frame); + if (!ok) { + ok = sc_display_set_pending_frame(display, frame); + if (!ok) { + LOGE("Could not set pending frame"); + return SC_DISPLAY_RESULT_ERROR; + } + + return SC_DISPLAY_RESULT_PENDING; + } + + return SC_DISPLAY_RESULT_OK; +} + +enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, unsigned rotation) { SDL_RenderClear(display->renderer); + if (display->pending.flags) { + bool ok = sc_display_apply_pending(display); + if (!ok) { + return SC_DISPLAY_RESULT_PENDING; + } + } + SDL_Renderer *renderer = display->renderer; SDL_Texture *texture = display->texture; @@ -151,7 +251,7 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, int ret = SDL_RenderCopy(renderer, texture, NULL, geometry); if (ret) { LOGE("Could not render texture: %s", SDL_GetError()); - return false; + return SC_DISPLAY_RESULT_ERROR; } } else { // rotation in RenderCopyEx() is clockwise, while screen->rotation is @@ -176,10 +276,10 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, NULL, 0); if (ret) { LOGE("Could not render texture: %s", SDL_GetError()); - return false; + return SC_DISPLAY_RESULT_ERROR; } } SDL_RenderPresent(display->renderer); - return true; + return SC_DISPLAY_RESULT_OK; } diff --git a/app/src/display.h b/app/src/display.h index e30b4822..6b83a5c9 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -24,6 +24,20 @@ struct sc_display { #endif bool mipmaps; + + struct { +#define SC_DISPLAY_PENDING_FLAG_SIZE 1 +#define SC_DISPLAY_PENDING_FLAG_FRAME 2 + int8_t flags; + struct sc_size size; + AVFrame *frame; + } pending; +}; + +enum sc_display_result { + SC_DISPLAY_RESULT_OK, + SC_DISPLAY_RESULT_PENDING, + SC_DISPLAY_RESULT_ERROR, }; bool @@ -32,13 +46,13 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps); void sc_display_destroy(struct sc_display *display); -bool +enum sc_display_result sc_display_set_texture_size(struct sc_display *display, struct sc_size size); -bool +enum sc_display_result sc_display_update_texture(struct sc_display *display, const AVFrame *frame); -bool +enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, unsigned rotation); diff --git a/app/src/screen.c b/app/src/screen.c index 70665ed6..14fd2a00 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -249,9 +249,9 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { sc_screen_update_content_rect(screen); } - bool ok = sc_display_render(&screen->display, &screen->rect, - screen->rotation); - (void) ok; // error already logged + enum sc_display_result res = + sc_display_render(&screen->display, &screen->rect, screen->rotation); + (void) res; // any error already logged } #if defined(__APPLE__) || defined(__WINDOWS__) @@ -583,15 +583,17 @@ sc_screen_init_size(struct sc_screen *screen) { get_rotated_size(screen->frame_size, screen->rotation); screen->content_size = content_size; - return sc_display_set_texture_size(&screen->display, screen->frame_size); + enum sc_display_result res = + sc_display_set_texture_size(&screen->display, screen->frame_size); + return res != SC_DISPLAY_RESULT_ERROR; } // recreate the texture and resize the window if the frame size has changed -static bool +static enum sc_display_result prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { if (screen->frame_size.width == new_frame_size.width && screen->frame_size.height == new_frame_size.height) { - return true; + return SC_DISPLAY_RESULT_OK; } // frame dimension changed @@ -615,13 +617,23 @@ sc_screen_update_frame(struct sc_screen *screen) { sc_fps_counter_add_rendered_frame(&screen->fps_counter); struct sc_size new_frame_size = {frame->width, frame->height}; - if (!prepare_for_frame(screen, new_frame_size)) { + enum sc_display_result res = prepare_for_frame(screen, new_frame_size); + if (res == SC_DISPLAY_RESULT_ERROR) { return false; } + if (res == SC_DISPLAY_RESULT_PENDING) { + // Not an error, but do not continue + return true; + } - if (!sc_display_update_texture(&screen->display, frame)) { + res = sc_display_update_texture(&screen->display, frame); + if (res == SC_DISPLAY_RESULT_ERROR) { return false; } + if (res == SC_DISPLAY_RESULT_PENDING) { + // Not an error, but do not continue + return true; + } if (!screen->has_frame) { screen->has_frame = true; From e926bf1fe8546dc3df129d1ecab555539af5c19e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 8 May 2023 21:02:01 +0200 Subject: [PATCH 0881/1133] Delay window resize when minimized On some window managers (e.g. on Windows), performing a resize while the window is minimized does nothing (the restored window keeps its old size). Therefore, like for maximized and fullscreen states, wait for the window to be restored to apply a resize. Refs #3947 --- app/src/screen.c | 17 ++++++++++++----- app/src/screen.h | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/screen.c b/app/src/screen.c index 14fd2a00..2724a266 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -56,6 +56,7 @@ static void set_window_size(struct sc_screen *screen, struct sc_size new_size) { assert(!screen->fullscreen); assert(!screen->maximized); + assert(!screen->minimized); SDL_SetWindowSize(screen->window, new_size.width, new_size.height); } @@ -359,6 +360,7 @@ sc_screen_init(struct sc_screen *screen, screen->has_frame = false; screen->fullscreen = false; screen->maximized = false; + screen->minimized = false; screen->mouse_capture_key_pressed = 0; screen->req.x = params->window_x; @@ -531,11 +533,11 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size, static void set_content_size(struct sc_screen *screen, struct sc_size new_content_size) { - if (!screen->fullscreen && !screen->maximized) { + if (!screen->fullscreen && !screen->maximized && !screen->minimized) { resize_for_content(screen, screen->content_size, new_content_size); } else if (!screen->resize_pending) { // Store the windowed size to be able to compute the optimal size once - // fullscreen and maximized are disabled + // fullscreen/maximized/minimized are disabled screen->windowed_content_size = screen->content_size; screen->resize_pending = true; } @@ -547,6 +549,7 @@ static void apply_pending_resize(struct sc_screen *screen) { assert(!screen->fullscreen); assert(!screen->maximized); + assert(!screen->minimized); if (screen->resize_pending) { resize_for_content(screen, screen->windowed_content_size, screen->content_size); @@ -659,7 +662,7 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) { } screen->fullscreen = !screen->fullscreen; - if (!screen->fullscreen && !screen->maximized) { + if (!screen->fullscreen && !screen->maximized && !screen->minimized) { apply_pending_resize(screen); } @@ -669,7 +672,7 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) { void sc_screen_resize_to_fit(struct sc_screen *screen) { - if (screen->fullscreen || screen->maximized) { + if (screen->fullscreen || screen->maximized || screen->minimized) { return; } @@ -693,7 +696,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) { void sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) { - if (screen->fullscreen) { + if (screen->fullscreen || screen->minimized) { return; } @@ -750,6 +753,9 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { case SDL_WINDOWEVENT_MAXIMIZED: screen->maximized = true; break; + case SDL_WINDOWEVENT_MINIMIZED: + screen->minimized = true; + break; case SDL_WINDOWEVENT_RESTORED: if (screen->fullscreen) { // On Windows, in maximized+fullscreen, disabling @@ -760,6 +766,7 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { break; } screen->maximized = false; + screen->minimized = false; apply_pending_resize(screen); sc_screen_render(screen, true); break; diff --git a/app/src/screen.h b/app/src/screen.h index 2c032119..acbaab4b 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -56,6 +56,7 @@ struct sc_screen { bool has_frame; bool fullscreen; bool maximized; + bool minimized; // To enable/disable mouse capture, a mouse capture key (LALT, LGUI or // RGUI) must be pressed. This variable tracks the pressed capture key. From 38900d77307d24690168ad05e4f95fe238d9e83a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 21:31:47 +0200 Subject: [PATCH 0882/1133] Extract audio source to a static constant For consistency with the other parameters. --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index dbb38dd2..ef9ef30d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -18,6 +18,7 @@ import java.nio.ByteBuffer; public final class AudioCapture { + public static final int SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX; public static final int SAMPLE_RATE = 48000; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; public static final int CHANNELS = 2; @@ -50,7 +51,7 @@ public final class AudioCapture { // On older APIs, Workarounds.fillAppInfo() must be called beforehand builder.setContext(FakeContext.get()); } - builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX); + builder.setAudioSource(SOURCE); builder.setAudioFormat(createAudioFormat()); int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT); // This buffer size does not impact latency From 597d2ccc01b6fe32fe21d315182eac4a523e014d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 22:13:02 +0200 Subject: [PATCH 0883/1133] Rename FORMAT to ENCODING The AudioFormat contains several properties. This specific value is named "encoding". --- .../src/main/java/com/genymobile/scrcpy/AudioCapture.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index ef9ef30d..92ecd839 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -22,7 +22,7 @@ public final class AudioCapture { public static final int SAMPLE_RATE = 48000; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; public static final int CHANNELS = 2; - public static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; + public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int BYTES_PER_SAMPLE = 2; private AudioRecord recorder; @@ -37,7 +37,7 @@ public final class AudioCapture { private static AudioFormat createAudioFormat() { AudioFormat.Builder builder = new AudioFormat.Builder(); - builder.setEncoding(FORMAT); + builder.setEncoding(ENCODING); builder.setSampleRate(SAMPLE_RATE); builder.setChannelMask(CHANNEL_CONFIG); return builder.build(); @@ -53,7 +53,7 @@ public final class AudioCapture { } builder.setAudioSource(SOURCE); builder.setAudioFormat(createAudioFormat()); - int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT); + int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING); // This buffer size does not impact latency builder.setBufferSizeInBytes(8 * minBufferSize); return builder.build(); From cab354102d722c27660e83817ecff6fd3cb9cfc7 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Mon, 27 Mar 2023 01:09:07 +0800 Subject: [PATCH 0884/1133] Create AudioRecord by reflection as a fallback Some devices (Vivo phones) fail to create an AudioRecord from an AudioRecord.Builder (which throws a NullPointerException). In that case, create an AudioRecord instance directly by reflection. The AOSP version of AudioRecord constructor code can be found at: - Android 11 (R): - Android 12 (S): - Android 13 (T, functionally identical to Android 12): - Android 14 (U): Not released, but expected to change PR #3862 Fixes #3805 Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/AudioCapture.java | 10 +- .../com/genymobile/scrcpy/Workarounds.java | 145 ++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 92ecd839..b8fc076b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -22,6 +22,7 @@ public final class AudioCapture { public static final int SAMPLE_RATE = 48000; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; public static final int CHANNELS = 2; + public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT; public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int BYTES_PER_SAMPLE = 2; @@ -98,7 +99,14 @@ public final class AudioCapture { } private void startRecording() { - recorder = createAudioRecord(); + try { + recorder = createAudioRecord(); + } catch (NullPointerException e) { + // Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones: + // - + // - + recorder = Workarounds.createAudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING); + } recorder.startRecording(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 89380ece..b343a344 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -1,13 +1,22 @@ package com.genymobile.scrcpy; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Application; +import android.content.AttributionSource; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.os.Build; import android.os.Looper; +import android.os.Parcel; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; public final class Workarounds { @@ -95,4 +104,140 @@ public final class Workarounds { Ln.d("Could not fill app context: " + throwable.getMessage()); } } + + @TargetApi(Build.VERSION_CODES.R) + @SuppressLint({"WrongConstant", "MissingPermission", "BlockedPrivateApi", "SoonBlockedPrivateApi"}) + public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) { + // Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment. + // + // This method invokes the `AudioRecord(long nativeRecordInJavaObj)` constructor to create an empty `AudioRecord` instance, then uses + // reflections to initialize it like the normal constructor do (or the `AudioRecord.Builder.build()` method do). + // As a result, the modified code was not executed. + try { + // AudioRecord audioRecord = new AudioRecord(0L); + Constructor audioRecordConstructor = AudioRecord.class.getDeclaredConstructor(long.class); + audioRecordConstructor.setAccessible(true); + AudioRecord audioRecord = audioRecordConstructor.newInstance(0L); + + // audioRecord.mRecordingState = RECORDSTATE_STOPPED; + Field mRecordingStateField = AudioRecord.class.getDeclaredField("mRecordingState"); + mRecordingStateField.setAccessible(true); + mRecordingStateField.set(audioRecord, AudioRecord.RECORDSTATE_STOPPED); + + Looper looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + + // audioRecord.mInitializationLooper = looper; + Field mInitializationLooperField = AudioRecord.class.getDeclaredField("mInitializationLooper"); + mInitializationLooperField.setAccessible(true); + mInitializationLooperField.set(audioRecord, looper); + + // Create `AudioAttributes` with fixed capture preset + int capturePreset = source; + AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder(); + Method setInternalCapturePresetMethod = AudioAttributes.Builder.class.getMethod("setInternalCapturePreset", int.class); + setInternalCapturePresetMethod.invoke(audioAttributesBuilder, capturePreset); + AudioAttributes attributes = audioAttributesBuilder.build(); + + // audioRecord.mAudioAttributes = attributes; + Field mAudioAttributesField = AudioRecord.class.getDeclaredField("mAudioAttributes"); + mAudioAttributesField.setAccessible(true); + mAudioAttributesField.set(audioRecord, attributes); + + // audioRecord.audioParamCheck(capturePreset, sampleRate, encoding); + Method audioParamCheckMethod = AudioRecord.class.getDeclaredMethod("audioParamCheck", int.class, int.class, int.class); + audioParamCheckMethod.setAccessible(true); + audioParamCheckMethod.invoke(audioRecord, capturePreset, sampleRate, encoding); + + // audioRecord.mChannelCount = channels + Field mChannelCountField = AudioRecord.class.getDeclaredField("mChannelCount"); + mChannelCountField.setAccessible(true); + mChannelCountField.set(audioRecord, channels); + + // audioRecord.mChannelMask = channelMask + Field mChannelMaskField = AudioRecord.class.getDeclaredField("mChannelMask"); + mChannelMaskField.setAccessible(true); + mChannelMaskField.set(audioRecord, channelMask); + + int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, encoding); + int bufferSizeInBytes = minBufferSize * 8; + + // audioRecord.audioBuffSizeCheck(bufferSizeInBytes) + Method audioBuffSizeCheckMethod = AudioRecord.class.getDeclaredMethod("audioBuffSizeCheck", int.class); + audioBuffSizeCheckMethod.setAccessible(true); + audioBuffSizeCheckMethod.invoke(audioRecord, bufferSizeInBytes); + + final int channelIndexMask = 0; + + int[] sampleRateArray = new int[]{sampleRate}; + int[] session = new int[]{AudioManager.AUDIO_SESSION_ID_GENERATE}; + + int initResult; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + // private native final int native_setup(Object audiorecord_this, + // Object /*AudioAttributes*/ attributes, + // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + // int buffSizeInBytes, int[] sessionId, String opPackageName, + // long nativeRecordInJavaObj); + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class, + int.class, int.class, int.class, int[].class, String.class, long.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, sampleRateArray, + channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, FakeContext.get().getOpPackageName(), + 0L); + } else { + // Assume `context` is never `null` + AttributionSource attributionSource = FakeContext.get().getAttributionSource(); + + // Assume `attributionSource.getPackageName()` is never null + + // ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState() + Method asScopedParcelStateMethod = AttributionSource.class.getDeclaredMethod("asScopedParcelState"); + asScopedParcelStateMethod.setAccessible(true); + + try (AutoCloseable attributionSourceState = (AutoCloseable) asScopedParcelStateMethod.invoke(attributionSource)) { + Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel"); + Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState); + + // private native int native_setup(Object audiorecordThis, + // Object /*AudioAttributes*/ attributes, + // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + // int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, + // long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class, + int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, sampleRateArray, + channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0); + } + } + + if (initResult != AudioRecord.SUCCESS) { + Ln.e("Error code " + initResult + " when initializing native AudioRecord object."); + throw new RuntimeException("Cannot create AudioRecord"); + } + + // mSampleRate = sampleRate[0] + Field mSampleRateField = AudioRecord.class.getDeclaredField("mSampleRate"); + mSampleRateField.setAccessible(true); + mSampleRateField.set(audioRecord, sampleRateArray[0]); + + // audioRecord.mSessionId = session[0] + Field mSessionIdField = AudioRecord.class.getDeclaredField("mSessionId"); + mSessionIdField.setAccessible(true); + mSessionIdField.set(audioRecord, session[0]); + + // audioRecord.mState = AudioRecord.STATE_INITIALIZED + Field mStateField = AudioRecord.class.getDeclaredField("mState"); + mStateField.setAccessible(true); + mStateField.set(audioRecord, AudioRecord.STATE_INITIALIZED); + + return audioRecord; + } catch (Exception e) { + Ln.e("Failed to invoke AudioRecord..", e); + throw new RuntimeException("Cannot create AudioRecord"); + } + } } From a2c89100065f684dcf8859be4ea5274a7e6d5b26 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 20:22:35 +0200 Subject: [PATCH 0885/1133] Rename --no-mirror to --no-playback This option impacts video and audio _playback_. For example, if we use V4L2, the device is still "mirrored" (via V4L2), even if playback is disabled. Therefore, "playback" is more approriate than "mirror". The initial option --no-display option was renamed to --no-mirror by commit 6928acdeac29eee404a7c7014654965ef5128b88, but this has never been released, so it is ok to rename it one more time. Refs #3978 PR #4033 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 4 ++-- app/src/cli.c | 27 +++++++++++++-------------- app/src/options.c | 2 +- app/src/options.h | 2 +- app/src/scrcpy.c | 18 +++++++++--------- app/tests/test_cli.c | 8 ++++---- doc/recording.md | 4 ++-- doc/v4l2.md | 2 +- doc/video.md | 8 ++++---- 11 files changed, 39 insertions(+), 40 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index cdc9270f..dccb68e5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -33,7 +33,7 @@ _scrcpy() { --no-clipboard-autosync --no-downsize-on-error -n --no-control - -N --no-mirror + -N --no-playback --no-key-repeat --no-mipmaps --no-power-on diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 5e40b2fb..afc6b1e6 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -39,7 +39,7 @@ arguments=( '--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' - {-N,--no-mirror}'[Do not mirror device \(only when recording or V4L2 sink is enabled\)]' + {-N,--no-playback}'[Disable video and audio playback]' '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 29d14b58..a622d22b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -210,8 +210,8 @@ This option disables this behavior. Disable device control (mirror the device in read\-only). .TP -.B \-N, \-\-no\-mirror -Do not mirror device video or audio on the computer (only when recording or V4L2 sink is enabled). +.B \-N, \-\-no\-playback +Disable video and audio playback on the computer. .TP .B \-\-no\-key\-repeat diff --git a/app/src/cli.c b/app/src/cli.c index e5b18277..dbf774a8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -382,9 +382,8 @@ static const struct sc_option options[] = { }, { .shortopt = 'N', - .longopt = "no-mirror", - .text = "Do not mirror device video or audio on the computer (only " - "when recording or V4L2 sink is enabled).", + .longopt = "no-playback", + .text = "Disable video and audio playback on the computer.", }, { // deprecated @@ -1671,10 +1670,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->control = false; break; case OPT_NO_DISPLAY: - LOGW("--no-display is deprecated, use --no-mirror instead."); + LOGW("--no-display is deprecated, use --no-playback instead."); // fall through case 'N': - opts->mirror = false; + opts->playback = false; break; case 'p': if (!parse_port_range(optarg, &opts->port_range)) { @@ -1924,8 +1923,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #ifdef HAVE_V4L2 - if (!opts->mirror && !opts->record_filename && !opts->v4l2_device) { - LOGE("-N/--no-mirror requires either screen recording (-r/--record)" + if (!opts->playback && !opts->record_filename && !opts->v4l2_device) { + LOGE("-N/--no-playback requires either screen recording (-r/--record)" " or sink to v4l2loopback device (--v4l2-sink)"); return false; } @@ -1949,14 +1948,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } #else - if (!opts->mirror && !opts->record_filename) { - LOGE("-N/--no-mirror requires screen recording (-r/--record)"); + if (!opts->playback && !opts->record_filename) { + LOGE("-N/--no-playback requires screen recording (-r/--record)"); return false; } #endif - if (opts->audio && !opts->mirror && !opts->record_filename) { - LOGI("No mirror and no recording: audio disabled"); + if (opts->audio && !opts->playback && !opts->record_filename) { + LOGI("No playback and no recording: audio disabled"); opts->audio = false; } @@ -2089,11 +2088,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], #endif #ifdef HAVE_USB - if (!(opts->mirror && opts->video) && !opts->otg) { + if (!(opts->playback && opts->video) && !opts->otg) { #else - if (!(opts->mirror && opts->video)) { + if (!(opts->playback && opts->video)) { #endif - // If video mirroring is disabled and OTG are disabled, then there is + // If video playback is disabled and OTG are disabled, then there is // no way to control the device. opts->control = false; } diff --git a/app/src/options.c b/app/src/options.c index 12283952..b021921f 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -52,7 +52,7 @@ const struct scrcpy_options scrcpy_options_default = { .fullscreen = false, .always_on_top = false, .control = true, - .mirror = true, + .playback = true, .turn_screen_off = false, .key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .window_borderless = false, diff --git a/app/src/options.h b/app/src/options.h index 7c442149..0c886de0 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -147,7 +147,7 @@ struct scrcpy_options { bool fullscreen; bool always_on_top; bool control; - bool mirror; + bool playback; bool turn_screen_off; enum sc_key_inject_mode key_inject_mode; bool window_borderless; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 39a2f1a4..3334f57d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -137,7 +137,7 @@ sdl_set_hints(const char *render_driver) { } static void -sdl_configure(bool mirror, bool disable_screensaver) { +sdl_configure(bool playback, bool disable_screensaver) { #ifdef _WIN32 // Clean up properly on Ctrl+C on Windows bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); @@ -146,7 +146,7 @@ sdl_configure(bool mirror, bool disable_screensaver) { } #endif // _WIN32 - if (!mirror) { + if (!playback) { return; } @@ -386,10 +386,10 @@ scrcpy(struct scrcpy_options *options) { goto end; } - if (options->mirror) { + if (options->playback) { sdl_set_hints(options->render_driver); - // Initialize SDL video and audio in addition if mirroring is enabled + // Initialize SDL video and audio in addition if playback is enabled if (options->video && SDL_Init(SDL_INIT_VIDEO)) { LOGE("Could not initialize SDL video: %s", SDL_GetError()); goto end; @@ -401,7 +401,7 @@ scrcpy(struct scrcpy_options *options) { } } - sdl_configure(options->mirror, options->disable_screensaver); + sdl_configure(options->playback, options->disable_screensaver); // Await for server without blocking Ctrl+C handling bool connected; @@ -427,7 +427,7 @@ scrcpy(struct scrcpy_options *options) { struct sc_file_pusher *fp = NULL; - assert(!options->control || options->mirror); // control implies mirror + assert(!options->control || options->playback); // control implies playback if (options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { @@ -453,8 +453,8 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, options); } - bool needs_video_decoder = options->mirror && options->video; - bool needs_audio_decoder = options->mirror && options->audio; + bool needs_video_decoder = options->playback && options->video; + bool needs_audio_decoder = options->playback && options->audio; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; #endif @@ -650,7 +650,7 @@ aoa_hid_end: // There is a controller if and only if control is enabled assert(options->control == !!controller); - if (options->mirror) { + if (options->playback) { const char *window_title = options->window_title ? options->window_title : info->device_name; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 1f00cb90..ec1a9531 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -53,7 +53,7 @@ static void test_options(void) { "--max-size", "1024", "--lock-video-orientation=2", // optional arguments require '=' // "--no-control" is not compatible with "--turn-screen-off" - // "--no-mirror" is not compatible with "--fulscreen" + // "--no-playback" is not compatible with "--fulscreen" "--port", "1234:1236", "--push-target", "/sdcard/Movies", "--record", "file", @@ -108,8 +108,8 @@ static void test_options2(void) { char *argv[] = { "scrcpy", "--no-control", - "--no-mirror", - "--record", "file.mp4", // cannot enable --no-mirror without recording + "--no-playback", + "--record", "file.mp4", // cannot enable --no-playback without recording }; bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv); @@ -117,7 +117,7 @@ static void test_options2(void) { const struct scrcpy_options *opts = &args.opts; assert(!opts->control); - assert(!opts->mirror); + assert(!opts->playback); assert(!strcmp(opts->record_filename, "file.mp4")); assert(opts->record_format == SC_RECORD_FORMAT_MP4); } diff --git a/doc/recording.md b/doc/recording.md index 7d66f2ff..6f721062 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -21,10 +21,10 @@ scrcpy --no-video --audio-codec=aac --record-file=file.aac # .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac ``` -To disable mirroring while recording: +To disable playback while recording: ```bash -scrcpy --no-mirror --record=file.mp4 +scrcpy --no-playback --record=file.mp4 scrcpy -Nr file.mkv # interrupt recording with Ctrl+C ``` diff --git a/doc/v4l2.md b/doc/v4l2.md index 2c6e2cfc..62480478 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -35,7 +35,7 @@ To start `scrcpy` using a v4l2 sink: ```bash scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-mirror # disable mirroring window +scrcpy --v4l2-sink=/dev/videoN --no-playback # disable playback window ``` (replace `N` with the device ID, check with `ls /dev/video*`) diff --git a/doc/video.md b/doc/video.md index 303d9048..757d9324 100644 --- a/doc/video.md +++ b/doc/video.md @@ -159,15 +159,15 @@ scrcpy --display-buffer=50 --v4l2-buffer=300 ``` -## No mirror +## No playback -It is possible to capture an Android device without displaying a mirroring +It is possible to capture an Android device without displaying a playback window. This option is available if either [recording](recording.md) or [v4l2](#video4linux) is enabled: ```bash -scrcpy --v4l2-sink=/dev/video2 --no-mirror -scrcpy --record=file.mkv --no-mirror +scrcpy --v4l2-sink=/dev/video2 --no-playback +scrcpy --record=file.mkv --no-playback ``` From e71f5358b3ebf44569b1f3798c13ed65554b69b9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 20:25:01 +0200 Subject: [PATCH 0886/1133] Reorder command line options checks Perform checks that impact the options first. --- app/src/cli.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index dbf774a8..60a007e9 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1922,6 +1922,21 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } +#ifdef HAVE_USB + if (!(opts->playback && opts->video) && !opts->otg) { +#else + if (!(opts->playback && opts->video)) { +#endif + // If video playback is disabled and OTG are disabled, then there is + // no way to control the device. + opts->control = false; + } + + if (!opts->video) { + // If video is disabled, then scrcpy must exit on audio failure. + opts->require_audio = true; + } + #ifdef HAVE_V4L2 if (!opts->playback && !opts->record_filename && !opts->v4l2_device) { LOGE("-N/--no-playback requires either screen recording (-r/--record)" @@ -2087,21 +2102,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif -#ifdef HAVE_USB - if (!(opts->playback && opts->video) && !opts->otg) { -#else - if (!(opts->playback && opts->video)) { -#endif - // If video playback is disabled and OTG are disabled, then there is - // no way to control the device. - opts->control = false; - } - - if (!opts->video) { - // If video is disabled, then scrcpy must exit on audio failure. - opts->require_audio = true; - } - return true; } From f46758d1c5ccef668435d712958a6c5acfb1c44c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 20:36:27 +0200 Subject: [PATCH 0887/1133] Fix V4L2 error message when disabled For consistency, use the same error message for --v4l2-sink and --v4l2-buffer. --- app/src/cli.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 60a007e9..10565da9 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1871,7 +1871,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } break; #else - LOGE("V4L2 (--v4l2-buffer) is only available on Linux."); + LOGE("V4L2 (--v4l2-buffer) is disabled (or unsupported on this " + "platform)."); return false; #endif case OPT_LIST_ENCODERS: From 6ad46d70b8190a8f409cdd62870f51a6e24baa50 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 20:39:13 +0200 Subject: [PATCH 0888/1133] Define v4l2_buffer only if HAVE_V4L2 If V4L2 support is disabled, there is no v4l2 buffer option. --- app/src/options.c | 8 ++++---- app/src/options.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/options.c b/app/src/options.c index b021921f..fc3b1790 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -11,9 +11,6 @@ const struct scrcpy_options scrcpy_options_default = { .audio_codec_options = NULL, .video_encoder = NULL, .audio_encoder = NULL, -#ifdef HAVE_V4L2 - .v4l2_device = NULL, -#endif .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, @@ -42,9 +39,12 @@ const struct scrcpy_options scrcpy_options_default = { .window_height = 0, .display_id = 0, .display_buffer = 0, - .v4l2_buffer = 0, .audio_buffer = SC_TICK_FROM_MS(50), .audio_output_buffer = SC_TICK_FROM_MS(5), +#ifdef HAVE_V4L2 + .v4l2_device = NULL, + .v4l2_buffer = 0, +#endif #ifdef HAVE_USB .otg = false, #endif diff --git a/app/src/options.h b/app/src/options.h index 0c886de0..e3cc8064 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -112,9 +112,6 @@ struct scrcpy_options { const char *audio_codec_options; const char *video_encoder; const char *audio_encoder; -#ifdef HAVE_V4L2 - const char *v4l2_device; -#endif enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; @@ -137,9 +134,12 @@ struct scrcpy_options { uint16_t window_height; uint32_t display_id; sc_tick display_buffer; - sc_tick v4l2_buffer; sc_tick audio_buffer; sc_tick audio_output_buffer; +#ifdef HAVE_V4L2 + const char *v4l2_device; + sc_tick v4l2_buffer; +#endif #ifdef HAVE_USB bool otg; #endif From 751c09f47a025f0affbd44db2e9a1f343d827cb5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 20:44:17 +0200 Subject: [PATCH 0889/1133] Simplify V4L2/USB ifdefs Define local variables whose value depends on ifdefs, to avoid cluttering all conditions with ifdefs. --- app/src/cli.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 10565da9..e96a3b85 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1923,11 +1923,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + bool otg = false; + bool v4l2 = false; #ifdef HAVE_USB - if (!(opts->playback && opts->video) && !opts->otg) { -#else - if (!(opts->playback && opts->video)) { + otg = opts->otg; +#endif +#ifdef HAVE_V4L2 + v4l2 = !!opts->v4l2_device; #endif + + if (!(opts->playback && opts->video) && !otg) { // If video playback is disabled and OTG are disabled, then there is // no way to control the device. opts->control = false; @@ -1938,14 +1943,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->require_audio = true; } -#ifdef HAVE_V4L2 - if (!opts->playback && !opts->record_filename && !opts->v4l2_device) { + if (!opts->playback && !opts->record_filename && !v4l2) { LOGE("-N/--no-playback requires either screen recording (-r/--record)" " or sink to v4l2loopback device (--v4l2-sink)"); return false; } - if (opts->v4l2_device) { +#ifdef HAVE_V4L2 + if (v4l2) { if (opts->lock_video_orientation == SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { LOGI("Video orientation is locked for v4l2 sink. " @@ -1963,11 +1968,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("V4L2 buffer value without V4L2 sink\n"); return false; } -#else - if (!opts->playback && !opts->record_filename) { - LOGE("-N/--no-playback requires screen recording (-r/--record)"); - return false; - } #endif if (opts->audio && !opts->playback && !opts->record_filename) { @@ -2054,11 +2054,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } -#ifdef HAVE_USB - # ifdef _WIN32 - if (!opts->otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID - || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { + if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID + || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { LOGE("On Windows, it is not possible to open a USB device already open " "by another process (like adb)."); LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in " @@ -2067,7 +2065,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } # endif - if (opts->otg) { + if (otg) { // OTG mode is compatible with only very few options. // Only report obvious errors. if (opts->record_filename) { @@ -2094,14 +2092,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGE("OTG mode: could not select display"); return false; } -# ifdef HAVE_V4L2 - if (opts->v4l2_device) { + if (v4l2) { LOGE("OTG mode: could not sink to V4L2 device"); return false; } -# endif } -#endif return true; } From 1efbfe1175b023880b99f55df054f53ba3b30aa7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 21:22:31 +0200 Subject: [PATCH 0890/1133] Add separate video and audio playback options Add --no-video-playback and --no-audio-playback. The option --no-playback is now an alias for both. PR #4033 --- app/data/bash-completion/scrcpy | 2 ++ app/data/zsh-completion/_scrcpy | 2 ++ app/scrcpy.1 | 10 +++++- app/src/cli.c | 61 +++++++++++++++++++++++++-------- app/src/options.c | 3 +- app/src/options.h | 3 +- app/src/scrcpy.c | 53 ++++++++++++++-------------- app/tests/test_cli.c | 3 +- 8 files changed, 94 insertions(+), 43 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index dccb68e5..010125fb 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -29,6 +29,7 @@ _scrcpy() { -M --hid-mouse -m --max-size= --no-audio + --no-audio-playback --no-cleanup --no-clipboard-autosync --no-downsize-on-error @@ -38,6 +39,7 @@ _scrcpy() { --no-mipmaps --no-power-on --no-video + --no-video-playback --otg -p --port= --power-off-on-close diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index afc6b1e6..4f1f16b9 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -35,6 +35,7 @@ arguments=( {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' {-m,--max-size=}'[Limit both the width and height of the video to value]' '--no-audio[Disable audio forwarding]' + '--no-audio-playback[Disable audio playback]' '--no-cleanup[Disable device cleanup actions on exit]' '--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' @@ -44,6 +45,7 @@ arguments=( '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' '--no-video[Disable video forwarding]' + '--no-video-playback[Disable video playback]' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' '--power-off-on-close[Turn the device screen off when closing scrcpy]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a622d22b..d506d9e7 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -187,6 +187,10 @@ Also see \fB\-\-hid\-keyboard\fR. .B \-\-no\-audio Disable audio forwarding. +.TP +.B \-\-no\-audio\-playback +Disable audio playback on the computer. + .TP .B \-\-no\-cleanup By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit. @@ -211,7 +215,7 @@ Disable device control (mirror the device in read\-only). .TP .B \-N, \-\-no\-playback -Disable video and audio playback on the computer. +Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback). .TP .B \-\-no\-key\-repeat @@ -229,6 +233,10 @@ Do not power on the device on start. .B \-\-no\-video Disable video forwarding. +.TP +.B \-\-no\-video\-playback +Disable video playback on the computer. + .TP .B \-\-otg Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. diff --git a/app/src/cli.c b/app/src/cli.c index e96a3b85..1b42f75c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -74,6 +74,8 @@ enum { OPT_AUDIO_OUTPUT_BUFFER, OPT_NO_DISPLAY, OPT_NO_VIDEO, + OPT_NO_AUDIO_PLAYBACK, + OPT_NO_VIDEO_PLAYBACK, }; struct sc_option { @@ -351,6 +353,11 @@ static const struct sc_option options[] = { .longopt = "no-audio", .text = "Disable audio forwarding.", }, + { + .longopt_id = OPT_NO_AUDIO_PLAYBACK, + .longopt = "no-audio-playback", + .text = "Disable audio playback on the computer.", + }, { .longopt_id = OPT_NO_CLEANUP, .longopt = "no-cleanup", @@ -383,7 +390,8 @@ static const struct sc_option options[] = { { .shortopt = 'N', .longopt = "no-playback", - .text = "Disable video and audio playback on the computer.", + .text = "Disable video and audio playback on the computer (equivalent " + "to --no-video-playback --no-audio-playback).", }, { // deprecated @@ -412,6 +420,11 @@ static const struct sc_option options[] = { .longopt = "no-video", .text = "Disable video forwarding.", }, + { + .longopt_id = OPT_NO_VIDEO_PLAYBACK, + .longopt = "no-video-playback", + .text = "Disable video playback on the computer.", + }, { .longopt_id = OPT_OTG, .longopt = "otg", @@ -1673,7 +1686,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGW("--no-display is deprecated, use --no-playback instead."); // fall through case 'N': - opts->playback = false; + opts->video_playback = false; + opts->audio_playback = false; + break; + case OPT_NO_VIDEO_PLAYBACK: + opts->video_playback = false; + break; + case OPT_NO_AUDIO_PLAYBACK: + opts->audio_playback = false; break; case 'p': if (!parse_port_range(optarg, &opts->port_range)) { @@ -1932,23 +1952,41 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], v4l2 = !!opts->v4l2_device; #endif - if (!(opts->playback && opts->video) && !otg) { + if (!opts->video) { + opts->video_playback = false; + } + + if (!opts->audio) { + opts->audio_playback = false; + } + + if (!opts->video_playback && !otg) { // If video playback is disabled and OTG are disabled, then there is // no way to control the device. opts->control = false; } - if (!opts->video) { - // If video is disabled, then scrcpy must exit on audio failure. - opts->require_audio = true; + if (opts->video && !opts->video_playback && !opts->record_filename + && !v4l2) { + LOGI("No video playback, no recording, no V4L2 sink: video disabled"); + opts->video = false; + } + + if (opts->audio && !opts->audio_playback && !opts->record_filename) { + LOGI("No audio playback, no recording: audio disabled"); + opts->audio = false; } - if (!opts->playback && !opts->record_filename && !v4l2) { - LOGE("-N/--no-playback requires either screen recording (-r/--record)" - " or sink to v4l2loopback device (--v4l2-sink)"); + if (!opts->video && !opts->audio && !otg) { + LOGE("No video, no audio, no OTG: nothing to do"); return false; } + if (!opts->video && !otg) { + // If video is disabled, then scrcpy must exit on audio failure. + opts->require_audio = true; + } + #ifdef HAVE_V4L2 if (v4l2) { if (opts->lock_video_orientation == @@ -1970,11 +2008,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif - if (opts->audio && !opts->playback && !opts->record_filename) { - LOGI("No playback and no recording: audio disabled"); - opts->audio = false; - } - if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { LOGI("Tunnel host/port is set, " "--force-adb-forward automatically enabled."); diff --git a/app/src/options.c b/app/src/options.c index fc3b1790..49cca969 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -52,7 +52,8 @@ const struct scrcpy_options scrcpy_options_default = { .fullscreen = false, .always_on_top = false, .control = true, - .playback = true, + .video_playback = true, + .audio_playback = true, .turn_screen_off = false, .key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .window_borderless = false, diff --git a/app/src/options.h b/app/src/options.h index e3cc8064..dd5d0dad 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -147,7 +147,8 @@ struct scrcpy_options { bool fullscreen; bool always_on_top; bool control; - bool playback; + bool video_playback; + bool audio_playback; bool turn_screen_off; enum sc_key_inject_mode key_inject_mode; bool window_borderless; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 3334f57d..27a1c6be 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -137,7 +137,7 @@ sdl_set_hints(const char *render_driver) { } static void -sdl_configure(bool playback, bool disable_screensaver) { +sdl_configure(bool video_playback, bool disable_screensaver) { #ifdef _WIN32 // Clean up properly on Ctrl+C on Windows bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); @@ -146,7 +146,7 @@ sdl_configure(bool playback, bool disable_screensaver) { } #endif // _WIN32 - if (!playback) { + if (!video_playback) { return; } @@ -386,22 +386,26 @@ scrcpy(struct scrcpy_options *options) { goto end; } - if (options->playback) { - sdl_set_hints(options->render_driver); + // playback implies capture + assert(!options->video_playback || options->video); + assert(!options->audio_playback || options->audio); - // Initialize SDL video and audio in addition if playback is enabled - if (options->video && SDL_Init(SDL_INIT_VIDEO)) { + if (options->video_playback) { + sdl_set_hints(options->render_driver); + if (SDL_Init(SDL_INIT_VIDEO)) { LOGE("Could not initialize SDL video: %s", SDL_GetError()); goto end; } + } - if (options->audio && SDL_Init(SDL_INIT_AUDIO)) { + if (options->audio_playback) { + if (SDL_Init(SDL_INIT_AUDIO)) { LOGE("Could not initialize SDL audio: %s", SDL_GetError()); goto end; } } - sdl_configure(options->playback, options->disable_screensaver); + sdl_configure(options->video_playback, options->disable_screensaver); // Await for server without blocking Ctrl+C handling bool connected; @@ -427,7 +431,8 @@ scrcpy(struct scrcpy_options *options) { struct sc_file_pusher *fp = NULL; - assert(!options->control || options->playback); // control implies playback + // control implies video playback + assert(!options->control || options->video_playback); if (options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { @@ -453,8 +458,8 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, options); } - bool needs_video_decoder = options->playback && options->video; - bool needs_audio_decoder = options->playback && options->audio; + bool needs_video_decoder = options->video_playback; + bool needs_audio_decoder = options->audio_playback; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; #endif @@ -650,7 +655,7 @@ aoa_hid_end: // There is a controller if and only if control is enabled assert(options->control == !!controller); - if (options->playback) { + if (options->video_playback) { const char *window_title = options->window_title ? options->window_title : info->device_name; @@ -684,21 +689,19 @@ aoa_hid_end: src = &s->display_buffer.frame_source; } - if (options->video) { - if (!sc_screen_init(&s->screen, &screen_params)) { - goto end; - } - screen_initialized = true; - - sc_frame_source_add_sink(src, &s->screen.frame_sink); + if (!sc_screen_init(&s->screen, &screen_params)) { + goto end; } + screen_initialized = true; - if (options->audio) { - sc_audio_player_init(&s->audio_player, options->audio_buffer, - options->audio_output_buffer); - sc_frame_source_add_sink(&s->audio_decoder.frame_source, - &s->audio_player.frame_sink); - } + sc_frame_source_add_sink(src, &s->screen.frame_sink); + } + + if (options->audio_playback) { + sc_audio_player_init(&s->audio_player, options->audio_buffer, + options->audio_output_buffer); + sc_frame_source_add_sink(&s->audio_decoder.frame_source, + &s->audio_player.frame_sink); } #ifdef HAVE_V4L2 diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index ec1a9531..f2a17272 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -117,7 +117,8 @@ static void test_options2(void) { const struct scrcpy_options *opts = &args.opts; assert(!opts->control); - assert(!opts->playback); + assert(!opts->video_playback); + assert(!opts->audio_playback); assert(!strcmp(opts->record_filename, "file.mp4")); assert(opts->record_format == SC_RECORD_FORMAT_MP4); } From c4caa6b81d86cb82fa648612b1b4e4a405d8cab0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 May 2023 21:28:40 +0200 Subject: [PATCH 0891/1133] Document --no-{video,audio}-playback PR #4033 --- doc/audio.md | 2 ++ doc/v4l2.md | 2 +- doc/video.md | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/audio.md b/doc/audio.md index 08868eaf..a437005c 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -24,6 +24,8 @@ To disable audio: scrcpy --no-audio ``` +To disable only the audio playback, see [no playback](video.md#no-playback). + ## Audio only To play audio only, disable the video: diff --git a/doc/v4l2.md b/doc/v4l2.md index 62480478..23c99912 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -35,7 +35,7 @@ To start `scrcpy` using a v4l2 sink: ```bash scrcpy --v4l2-sink=/dev/videoN -scrcpy --v4l2-sink=/dev/videoN --no-playback # disable playback window +scrcpy --v4l2-sink=/dev/videoN --no-video-playback # disable playback window ``` (replace `N` with the device ID, check with `ls /dev/video*`) diff --git a/doc/video.md b/doc/video.md index 757d9324..e411cf0d 100644 --- a/doc/video.md +++ b/doc/video.md @@ -161,8 +161,8 @@ scrcpy --display-buffer=50 --v4l2-buffer=300 ## No playback -It is possible to capture an Android device without displaying a playback -window. This option is available if either [recording](recording.md) or +It is possible to capture an Android device without playing video or audio on +the computer. This option is useful when [recording](recording.md) or when [v4l2](#video4linux) is enabled: ```bash @@ -170,6 +170,16 @@ scrcpy --v4l2-sink=/dev/video2 --no-playback scrcpy --record=file.mkv --no-playback ``` +It is also possible to disable video and audio playback separately: + +```bash +# Send video to V4L2 sink without playing it, but keep audio playback +scrcpy --v4l2-sink=/dev/video2 --no-video-playback + +# Record both video and audio, but only play video +scrcpy --record=file.mkv --no-audio-playback +``` + ## No video From 798dfd240e53e964094c656f4e365f0cef33b943 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 8 Apr 2023 10:11:47 +0200 Subject: [PATCH 0892/1133] Turn device screen off after set up Sometimes it can take quite a while for everything to get set up and the screen to appear. PR #3902 Signed-off-by: Romain Vimont --- app/src/scrcpy.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 27a1c6be..4bbc8dca 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -639,17 +639,6 @@ aoa_hid_end: } controller_started = true; controller = &s->controller; - - if (options->turn_screen_off) { - struct sc_control_msg msg; - msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; - msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF; - - if (!sc_controller_push_msg(&s->controller, &msg)) { - LOGW("Could not request 'set screen power mode'"); - } - } - } // There is a controller if and only if control is enabled @@ -740,6 +729,18 @@ aoa_hid_end: audio_demuxer_started = true; } + // If the device screen is to be turned off, send the control message after + // everything is set up + if (options->control && options->turn_screen_off) { + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; + msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF; + + if (!sc_controller_push_msg(&s->controller, &msg)) { + LOGW("Could not request 'set screen power mode'"); + } + } + ret = event_loop(s); LOGD("quit..."); From 4c4a03ebe16749aec8a83d27cd28e3d686de171c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 30 May 2023 19:31:48 +0200 Subject: [PATCH 0893/1133] Reorder options to maintain alphabetical order --- app/scrcpy.1 | 16 ++++++++-------- app/src/cli.c | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d506d9e7..a30e7db0 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -33,14 +33,6 @@ Lower values decrease the latency, but increase the likelyhood of buffer underru Default is 50. -.TP -.BI "\-\-audio\-output\-buffer ms -Configure the size of the SDL audio output buffer (in milliseconds). - -If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise. - -Default is 5. - .TP .BI "\-\-audio\-codec " name Select an audio codec (opus, aac or raw). @@ -63,6 +55,14 @@ Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\ The available encoders can be listed by \-\-list\-encoders. +.TP +.BI "\-\-audio\-output\-buffer ms +Configure the size of the SDL audio output buffer (in milliseconds). + +If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise. + +Default is 5. + .TP .BI "\-b, \-\-video\-bit\-rate " value Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). diff --git a/app/src/cli.c b/app/src/cli.c index 1b42f75c..01b55406 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -134,16 +134,6 @@ static const struct sc_option options[] = { "likelyhood of buffer underrun (causing audio glitches).\n" "Default is 50.", }, - { - .longopt_id = OPT_AUDIO_OUTPUT_BUFFER, - .longopt = "audio-output-buffer", - .argdesc = "ms", - .text = "Configure the size of the SDL audio output buffer (in " - "milliseconds).\n" - "If you get \"robotic\" audio playback, you should test with " - "a higher value (10). Do not change this setting otherwise.\n" - "Default is 5.", - }, { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", @@ -171,6 +161,16 @@ static const struct sc_option options[] = { "codec provided by --audio-codec).\n" "The available encoders can be listed by --list-encoders.", }, + { + .longopt_id = OPT_AUDIO_OUTPUT_BUFFER, + .longopt = "audio-output-buffer", + .argdesc = "ms", + .text = "Configure the size of the SDL audio output buffer (in " + "milliseconds).\n" + "If you get \"robotic\" audio playback, you should test with " + "a higher value (10). Do not change this setting otherwise.\n" + "Default is 5.", + }, { .shortopt = 'b', .longopt = "video-bit-rate", From b2d860382fd6ccefe85c45343da622ffbed8b3fa Mon Sep 17 00:00:00 2001 From: shuax <6940583+shuax@users.noreply.github.com> Date: Wed, 31 May 2023 03:26:33 +0000 Subject: [PATCH 0894/1133] Fix stream offset on audio buffer underflow The `read` variable is in number of samples, while the offset must be in bytes. PR #4045 Signed-off-by: Romain Vimont --- app/src/audio_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index a0c52c62..8f0ad7fb 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -107,7 +107,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { // latency. LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples", silence); - memset(stream + read, 0, TO_BYTES(silence)); + memset(stream + TO_BYTES(read), 0, TO_BYTES(silence)); if (ap->received) { // Inserting additional samples immediately increases buffering From 9a2abba09827b162f0dc5e2d48badde897c3f7dc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Jun 2023 09:01:52 +0200 Subject: [PATCH 0895/1133] Update demuxer comment The comment was outdated: - the "meta" header is now always present (not only when recording is enabled); - it is not only used for the video stream, but also for the audio stream. --- app/src/demuxer.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 4fcdd9ad..943f72b6 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -79,9 +79,8 @@ sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width, static bool sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { - // The video stream contains raw packets, without time information. When we - // record, we retrieve the timestamps separately, from a "meta" header - // added by the server before each raw packet. + // The video and audio streams contain a sequence of raw packets (as + // provided by MediaCodec), each prefixed with a "meta" header. // // The "meta" header length is 12 bytes: // [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ... From 8e2c0d64079672d802b52c03862492671a0351d6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 29 May 2023 19:23:51 +0200 Subject: [PATCH 0896/1133] Rename rotationChanged to resetCapture The flag is used to reset the capture (restart the encoding) on rotation change. It will also be used for other events (on folding change), so rename it. PR #3979 --- .../java/com/genymobile/scrcpy/ScreenEncoder.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 901ba94c..d45ca853 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -26,7 +26,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final int MAX_CONSECUTIVE_ERRORS = 3; - private final AtomicBoolean rotationChanged = new AtomicBoolean(); + private final AtomicBoolean resetCapture = new AtomicBoolean(); private final Device device; private final Streamer streamer; @@ -55,11 +55,11 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { @Override public void onRotationChanged(int rotation) { - rotationChanged.set(true); + resetCapture.set(true); } - private boolean consumeRotationChange() { - return rotationChanged.getAndSet(false); + private boolean consumeResetCapture() { + return resetCapture.getAndSet(false); } private void streamScreen() throws IOException, ConfigurationException { @@ -169,14 +169,14 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { boolean alive = true; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - while (!consumeRotationChange() && !eof) { + while (!consumeResetCapture() && !eof) { if (stopped.get()) { alive = false; break; } int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); try { - if (consumeRotationChange()) { + if (consumeResetCapture()) { // must restart encoding with new size break; } From 24999d0d32daf50246168dde212f01cf109569f9 Mon Sep 17 00:00:00 2001 From: Adonis Najimi Date: Sun, 7 May 2023 22:09:21 +0200 Subject: [PATCH 0897/1133] Reset video capture on folding event Handle folding event the same way as rotation events. Fixes #3960 PR #3979 Signed-off-by: Romain Vimont --- .../android/view/IDisplayFoldListener.aidl | 26 ++++++++++++++++ .../java/com/genymobile/scrcpy/Device.java | 30 +++++++++++++++++++ .../com/genymobile/scrcpy/ScreenEncoder.java | 9 +++++- .../scrcpy/wrappers/WindowManager.java | 10 +++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 server/src/main/aidl/android/view/IDisplayFoldListener.aidl diff --git a/server/src/main/aidl/android/view/IDisplayFoldListener.aidl b/server/src/main/aidl/android/view/IDisplayFoldListener.aidl new file mode 100644 index 00000000..2c91149d --- /dev/null +++ b/server/src/main/aidl/android/view/IDisplayFoldListener.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * {@hide} + */ +oneway interface IDisplayFoldListener +{ + /** Called when the foldedness of a display changes */ + void onDisplayFoldChanged(int displayId, boolean folded); +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 3d83f73e..a3b6a270 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -12,6 +12,7 @@ import android.os.Build; import android.os.IBinder; import android.os.SystemClock; import android.view.IRotationWatcher; +import android.view.IDisplayFoldListener; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyCharacterMap; @@ -35,6 +36,10 @@ public final class Device { void onRotationChanged(int rotation); } + public interface FoldListener { + void onFoldChanged(int displayId, boolean folded); + } + public interface ClipboardListener { void onClipboardTextChanged(String text); } @@ -46,6 +51,7 @@ public final class Device { private ScreenInfo screenInfo; private RotationListener rotationListener; + private FoldListener foldListener; private ClipboardListener clipboardListener; private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); @@ -93,6 +99,26 @@ public final class Device { } }, displayId); + ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { + @Override + public void onDisplayFoldChanged(int displayId, boolean folded) { + synchronized (Device.this) { + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + if (displayInfo == null) { + Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); + return; + } + + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), + options.getMaxSize(), options.getLockVideoOrientation()); + // notify + if (foldListener != null) { + foldListener.onFoldChanged(displayId, folded); + } + } + } + }); + if (options.getControl() && options.getClipboardAutosync()) { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); @@ -224,6 +250,10 @@ public final class Device { this.rotationListener = rotationListener; } + public synchronized void setFoldListener(FoldListener foldlistener) { + this.foldListener = foldlistener; + } + public synchronized void setClipboardListener(ClipboardListener clipboardListener) { this.clipboardListener = clipboardListener; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index d45ca853..d56e5d27 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -16,7 +16,7 @@ import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { +public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, AsyncProcessor { private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms @@ -53,6 +53,11 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { this.downsizeOnError = downsizeOnError; } + @Override + public void onFoldChanged(int displayId, boolean folded) { + resetCapture.set(true); + } + @Override public void onRotationChanged(int rotation) { resetCapture.set(true); @@ -68,6 +73,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); IBinder display = createDisplay(); device.setRotationListener(this); + device.setFoldListener(this); streamer.writeVideoHeader(device.getScreenInfo().getVideoSize()); @@ -115,6 +121,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor { } finally { mediaCodec.release(); device.setRotationListener(null); + device.setFoldListener(null); SurfaceControl.destroyDisplay(display); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index faa366a5..d9fd9825 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -4,6 +4,7 @@ import com.genymobile.scrcpy.Ln; import android.os.IInterface; import android.view.IRotationWatcher; +import android.view.IDisplayFoldListener; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -108,4 +109,13 @@ public final class WindowManager { throw new AssertionError(e); } } + + public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { + try { + Class cls = manager.getClass(); + cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener); + } catch (Exception e) { + throw new AssertionError(e); + } + } } From 360f2fea1e66a34fb4a6739887d2efc926dee856 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 30 May 2023 21:25:47 +0200 Subject: [PATCH 0898/1133] Extract AudioCapture creation This will allow to pass capture options without code duplication. --- .../src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 8 ++++---- .../main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 5 +++-- server/src/main/java/com/genymobile/scrcpy/Server.java | 5 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index a1abd71b..108bbaa1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -40,6 +40,7 @@ public final class AudioEncoder implements AsyncProcessor { private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); + private final AudioCapture capture; private final Streamer streamer; private final int bitRate; private final List codecOptions; @@ -58,7 +59,8 @@ public final class AudioEncoder implements AsyncProcessor { private boolean ended; - public AudioEncoder(Streamer streamer, int bitRate, List codecOptions, String encoderName) { + public AudioEncoder(AudioCapture capture, Streamer streamer, int bitRate, List codecOptions, String encoderName) { + this.capture = capture; this.streamer = streamer; this.bitRate = bitRate; this.codecOptions = codecOptions; @@ -175,7 +177,6 @@ public final class AudioEncoder implements AsyncProcessor { } MediaCodec mediaCodec = null; - AudioCapture capture = new AudioCapture(); boolean mediaCodecStarted = false; try { @@ -192,10 +193,9 @@ public final class AudioEncoder implements AsyncProcessor { capture.start(); final MediaCodec mediaCodecRef = mediaCodec; - final AudioCapture captureRef = capture; inputThread = new Thread(() -> { try { - inputThread(mediaCodecRef, captureRef); + inputThread(mediaCodecRef, capture); } catch (IOException | InterruptedException e) { Ln.e("Audio capture error", e); } finally { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 685ac3bd..2fc8c887 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -8,6 +8,7 @@ import java.nio.ByteBuffer; public final class AudioRawRecorder implements AsyncProcessor { + private final AudioCapture capture; private final Streamer streamer; private Thread thread; @@ -15,7 +16,8 @@ public final class AudioRawRecorder implements AsyncProcessor { private static final int READ_MS = 5; // milliseconds private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); - public AudioRawRecorder(Streamer streamer) { + public AudioRawRecorder(AudioCapture capture, Streamer streamer) { + this.capture = capture; this.streamer = streamer; } @@ -29,7 +31,6 @@ public final class AudioRawRecorder implements AsyncProcessor { final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - AudioCapture capture = new AudioCapture(); try { capture.start(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index db993830..616b771e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -136,13 +136,14 @@ public final class Server { if (audio) { AudioCodec audioCodec = options.getAudioCodec(); + AudioCapture audioCapture = new AudioCapture(); Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta()); AsyncProcessor audioRecorder; if (audioCodec == AudioCodec.RAW) { - audioRecorder = new AudioRawRecorder(audioStreamer); + audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer); } else { - audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), + audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), options.getAudioEncoder()); } asyncProcessors.add(audioRecorder); From ff5ffc892fa50993e7b5f9ea9d9790c7e4f29693 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 30 May 2023 21:29:05 +0200 Subject: [PATCH 0899/1133] Add option to select audio source Pass --audio-source=mic to capture the microphone instead of the device audio output. --- app/data/bash-completion/scrcpy | 5 ++++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 ++++ app/src/cli.c | 29 ++++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 6 ++++ app/src/scrcpy.c | 1 + app/src/server.c | 4 +++ app/src/server.h | 1 + doc/audio.md | 18 +++++++++++ .../com/genymobile/scrcpy/AudioCapture.java | 16 ++++++---- .../com/genymobile/scrcpy/AudioSource.java | 30 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 12 ++++++++ .../java/com/genymobile/scrcpy/Server.java | 2 +- 14 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/AudioSource.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 010125fb..8d1f1e13 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -7,6 +7,7 @@ _scrcpy() { --audio-codec= --audio-codec-options= --audio-encoder= + --audio-source= --audio-output-buffer= -b --video-bit-rate= --crop= @@ -86,6 +87,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) return ;; + --audio-source) + COMPREPLY=($(compgen -W 'output mic' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 4f1f16b9..6e742fd7 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -14,6 +14,7 @@ arguments=( '--audio-codec=[Select the audio codec]:codec:(opus aac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]' + '--audio-source=[Select the audio source]:source:(output mic)' '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a30e7db0..04098b9c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -55,6 +55,12 @@ Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\ The available encoders can be listed by \-\-list\-encoders. +.TP +.BI "\-\-audio\-source " source +Select the audio source (output or mic). + +Default is output. + .TP .BI "\-\-audio\-output\-buffer ms Configure the size of the SDL audio output buffer (in milliseconds). diff --git a/app/src/cli.c b/app/src/cli.c index 01b55406..533e63ca 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -76,6 +76,7 @@ enum { OPT_NO_VIDEO, OPT_NO_AUDIO_PLAYBACK, OPT_NO_VIDEO_PLAYBACK, + OPT_AUDIO_SOURCE, }; struct sc_option { @@ -161,6 +162,13 @@ static const struct sc_option options[] = { "codec provided by --audio-codec).\n" "The available encoders can be listed by --list-encoders.", }, + { + .longopt_id = OPT_AUDIO_SOURCE, + .longopt = "audio-source", + .argdesc = "source", + .text = "Select the audio source (output or mic).\n" + "Default is output.", + }, { .longopt_id = OPT_AUDIO_OUTPUT_BUFFER, .longopt = "audio-output-buffer", @@ -1588,6 +1596,22 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { return false; } +static bool +parse_audio_source(const char *optarg, enum sc_audio_source *source) { + if (!strcmp(optarg, "mic")) { + *source = SC_AUDIO_SOURCE_MIC; + return true; + } + + if (!strcmp(optarg, "output")) { + *source = SC_AUDIO_SOURCE_OUTPUT; + return true; + } + + LOGE("Unsupported audio source: %s (expected output or mic)", optarg); + return false; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1915,6 +1939,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_AUDIO_SOURCE: + if (!parse_audio_source(optarg, &opts->audio_source)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 49cca969..e1373753 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = { .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, + .audio_source = SC_AUDIO_SOURCE_OUTPUT, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index dd5d0dad..c33fafef 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -44,6 +44,11 @@ enum sc_codec { SC_CODEC_RAW, }; +enum sc_audio_source { + SC_AUDIO_SOURCE_OUTPUT, + SC_AUDIO_SOURCE_MIC, +}; + enum sc_lock_video_orientation { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, // lock the current orientation when scrcpy starts @@ -115,6 +120,7 @@ struct scrcpy_options { enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; + enum sc_audio_source audio_source; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4bbc8dca..79007bf2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -334,6 +334,7 @@ scrcpy(struct scrcpy_options *options) { .log_level = options->log_level, .video_codec = options->video_codec, .audio_codec = options->audio_codec, + .audio_source = options->audio_source, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index 9c554760..4e70c4d9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -246,6 +246,10 @@ execute_server(struct sc_server *server, ADD_PARAM("audio_codec=%s", sc_server_get_codec_name(params->audio_codec)); } + if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT) { + assert(params->audio_source == SC_AUDIO_SOURCE_MIC); + ADD_PARAM("audio_source=mic"); + } if (params->max_size) { ADD_PARAM("max_size=%" PRIu16, params->max_size); } diff --git a/app/src/server.h b/app/src/server.h index 31648445..fad44e66 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -26,6 +26,7 @@ struct sc_server_params { enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; + enum sc_audio_source audio_source; const char *crop; const char *video_codec_options; const char *audio_codec_options; diff --git a/doc/audio.md b/doc/audio.md index a437005c..40dd6036 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -41,6 +41,24 @@ interesting to add [buffering](#buffering) to minimize glitches: scrcpy --no-video --audio-buffer=200 ``` +## Source + +By default, the device audio output is forwarded. + +It is possible to capture the device microphone instead: + +``` +scrcpy --audio-source=mic +``` + +For example, to use the device as a dictaphone and record a capture directly on +the computer: + +``` +scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus +``` + + ## Codec The audio codec can be selected. The possible values are `opus` (default), `aac` diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index b8fc076b..7b20cce4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -10,7 +10,6 @@ import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTimestamp; import android.media.MediaCodec; -import android.media.MediaRecorder; import android.os.Build; import android.os.SystemClock; @@ -18,7 +17,6 @@ import java.nio.ByteBuffer; public final class AudioCapture { - public static final int SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX; public static final int SAMPLE_RATE = 48000; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; public static final int CHANNELS = 2; @@ -26,12 +24,18 @@ public final class AudioCapture { public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int BYTES_PER_SAMPLE = 2; + private final int audioSource; + private AudioRecord recorder; private final AudioTimestamp timestamp = new AudioTimestamp(); private long previousPts = 0; private long nextPts = 0; + public AudioCapture(AudioSource audioSource) { + this.audioSource = audioSource.value(); + } + public static int millisToBytes(int millis) { return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000; } @@ -46,13 +50,13 @@ public final class AudioCapture { @TargetApi(Build.VERSION_CODES.M) @SuppressLint({"WrongConstant", "MissingPermission"}) - private static AudioRecord createAudioRecord() { + private static AudioRecord createAudioRecord(int audioSource) { AudioRecord.Builder builder = new AudioRecord.Builder(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // On older APIs, Workarounds.fillAppInfo() must be called beforehand builder.setContext(FakeContext.get()); } - builder.setAudioSource(SOURCE); + builder.setAudioSource(audioSource); builder.setAudioFormat(createAudioFormat()); int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING); // This buffer size does not impact latency @@ -100,12 +104,12 @@ public final class AudioCapture { private void startRecording() { try { - recorder = createAudioRecord(); + recorder = createAudioRecord(audioSource); } catch (NullPointerException e) { // Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones: // - // - - recorder = Workarounds.createAudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING); + recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING); } recorder.startRecording(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioSource.java b/server/src/main/java/com/genymobile/scrcpy/AudioSource.java new file mode 100644 index 00000000..466ea297 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/AudioSource.java @@ -0,0 +1,30 @@ +package com.genymobile.scrcpy; + +import android.media.MediaRecorder; + +public enum AudioSource { + OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX), + MIC("mic", MediaRecorder.AudioSource.MIC); + + private final String name; + private final int value; + + AudioSource(String name, int value) { + this.name = name; + this.value = value; + } + + int value() { + return value; + } + + static AudioSource findByName(String name) { + for (AudioSource audioSource : AudioSource.values()) { + if (name.equals(audioSource.name)) { + return audioSource; + } + } + + return null; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 7bd94cb3..23d4e383 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -14,6 +14,7 @@ public class Options { private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; private AudioCodec audioCodec = AudioCodec.OPUS; + private AudioSource audioSource = AudioSource.OUTPUT; private int videoBitRate = 8000000; private int audioBitRate = 128000; private int maxFps; @@ -72,6 +73,10 @@ public class Options { return audioCodec; } + public AudioSource getAudioSource() { + return audioSource; + } + public int getVideoBitRate() { return videoBitRate; } @@ -225,6 +230,13 @@ public class Options { } options.audioCodec = audioCodec; break; + case "audio_source": + AudioSource audioSource = AudioSource.findByName(value); + if (audioSource == null) { + throw new IllegalArgumentException("Audio source " + value + " not supported"); + } + options.audioSource = audioSource; + break; case "max_size": options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8 break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 616b771e..214ac27d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -136,7 +136,7 @@ public final class Server { if (audio) { AudioCodec audioCodec = options.getAudioCodec(); - AudioCapture audioCapture = new AudioCapture(); + AudioCapture audioCapture = new AudioCapture(options.getAudioSource()); Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta()); AsyncProcessor audioRecorder; From fc52b2450333beb94c0dc2f94d01d4de13afeec5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Jun 2023 09:52:35 +0200 Subject: [PATCH 0900/1133] Reorder options in alphabetical order Fix the options order, using the short option as key first (if any) in all cases for consistency. --- app/data/bash-completion/scrcpy | 16 ++--- app/data/zsh-completion/_scrcpy | 16 ++--- app/scrcpy.1 | 80 ++++++++++++------------ app/src/cli.c | 104 ++++++++++++++++---------------- 4 files changed, 108 insertions(+), 108 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 8d1f1e13..de298056 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -16,9 +16,9 @@ _scrcpy() { --display= --display-buffer= -e --select-tcpip + -f --fullscreen --force-adb-forward --forward-all-clicks - -f --fullscreen -K --hid-keyboard -h --help --legacy-paste @@ -26,16 +26,16 @@ _scrcpy() { --list-encoders --lock-video-orientation --lock-video-orientation= - --max-fps= - -M --hid-mouse -m --max-size= + -M --hid-mouse + --max-fps= + -n --no-control + -N --no-playback --no-audio --no-audio-playback --no-cleanup --no-clipboard-autosync --no-downsize-on-error - -n --no-control - -N --no-playback --no-key-repeat --no-mipmaps --no-power-on @@ -47,15 +47,15 @@ _scrcpy() { --prefer-text --print-fps --push-target= - --raw-key-events -r --record= + --raw-key-events --record-format= --render-driver= --require-audio --rotation= -s --serial= - --shortcut-mod= -S --turn-screen-off + --shortcut-mod= -t --show-touches --tcpip --tcpip= @@ -63,8 +63,8 @@ _scrcpy() { --tunnel-port= --v4l2-buffer= --v4l2-sink= - -V --verbosity= -v --version + -V --verbosity= --video-codec= --video-codec-options= --video-encoder= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 6e742fd7..17e30dc5 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -23,25 +23,25 @@ arguments=( '--display=[Specify the display id to mirror]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' {-e,--select-tcpip}'[Use TCP/IP device]' + {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' - {-f,--fullscreen}'[Start in fullscreen]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-h,--help}'[Print the help]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' - '--max-fps=[Limit the frame rate of screen capture]' - {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' {-m,--max-size=}'[Limit both the width and height of the video to value]' + {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' + '--max-fps=[Limit the frame rate of screen capture]' + {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' + {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' '--no-audio-playback[Disable audio playback]' '--no-cleanup[Disable device cleanup actions on exit]' '--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' - {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' - {-N,--no-playback}'[Disable video and audio playback]' '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' @@ -53,23 +53,23 @@ arguments=( '--prefer-text[Inject alpha characters and space as text events instead of key events]' '--print-fps[Start FPS counter, to print frame logs to the console]' '--push-target=[Set the target directory for pushing files to the device by drag and drop]' - '--raw-key-events[Inject key events for all input keys, and ignore text events]' {-r,--record=}'[Record screen to file]:record file:_files' + '--raw-key-events[Inject key events for all input keys, and ignore text events]' '--record-format=[Force recording format]:format:(mp4 mkv)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' - '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' {-S,--turn-screen-off}'[Turn the device screen off immediately]' + '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' {-t,--show-touches}'[Show physical touches]' '--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]' '--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]' '--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]' '--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]' '--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]' - {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' {-v,--version}'[Print the version of scrcpy]' + {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' '--video-codec=[Select the video codec]:codec:(h264 h265 av1)' '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' '--video-encoder=[Use a specific MediaCodec video encoder]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 04098b9c..9b08f182 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -113,6 +113,10 @@ Use TCP/IP device (if there is exactly one, like adb -e). Also see \fB\-d\fR (\fB\-\-select\-usb\fR). +.TP +.B \-f, \-\-fullscreen +Start in fullscreen. + .TP .B \-\-force\-adb\-forward Do not attempt to use "adb reverse" to connect to the device. @@ -121,10 +125,6 @@ Do not attempt to use "adb reverse" to connect to the device. .B \-\-forward\-all\-clicks By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead. -.TP -.B \-f, \-\-fullscreen -Start in fullscreen. - .TP .B \-h, \-\-help Print this help. @@ -167,10 +167,6 @@ Default is "unlocked". Passing the option without argument is equivalent to passing "initial". -.TP -.BI "\-\-max\-fps " value -Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). - .TP .BI "\-m, \-\-max\-size " value Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved. @@ -189,6 +185,18 @@ It may only work over USB. Also see \fB\-\-hid\-keyboard\fR. +.TP +.BI "\-\-max\-fps " value +Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). + +.TP +.B \-n, \-\-no\-control +Disable device control (mirror the device in read\-only). + +.TP +.B \-N, \-\-no\-playback +Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback). + .TP .B \-\-no\-audio Disable audio forwarding. @@ -215,14 +223,6 @@ By default, on MediaCodec error, scrcpy automatically tries again with a lower d This option disables this behavior. -.TP -.B \-n, \-\-no\-control -Disable device control (mirror the device in read\-only). - -.TP -.B \-N, \-\-no\-playback -Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback). - .TP .B \-\-no\-key\-repeat Do not forward repeated key events when a key is held down. @@ -284,10 +284,6 @@ Set the target directory for pushing files to the device by drag & drop. It is p Default is "/sdcard/Download/". -.TP -.B \-\-raw\-key\-events -Inject key events for all input keys, and ignore text events. - .TP .BI "\-r, \-\-record " file Record screen to @@ -297,6 +293,10 @@ The format is determined by the .B \-\-record\-format option if set, or by the file extension (.mp4 or .mkv). +.TP +.B \-\-raw\-key\-events +Inject key events for all input keys, and ignore text events. + .TP .BI "\-\-record\-format " format Force recording format (either mp4 or mkv). @@ -322,6 +322,10 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre .BI "\-s, \-\-serial " number The device serial number. Mandatory only if several devices are connected to adb. +.TP +.B \-S, \-\-turn\-screen\-off +Turn the device screen off immediately. + .TP .BI "\-\-shortcut\-mod " key\fR[+...]][,...] Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". @@ -332,6 +336,12 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr Default is "lalt,lsuper" (left-Alt or left-Super). +.TP +.B \-t, \-\-show\-touches +Enable "show touches" on start, restore the initial value on exit. + +It only shows physical touches (not clicks from scrcpy). + .TP .BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]] Configure and reconnect the device over TCP/IP. @@ -340,16 +350,6 @@ If a destination address is provided, then scrcpy connects to this address befor If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting. -.TP -.B \-S, \-\-turn\-screen\-off -Turn the device screen off immediately. - -.TP -.B \-t, \-\-show\-touches -Enable "show touches" on start, restore the initial value on exit. - -It only shows physical touches (not clicks from scrcpy). - .TP .BI "\-\-tunnel\-host " ip Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. @@ -362,6 +362,16 @@ Set the TCP port of the adb tunnel to reach the scrcpy server. This option autom Default is 0 (not forced): the local port used for establishing the tunnel will be used. +.TP +.B \-v, \-\-version +Print the version of scrcpy. + +.TP +.BI "\-V, \-\-verbosity " value +Set the log level ("verbose", "debug", "info", "warn" or "error"). + +Default is "info" for release builds, "debug" for debug builds. + .TP .BI "\-\-v4l2-sink " /dev/videoN Output to v4l2loopback device. @@ -376,16 +386,6 @@ This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink. Default is 0 (no buffering). -.TP -.BI "\-V, \-\-verbosity " value -Set the log level ("verbose", "debug", "info", "warn" or "error"). - -Default is "info" for release builds, "debug" for debug builds. - -.TP -.B \-v, \-\-version -Print the version of scrcpy. - .TP .BI "\-\-video\-codec " name Select a video codec (h264, h265 or av1). diff --git a/app/src/cli.c b/app/src/cli.c index 533e63ca..eb784ebc 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -257,6 +257,11 @@ static const struct sc_option options[] = { .longopt = "encoder", .argdesc = "name", }, + { + .shortopt = 'f', + .longopt = "fullscreen", + .text = "Start in fullscreen.", + }, { .longopt_id = OPT_FORCE_ADB_FORWARD, .longopt = "force-adb-forward", @@ -270,11 +275,6 @@ static const struct sc_option options[] = { "middle-click triggers HOME. This option disables these " "shortcuts and forwards the clicks to the device instead.", }, - { - .shortopt = 'f', - .longopt = "fullscreen", - .text = "Start in fullscreen.", - }, { .shortopt = 'K', .longopt = "hid-keyboard", @@ -330,11 +330,13 @@ static const struct sc_option options[] = { "\"initial\".", }, { - .longopt_id = OPT_MAX_FPS, - .longopt = "max-fps", + .shortopt = 'm', + .longopt = "max-size", .argdesc = "value", - .text = "Limit the frame rate of screen capture (officially supported " - "since Android 10, but may work on earlier versions).", + .text = "Limit both the width and height of the video to value. The " + "other dimension is computed so that the device aspect-ratio " + "is preserved.\n" + "Default is 0 (unlimited).", }, { .shortopt = 'M', @@ -348,13 +350,22 @@ static const struct sc_option options[] = { "Also see --hid-keyboard.", }, { - .shortopt = 'm', - .longopt = "max-size", + .longopt_id = OPT_MAX_FPS, + .longopt = "max-fps", .argdesc = "value", - .text = "Limit both the width and height of the video to value. The " - "other dimension is computed so that the device aspect-ratio " - "is preserved.\n" - "Default is 0 (unlimited).", + .text = "Limit the frame rate of screen capture (officially supported " + "since Android 10, but may work on earlier versions).", + }, + { + .shortopt = 'n', + .longopt = "no-control", + .text = "Disable device control (mirror the device in read-only).", + }, + { + .shortopt = 'N', + .longopt = "no-playback", + .text = "Disable video and audio playback on the computer (equivalent " + "to --no-video-playback --no-audio-playback).", }, { .longopt_id = OPT_NO_AUDIO, @@ -390,17 +401,6 @@ static const struct sc_option options[] = { "again with a lower definition.\n" "This option disables this behavior.", }, - { - .shortopt = 'n', - .longopt = "no-control", - .text = "Disable device control (mirror the device in read-only).", - }, - { - .shortopt = 'N', - .longopt = "no-playback", - .text = "Disable video and audio playback on the computer (equivalent " - "to --no-video-playback --no-audio-playback).", - }, { // deprecated .longopt_id = OPT_NO_DISPLAY, @@ -484,11 +484,6 @@ static const struct sc_option options[] = { "drag & drop. It is passed as is to \"adb push\".\n" "Default is \"/sdcard/Download/\".", }, - { - .longopt_id = OPT_RAW_KEY_EVENTS, - .longopt = "raw-key-events", - .text = "Inject key events for all input keys, and ignore text events." - }, { .shortopt = 'r', .longopt = "record", @@ -497,6 +492,11 @@ static const struct sc_option options[] = { "The format is determined by the --record-format option if " "set, or by the file extension (.mp4 or .mkv).", }, + { + .longopt_id = OPT_RAW_KEY_EVENTS, + .longopt = "raw-key-events", + .text = "Inject key events for all input keys, and ignore text events." + }, { .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", @@ -535,6 +535,11 @@ static const struct sc_option options[] = { .text = "The device serial number. Mandatory only if several devices " "are connected to adb.", }, + { + .shortopt = 'S', + .longopt = "turn-screen-off", + .text = "Turn the device screen off immediately.", + }, { .longopt_id = OPT_SHORTCUT_MOD, .longopt = "shortcut-mod", @@ -548,11 +553,6 @@ static const struct sc_option options[] = { "shortcuts, pass \"lctrl+lalt,lsuper\".\n" "Default is \"lalt,lsuper\" (left-Alt or left-Super).", }, - { - .shortopt = 'S', - .longopt = "turn-screen-off", - .text = "Turn the device screen off immediately.", - }, { .shortopt = 't', .longopt = "show-touches", @@ -593,6 +593,22 @@ static const struct sc_option options[] = { "Default is 0 (not forced): the local port used for " "establishing the tunnel will be used.", }, + { + .shortopt = 'v', + .longopt = "version", + .text = "Print the version of scrcpy.", + }, + { + .shortopt = 'V', + .longopt = "verbosity", + .argdesc = "value", + .text = "Set the log level (verbose, debug, info, warn or error).\n" +#ifndef NDEBUG + "Default is debug.", +#else + "Default is info.", +#endif + }, { .longopt_id = OPT_V4L2_SINK, .longopt = "v4l2-sink", @@ -613,22 +629,6 @@ static const struct sc_option options[] = { "Default is 0 (no buffering).\n" "This option is only available on Linux.", }, - { - .shortopt = 'V', - .longopt = "verbosity", - .argdesc = "value", - .text = "Set the log level (verbose, debug, info, warn or error).\n" -#ifndef NDEBUG - "Default is debug.", -#else - "Default is info.", -#endif - }, - { - .shortopt = 'v', - .longopt = "version", - .text = "Print the version of scrcpy.", - }, { .longopt_id = OPT_VIDEO_CODEC, .longopt = "video-codec", From 2aec7b4c9d8637f16790a435e14ee020e49b5636 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 Jun 2023 08:59:12 +0200 Subject: [PATCH 0901/1133] Mention how to interrupt scrcpy without video There is no window to close if video playback is disabled. --- doc/audio.md | 3 ++- doc/video.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/audio.md b/doc/audio.md index 40dd6036..c16afd7f 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -30,8 +30,9 @@ To disable only the audio playback, see [no playback](video.md#no-playback). To play audio only, disable the video: -``` +```bash scrcpy --no-video +# interrupt with Ctrl+C ``` Without video, the audio latency is typically not criticial, so it might be diff --git a/doc/video.md b/doc/video.md index e411cf0d..5e7344d9 100644 --- a/doc/video.md +++ b/doc/video.md @@ -168,6 +168,7 @@ the computer. This option is useful when [recording](recording.md) or when ```bash scrcpy --v4l2-sink=/dev/video2 --no-playback scrcpy --record=file.mkv --no-playback +# interrupt with Ctrl+C ``` It is also possible to disable video and audio playback separately: From 379caf8551cdf1f2603decd9b44a8771c3b78475 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 Jun 2023 15:05:23 +0200 Subject: [PATCH 0902/1133] Use a single condvar in recorder The sc_cond_wait() in sc_recorder_process_header() needs to be notified of changes to video_init/audio_init (protected by stream_cond) and video_queue/audio_queue (protected by queue_cond). Use only one condition variable to simplify. --- app/src/recorder.c | 33 ++++++++++++--------------------- app/src/recorder.h | 3 +-- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index be1cbe71..adb1059d 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -181,7 +181,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { while (!recorder->stopped && (!recorder->video_init || !recorder->audio_init || sc_recorder_has_empty_queues(recorder))) { - sc_cond_wait(&recorder->stream_cond, &recorder->mutex); + sc_cond_wait(&recorder->cond, &recorder->mutex); } if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { @@ -289,7 +289,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // A new packet may be assigned to audio_pkt and be processed break; } - sc_cond_wait(&recorder->queue_cond, &recorder->mutex); + sc_cond_wait(&recorder->cond, &recorder->mutex); } // If stopped is set, continue to process the remaining events (to @@ -507,7 +507,7 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, recorder->video_stream_index = stream->index; recorder->video_init = true; - sc_cond_signal(&recorder->stream_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); return true; @@ -522,7 +522,7 @@ sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) { sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder recorder->stopped = true; - sc_cond_signal(&recorder->queue_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); } @@ -557,7 +557,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, return false; } - sc_cond_signal(&recorder->queue_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); return true; @@ -588,7 +588,7 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, recorder->audio_stream_index = stream->index; recorder->audio_init = true; - sc_cond_signal(&recorder->stream_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); return true; @@ -604,7 +604,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder recorder->stopped = true; - sc_cond_signal(&recorder->queue_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); } @@ -640,7 +640,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, return false; } - sc_cond_signal(&recorder->queue_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); return true; @@ -658,7 +658,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { sc_mutex_lock(&recorder->mutex); recorder->audio = false; recorder->audio_init = true; - sc_cond_signal(&recorder->stream_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); } @@ -677,16 +677,11 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_free_filename; } - ok = sc_cond_init(&recorder->queue_cond); + ok = sc_cond_init(&recorder->cond); if (!ok) { goto error_mutex_destroy; } - ok = sc_cond_init(&recorder->stream_cond); - if (!ok) { - goto error_queue_cond_destroy; - } - assert(video || audio); recorder->video = video; recorder->audio = audio; @@ -730,8 +725,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, return true; -error_queue_cond_destroy: - sc_cond_destroy(&recorder->queue_cond); error_mutex_destroy: sc_mutex_destroy(&recorder->mutex); error_free_filename: @@ -756,8 +749,7 @@ void sc_recorder_stop(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); recorder->stopped = true; - sc_cond_signal(&recorder->queue_cond); - sc_cond_signal(&recorder->stream_cond); + sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); } @@ -768,8 +760,7 @@ sc_recorder_join(struct sc_recorder *recorder) { void sc_recorder_destroy(struct sc_recorder *recorder) { - sc_cond_destroy(&recorder->stream_cond); - sc_cond_destroy(&recorder->queue_cond); + sc_cond_destroy(&recorder->cond); sc_mutex_destroy(&recorder->mutex); free(recorder->filename); } diff --git a/app/src/recorder.h b/app/src/recorder.h index 9c6cd4f9..24f0070f 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -35,14 +35,13 @@ struct sc_recorder { sc_thread thread; sc_mutex mutex; - sc_cond queue_cond; + sc_cond cond; // set on sc_recorder_stop(), packet_sink close or recording failure bool stopped; struct sc_recorder_queue video_queue; struct sc_recorder_queue audio_queue; // wake up the recorder thread once the video or audio codec is known - sc_cond stream_cond; bool video_init; bool audio_init; From 9d3c656414ac096ae30e42c22671c72a6bd491af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 Jun 2023 15:09:46 +0200 Subject: [PATCH 0903/1133] Fix recorder waiting when stream disabled In the recorder, if the video or audio stream is disabled, do not wait for its initialization (it will never happen) to process the header. In that case (scrcpy --no-audio --record=file.mp4), this caused the whole content to be buffered in memory, and written only on exit. --- app/src/recorder.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index adb1059d..204bf835 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -178,9 +178,10 @@ static bool sc_recorder_process_header(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->stopped && (!recorder->video_init - || !recorder->audio_init - || sc_recorder_has_empty_queues(recorder))) { + while (!recorder->stopped && + ((recorder->video && !recorder->video_init) + || (recorder->audio && !recorder->audio_init) + || sc_recorder_has_empty_queues(recorder))) { sc_cond_wait(&recorder->cond, &recorder->mutex); } From 9ca554ca417b3f92f8dc1603ed2100706f9ba578 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 Jun 2023 14:41:40 +0200 Subject: [PATCH 0904/1133] Extract stream-specific structure in recorder For now, it only contains the stream index, but more fields will be added. --- app/src/recorder.c | 37 ++++++++++++++++++++----------------- app/src/recorder.h | 8 ++++++-- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 204bf835..df1b1799 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -96,23 +96,21 @@ sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) { } static bool -sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index, - AVPacket *packet) { - AVStream *stream = recorder->ctx->streams[stream_index]; +sc_recorder_write_stream(struct sc_recorder *recorder, + struct sc_recorder_stream *st, AVPacket *packet) { + AVStream *stream = recorder->ctx->streams[st->index]; sc_recorder_rescale_packet(stream, packet); return av_interleaved_write_frame(recorder->ctx, packet) >= 0; } static inline bool sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) { - return sc_recorder_write_stream(recorder, recorder->video_stream_index, - packet); + return sc_recorder_write_stream(recorder, &recorder->video_stream, packet); } static inline bool sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) { - return sc_recorder_write_stream(recorder, recorder->audio_stream_index, - packet); + return sc_recorder_write_stream(recorder, &recorder->audio_stream, packet); } static bool @@ -215,9 +213,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { goto end; } - assert(recorder->video_stream_index >= 0); + assert(recorder->video_stream.index >= 0); AVStream *video_stream = - recorder->ctx->streams[recorder->video_stream_index]; + recorder->ctx->streams[recorder->video_stream.index]; bool ok = sc_recorder_set_extradata(video_stream, video_pkt); if (!ok) { goto end; @@ -230,9 +228,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { goto end; } - assert(recorder->audio_stream_index >= 0); + assert(recorder->audio_stream.index >= 0); AVStream *audio_stream = - recorder->ctx->streams[recorder->audio_stream_index]; + recorder->ctx->streams[recorder->audio_stream.index]; bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt); if (!ok) { goto end; @@ -505,7 +503,7 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, return false; } - recorder->video_stream_index = stream->index; + recorder->video_stream.index = stream->index; recorder->video_init = true; sc_cond_signal(&recorder->cond); @@ -549,7 +547,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink, return false; } - rec->stream_index = recorder->video_stream_index; + rec->stream_index = recorder->video_stream.index; bool ok = sc_vecdeque_push(&recorder->video_queue, rec); if (!ok) { @@ -586,7 +584,7 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, return false; } - recorder->audio_stream_index = stream->index; + recorder->audio_stream.index = stream->index; recorder->audio_init = true; sc_cond_signal(&recorder->cond); @@ -632,7 +630,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, return false; } - rec->stream_index = recorder->audio_stream_index; + rec->stream_index = recorder->audio_stream.index; bool ok = sc_vecdeque_push(&recorder->audio_queue, rec); if (!ok) { @@ -663,6 +661,11 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { sc_mutex_unlock(&recorder->mutex); } +static void +sc_recorder_stream_init(struct sc_recorder_stream *stream) { + stream->index = -1; +} + bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool video, bool audio, @@ -694,8 +697,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_init = false; recorder->audio_init = false; - recorder->video_stream_index = -1; - recorder->audio_stream_index = -1; + sc_recorder_stream_init(&recorder->video_stream); + sc_recorder_stream_init(&recorder->audio_stream); recorder->format = format; diff --git a/app/src/recorder.h b/app/src/recorder.h index 24f0070f..a40b4984 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -14,6 +14,10 @@ struct sc_recorder_queue SC_VECDEQUE(AVPacket *); +struct sc_recorder_stream { + int index; +}; + struct sc_recorder { struct sc_packet_sink video_packet_sink; struct sc_packet_sink audio_packet_sink; @@ -45,8 +49,8 @@ struct sc_recorder { bool video_init; bool audio_init; - int video_stream_index; - int audio_stream_index; + struct sc_recorder_stream video_stream; + struct sc_recorder_stream audio_stream; const struct sc_recorder_callbacks *cbs; void *cbs_userdata; From 323ea2f1d947f6e8917b66db797380a739cd261d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 Jun 2023 14:52:53 +0200 Subject: [PATCH 0905/1133] Fix PTS when not monotonically increasing Some decoders fail to guarantee that PTS is strictly monotonically increasing. Fix the (rescaled) PTS when it does not respect this constraint. Fixes #4054 --- app/src/recorder.c | 10 ++++++++++ app/src/recorder.h | 1 + 2 files changed, 11 insertions(+) diff --git a/app/src/recorder.c b/app/src/recorder.c index df1b1799..23c8b497 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -100,6 +100,15 @@ sc_recorder_write_stream(struct sc_recorder *recorder, struct sc_recorder_stream *st, AVPacket *packet) { AVStream *stream = recorder->ctx->streams[st->index]; sc_recorder_rescale_packet(stream, packet); + if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) { + LOGW("Fixing PTS non monotonically increasing in stream %d " + "(%" PRIi64 " >= %" PRIi64 ")", + st->index, st->last_pts, packet->pts); + packet->pts = ++st->last_pts; + packet->dts = packet->pts; + } else { + st->last_pts = packet->pts; + } return av_interleaved_write_frame(recorder->ctx, packet) >= 0; } @@ -664,6 +673,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { static void sc_recorder_stream_init(struct sc_recorder_stream *stream) { stream->index = -1; + stream->last_pts = AV_NOPTS_VALUE; } bool diff --git a/app/src/recorder.h b/app/src/recorder.h index a40b4984..47fd3f21 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -16,6 +16,7 @@ struct sc_recorder_queue SC_VECDEQUE(AVPacket *); struct sc_recorder_stream { int index; + int64_t last_pts; }; struct sc_recorder { From 888a5aae7d90e6aba969baabb4c951b4ab9f4a7f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 4 Jun 2023 18:40:55 +0200 Subject: [PATCH 0906/1133] Fix typo in recording documentation The option is --record, not --record-file. --- doc/recording.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/recording.md b/doc/recording.md index 6f721062..02e3cfc8 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -17,7 +17,7 @@ To record only the audio: ```bash scrcpy --no-video --record=file.opus -scrcpy --no-video --audio-codec=aac --record-file=file.aac +scrcpy --no-video --audio-codec=aac --record=file.aac # .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac ``` From 2d79aeb117a4532761ffd26c4093f6cbf4a8d337 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 4 Jun 2023 18:43:35 +0200 Subject: [PATCH 0907/1133] Simplify command in documentation If --no-video is passed, --no-playback is equivalent to --no-audio-playback. --- doc/audio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/audio.md b/doc/audio.md index c16afd7f..fd17931e 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -56,7 +56,7 @@ For example, to use the device as a dictaphone and record a capture directly on the computer: ``` -scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus +scrcpy --audio-source=mic --no-video --no-playback --record=file.opus ``` From b8d43866d24495c543edc63d48d180975713e93c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Jun 2023 19:44:15 +0200 Subject: [PATCH 0908/1133] Fix options alphabetical order Commit fc52b2450333beb94c0dc2f94d01d4de13afeec5 missed this one. --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/src/cli.c | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index de298056..a44d38f5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -19,8 +19,8 @@ _scrcpy() { -f --fullscreen --force-adb-forward --forward-all-clicks - -K --hid-keyboard -h --help + -K --hid-keyboard --legacy-paste --list-displays --list-encoders diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 17e30dc5..8d3426dc 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -26,8 +26,8 @@ arguments=( {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' - {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-h,--help}'[Print the help]' + {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' diff --git a/app/src/cli.c b/app/src/cli.c index eb784ebc..318a4230 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -275,6 +275,11 @@ static const struct sc_option options[] = { "middle-click triggers HOME. This option disables these " "shortcuts and forwards the clicks to the device instead.", }, + { + .shortopt = 'h', + .longopt = "help", + .text = "Print this help.", + }, { .shortopt = 'K', .longopt = "hid-keyboard", @@ -292,11 +297,6 @@ static const struct sc_option options[] = { "is enabled (or a physical keyboard is connected).\n" "Also see --hid-mouse.", }, - { - .shortopt = 'h', - .longopt = "help", - .text = "Print this help.", - }, { .longopt_id = OPT_LEGACY_PASTE, .longopt = "legacy-paste", From b16d4d18359cced40c4218a353c16b0093ee0608 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Jun 2023 19:45:20 +0200 Subject: [PATCH 0909/1133] Fix adb server vs adb daemon confusion The adb daemon runs on the device, the adb server runs as a background process on the computer. --- app/src/server.c | 2 +- app/src/usb/scrcpy_otg.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 4e70c4d9..2c0779d9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -805,7 +805,7 @@ run_server(void *data) { // is parsed, so it is not output) bool ok = sc_adb_start_server(&server->intr, 0); if (!ok) { - LOGE("Could not start adb daemon"); + LOGE("Could not start adb server"); goto error_connection_failed; } diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index f469de1a..35d8d4cc 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -83,7 +83,7 @@ scrcpy_otg(struct scrcpy_options *options) { #ifdef _WIN32 // On Windows, only one process could open a USB device // - LOGI("Killing adb daemon (if any)..."); + LOGI("Killing adb server (if any)..."); unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; // uninterruptible (intr == NULL), but in practice it's very quick sc_adb_kill_server(NULL, flags); From a3cdf1a6b86ea22786e1f7d09b9c202feabc6949 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 5 Jun 2023 19:48:21 +0200 Subject: [PATCH 0910/1133] Add option to kill adb on close Killing adb on close by default would be incorrect, since it would break any other usage of adb in parallel. It could be easily done manually by calling "adb kill-server" once scrcpy terminates, but add an option --kill-adb-on-close for convenience. Fixes #205 Fixes #2580 Fixes #4049 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 9 +++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 12 ++++++++++++ app/src/server.h | 1 + 9 files changed, 31 insertions(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a44d38f5..a34a1a44 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -20,6 +20,7 @@ _scrcpy() { --force-adb-forward --forward-all-clicks -h --help + --kill-adb-on-close -K --hid-keyboard --legacy-paste --list-displays diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 8d3426dc..325ccb76 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -27,6 +27,7 @@ arguments=( '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' + '--kill-adb-on-close[Kill adb when scrcpy terminates]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-displays[List displays available on the device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 9b08f182..21c3ac8f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -129,6 +129,10 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO .B \-h, \-\-help Print this help. +.TP +.B \-\-kill\-adb\-on\-close +Kill adb when scrcpy terminates. + .TP .B \-K, \-\-hid\-keyboard Simulate a physical keyboard by using HID over AOAv2. diff --git a/app/src/cli.c b/app/src/cli.c index 318a4230..c9b818a1 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -77,6 +77,7 @@ enum { OPT_NO_AUDIO_PLAYBACK, OPT_NO_VIDEO_PLAYBACK, OPT_AUDIO_SOURCE, + OPT_KILL_ADB_ON_CLOSE, }; struct sc_option { @@ -280,6 +281,11 @@ static const struct sc_option options[] = { .longopt = "help", .text = "Print this help.", }, + { + .longopt_id = OPT_KILL_ADB_ON_CLOSE, + .longopt = "kill-adb-on-close", + .text = "Kill adb when scrcpy terminates.", + }, { .shortopt = 'K', .longopt = "hid-keyboard", @@ -1944,6 +1950,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_KILL_ADB_ON_CLOSE: + opts->kill_adb_on_close = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index e1373753..30b5cb56 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -80,4 +80,5 @@ const struct scrcpy_options scrcpy_options_default = { .require_audio = false, .list_encoders = false, .list_displays = false, + .kill_adb_on_close = false, }; diff --git a/app/src/options.h b/app/src/options.h index c33fafef..75f193b3 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -180,6 +180,7 @@ struct scrcpy_options { bool require_audio; bool list_encoders; bool list_displays; + bool kill_adb_on_close; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 79007bf2..f9679ac1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -364,6 +364,7 @@ scrcpy(struct scrcpy_options *options) { .power_on = options->power_on, .list_encoders = options->list_encoders, .list_displays = options->list_displays, + .kill_adb_on_close = options->kill_adb_on_close, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index 2c0779d9..360e7e7c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -794,6 +794,15 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server, return sc_server_connect_to_tcpip(server, ip_port); } +static void +sc_server_kill_adb_if_requested(struct sc_server *server) { + if (server->params.kill_adb_on_close) { + LOGI("Killing adb server..."); + unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; + sc_adb_kill_server(&server->intr, flags); + } +} + static int run_server(void *data) { struct sc_server *server = data; @@ -993,9 +1002,12 @@ run_server(void *data) { sc_process_close(pid); + sc_server_kill_adb_if_requested(server); + return 0; error_connection_failed: + sc_server_kill_adb_if_requested(server); server->cbs->on_connection_failed(server, server->cbs_userdata); return -1; } diff --git a/app/src/server.h b/app/src/server.h index fad44e66..adba2652 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -58,6 +58,7 @@ struct sc_server_params { bool power_on; bool list_encoders; bool list_displays; + bool kill_adb_on_close; }; struct sc_server { From 4ad74794255b7669a89c023afc7ae7a2c0da4dc3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 8 Jun 2023 08:52:21 +0200 Subject: [PATCH 0911/1133] Add missing shortcut in documentation MOD+Backspace also triggers BACK. --- doc/shortcuts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 6528e7b4..5e706402 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -29,7 +29,7 @@ _[Super] is typically the Windows or Cmd key._ | Resize window to 1:1 (pixel-perfect) | MOD+g | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ | Click on `HOME` | MOD+h \| _Middle-click_ - | Click on `BACK` | MOD+b \| _Right-click²_ + | Click on `BACK` | MOD+b \| MOD+Backspace \| _Right-click²_ | Click on `APP_SWITCH` | MOD+s \| _4th-click³_ | Click on `MENU` (unlock screen)⁴ | MOD+m | Click on `VOLUME_UP` | MOD+ _(up)_ From fdbc9397a7cb93eeac29481effb71eacbb74d24b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Jun 2023 22:24:32 +0200 Subject: [PATCH 0912/1133] Name Java threads Give a user-friendly name to Java threads created by the server. --- .../src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 8 ++++---- .../main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 2 +- .../src/main/java/com/genymobile/scrcpy/Controller.java | 2 +- .../java/com/genymobile/scrcpy/DeviceMessageSender.java | 2 +- .../main/java/com/genymobile/scrcpy/ScreenEncoder.java | 2 +- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 108bbaa1..bec79b05 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -134,7 +134,7 @@ public final class AudioEncoder implements AsyncProcessor { Ln.d("Audio encoder stopped"); listener.onTerminated(fatalError); } - }); + }, "audio-encoder"); thread.start(); } @@ -183,7 +183,7 @@ public final class AudioEncoder implements AsyncProcessor { Codec codec = streamer.getCodec(); mediaCodec = createMediaCodec(codec, encoderName); - mediaCodecThread = new HandlerThread("AudioEncoder"); + mediaCodecThread = new HandlerThread("media-codec"); mediaCodecThread.start(); MediaFormat format = createFormat(codec.getMimeType(), bitRate, codecOptions); @@ -201,7 +201,7 @@ public final class AudioEncoder implements AsyncProcessor { } finally { end(); } - }); + }, "audio-in"); outputThread = new Thread(() -> { try { @@ -216,7 +216,7 @@ public final class AudioEncoder implements AsyncProcessor { } finally { end(); } - }); + }, "audio-out"); mediaCodec.start(); mediaCodecStarted = true; diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 2fc8c887..7d2adade 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -69,7 +69,7 @@ public final class AudioRawRecorder implements AsyncProcessor { Ln.d("Audio recorder stopped"); listener.onTerminated(fatalError); } - }); + }, "audio-raw"); thread.start(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 9a4e275a..733a2032 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -95,7 +95,7 @@ public class Controller implements AsyncProcessor { Ln.d("Controller stopped"); listener.onTerminated(true); } - }); + }, "control-recv"); thread.start(); sender.start(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 0ef2a9ee..628c1d3c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -60,7 +60,7 @@ public final class DeviceMessageSender { } finally { Ln.d("Device message sender stopped"); } - }); + }, "control-send"); thread.start(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index d56e5d27..ce7c2838 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -299,7 +299,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen Ln.d("Screen streaming stopped"); listener.onTerminated(true); } - }); + }, "video"); thread.start(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 214ac27d..91e6f40a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -185,7 +185,7 @@ public final class Server { } private static Thread startInitThread(final Options options) { - Thread thread = new Thread(() -> initAndCleanUp(options)); + Thread thread = new Thread(() -> initAndCleanUp(options), "init-cleanup"); thread.start(); return thread; } From 28313631e549444795d9282574c1526c5d4008ce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Jun 2023 22:28:01 +0200 Subject: [PATCH 0913/1133] Reformat Java code Fix code style. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 91e6f40a..b2ee2fa9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -137,8 +137,7 @@ public final class Server { if (audio) { AudioCodec audioCodec = options.getAudioCodec(); AudioCapture audioCapture = new AudioCapture(options.getAudioSource()); - Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), - options.getSendFrameMeta()); + Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta()); AsyncProcessor audioRecorder; if (audioCodec == AudioCodec.RAW) { audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer); From 6832e8d629305b8ad90b125258ebf612aae88af0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Jun 2023 12:07:35 +0200 Subject: [PATCH 0914/1133] Remove spurious empty line --- .../java/com/genymobile/scrcpy/ControlMessageReaderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 8405905a..47097c78 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; - public class ControlMessageReaderTest { @Test From 7536f95d1c1a8b48f12a7a4556d571b9a2f72667 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Jun 2023 12:09:23 +0200 Subject: [PATCH 0915/1133] Rename raw_video_stream to raw_stream This server-specific option impacts both the video and audio streams. --- server/src/main/java/com/genymobile/scrcpy/Options.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 23d4e383..aab6fce8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -318,9 +318,9 @@ public class Options { case "send_codec_meta": options.sendCodecMeta = Boolean.parseBoolean(value); break; - case "raw_video_stream": - boolean rawVideoStream = Boolean.parseBoolean(value); - if (rawVideoStream) { + case "raw_stream": + boolean rawStream = Boolean.parseBoolean(value); + if (rawStream) { options.sendDeviceMeta = false; options.sendFrameMeta = false; options.sendDummyByte = false; From 5042f8de933faecbeea270fc78713c294d93ec06 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Jun 2023 18:44:53 +0200 Subject: [PATCH 0916/1133] Improve recording documentation --- doc/recording.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/recording.md b/doc/recording.md index 02e3cfc8..d0c33181 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -21,20 +21,15 @@ scrcpy --no-video --audio-codec=aac --record=file.aac # .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac ``` -To disable playback while recording: - -```bash -scrcpy --no-playback --record=file.mp4 -scrcpy -Nr file.mkv -# interrupt recording with Ctrl+C -``` - Timestamps are captured on the device, so [packet delay variation] does not impact the recorded file, which is always clean (only if you use `--record` of course, not if you capture your scrcpy window and audio output on the computer). [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation + +## Format + The video and audio streams are encoded on the device, but are muxed on the client side. Two formats (containers) are supported: - Matroska (`.mkv`) @@ -48,3 +43,21 @@ needs not end with `.mkv` or `.mp4`): ``` scrcpy --record=file --record-format=mkv ``` + + +## No playback + +To disable playback while recording: + +```bash +scrcpy --no-playback --record=file.mp4 +scrcpy -Nr file.mkv +# interrupt recording with Ctrl+C +``` + +It is also possible to disable video and audio playback separately: + +```bash +# Record both video and audio, but only play video +scrcpy --record=file.mkv --no-audio-playback +``` From d3c2955fb9e46949694ce91cbe6cc15ca068207b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Jun 2023 18:46:50 +0200 Subject: [PATCH 0917/1133] Add --time-limit Add an option to stop scrcpy automatically after a given delay. PR #4052 Fixes #3752 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/meson.build | 1 + app/scrcpy.1 | 4 ++ app/src/cli.c | 24 ++++++++++ app/src/events.h | 1 + app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 47 ++++++++++++++++++++ app/src/util/timeout.c | 77 +++++++++++++++++++++++++++++++++ app/src/util/timeout.h | 43 ++++++++++++++++++ doc/recording.md | 15 +++++++ 12 files changed, 216 insertions(+) create mode 100644 app/src/util/timeout.c create mode 100644 app/src/util/timeout.h diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a34a1a44..003f9d73 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -60,6 +60,7 @@ _scrcpy() { -t --show-touches --tcpip --tcpip= + --time-limit= --tunnel-host= --tunnel-port= --v4l2-buffer= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 325ccb76..81142851 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -65,6 +65,7 @@ arguments=( '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' {-t,--show-touches}'[Show physical touches]' '--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]' + '--time-limit=[Set the maximum mirroring time, in seconds]' '--tunnel-host=[Set the IP address of the adb tunnel to reach the scrcpy server]' '--tunnel-port=[Set the TCP port of the adb tunnel to reach the scrcpy server]' '--v4l2-buffer=[Add a buffering delay \(in milliseconds\) before pushing frames]' diff --git a/app/meson.build b/app/meson.build index 061fdcab..e0d92050 100644 --- a/app/meson.build +++ b/app/meson.build @@ -51,6 +51,7 @@ src = [ 'src/util/term.c', 'src/util/thread.c', 'src/util/tick.c', + 'src/util/timeout.c', ] conf = configuration_data() diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 21c3ac8f..0c91701f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -354,6 +354,10 @@ If a destination address is provided, then scrcpy connects to this address befor If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting. +.TP +.BI "\-\-time\-limit " seconds +Set the maximum mirroring time, in seconds. + .TP .BI "\-\-tunnel\-host " ip Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. diff --git a/app/src/cli.c b/app/src/cli.c index c9b818a1..72f4bea1 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -78,6 +78,7 @@ enum { OPT_NO_VIDEO_PLAYBACK, OPT_AUDIO_SOURCE, OPT_KILL_ADB_ON_CLOSE, + OPT_TIME_LIMIT, }; struct sc_option { @@ -580,6 +581,12 @@ static const struct sc_option options[] = { "connected over USB), enables TCP/IP mode, then connects to " "this address before starting.", }, + { + .longopt_id = OPT_TIME_LIMIT, + .longopt = "time-limit", + .argdesc = "seconds", + .text = "Set the maximum mirroring time, in seconds.", + }, { .longopt_id = OPT_TUNNEL_HOST, .longopt = "tunnel-host", @@ -1618,6 +1625,18 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) { return false; } +static bool +parse_time_limit(const char *s, sc_tick *tick) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "time limit"); + if (!ok) { + return false; + } + + *tick = SC_TICK_FROM_SEC(value); + return true; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1953,6 +1972,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_KILL_ADB_ON_CLOSE: opts->kill_adb_on_close = true; break; + case OPT_TIME_LIMIT: + if (!parse_time_limit(optarg, &opts->time_limit)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/events.h b/app/src/events.h index 609e3198..8bfa2582 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -6,3 +6,4 @@ #define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5) #define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) #define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7) +#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8) diff --git a/app/src/options.c b/app/src/options.c index 30b5cb56..530e003b 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -42,6 +42,7 @@ const struct scrcpy_options scrcpy_options_default = { .display_buffer = 0, .audio_buffer = SC_TICK_FROM_MS(50), .audio_output_buffer = SC_TICK_FROM_MS(5), + .time_limit = 0, #ifdef HAVE_V4L2 .v4l2_device = NULL, .v4l2_buffer = 0, diff --git a/app/src/options.h b/app/src/options.h index 75f193b3..1f36ad7f 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -142,6 +142,7 @@ struct scrcpy_options { sc_tick display_buffer; sc_tick audio_buffer; sc_tick audio_output_buffer; + sc_tick time_limit; #ifdef HAVE_V4L2 const char *v4l2_device; sc_tick v4l2_buffer; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f9679ac1..fd310c46 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -35,6 +35,7 @@ #include "util/log.h" #include "util/net.h" #include "util/rand.h" +#include "util/timeout.h" #ifdef HAVE_V4L2 # include "v4l2_sink.h" #endif @@ -73,6 +74,7 @@ struct scrcpy { struct sc_hid_mouse mouse_hid; #endif }; + struct sc_timeout timeout; }; static inline void @@ -171,6 +173,9 @@ event_loop(struct scrcpy *s) { case SC_EVENT_RECORDER_ERROR: LOGE("Recorder error"); return SCRCPY_EXIT_FAILURE; + case SC_EVENT_TIME_LIMIT_REACHED: + LOGI("Time limit reached"); + return SCRCPY_EXIT_SUCCESS; case SDL_QUIT: LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; @@ -280,6 +285,14 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) { // event } +static void +sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) { + (void) timeout; + (void) userdata; + + PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED); +} + // Generate a scrcpy id to differentiate multiple running scrcpy instances static uint32_t scrcpy_generate_scid() { @@ -321,6 +334,8 @@ scrcpy(struct scrcpy_options *options) { bool controller_initialized = false; bool controller_started = false; bool screen_initialized = false; + bool timeout_initialized = false; + bool timeout_started = false; struct sc_acksync *acksync = NULL; @@ -743,6 +758,27 @@ aoa_hid_end: } } + if (options->time_limit) { + bool ok = sc_timeout_init(&s->timeout); + if (!ok) { + goto end; + } + + timeout_initialized = true; + + sc_tick deadline = sc_tick_now() + options->time_limit; + static const struct sc_timeout_callbacks cbs = { + .on_timeout = sc_timeout_on_timeout, + }; + + ok = sc_timeout_start(&s->timeout, deadline, &cbs, NULL); + if (!ok) { + goto end; + } + + timeout_started = true; + } + ret = event_loop(s); LOGD("quit..."); @@ -751,6 +787,10 @@ aoa_hid_end: sc_screen_hide_window(&s->screen); end: + if (timeout_started) { + sc_timeout_stop(&s->timeout); + } + // The demuxer is not stopped explicitly, because it will stop by itself on // end-of-stream #ifdef HAVE_USB @@ -786,6 +826,13 @@ end: sc_server_stop(&s->server); } + if (timeout_started) { + sc_timeout_join(&s->timeout); + } + if (timeout_initialized) { + sc_timeout_destroy(&s->timeout); + } + // now that the sockets are shutdown, the demuxer and controller are // interrupted, we can join them if (video_demuxer_started) { diff --git a/app/src/util/timeout.c b/app/src/util/timeout.c new file mode 100644 index 00000000..a1665373 --- /dev/null +++ b/app/src/util/timeout.c @@ -0,0 +1,77 @@ +#include "timeout.h" + +#include + +#include "log.h" + +bool +sc_timeout_init(struct sc_timeout *timeout) { + bool ok = sc_mutex_init(&timeout->mutex); + if (!ok) { + return false; + } + + ok = sc_cond_init(&timeout->cond); + if (!ok) { + return false; + } + + timeout->stopped = false; + + return true; +} + +static int +run_timeout(void *data) { + struct sc_timeout *timeout = data; + sc_tick deadline = timeout->deadline; + + sc_mutex_lock(&timeout->mutex); + bool timed_out = false; + while (!timeout->stopped && !timed_out) { + timed_out = !sc_cond_timedwait(&timeout->cond, &timeout->mutex, + deadline); + } + sc_mutex_unlock(&timeout->mutex); + + timeout->cbs->on_timeout(timeout, timeout->cbs_userdata); + + return 0; +} + +bool +sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline, + const struct sc_timeout_callbacks *cbs, void *cbs_userdata) { + bool ok = sc_thread_create(&timeout->thread, run_timeout, "scrcpy-timeout", + timeout); + if (!ok) { + LOGE("Timeout: could not start thread"); + return false; + } + + timeout->deadline = deadline; + + assert(cbs && cbs->on_timeout); + timeout->cbs = cbs; + timeout->cbs_userdata = cbs_userdata; + + return true; +} + +void +sc_timeout_stop(struct sc_timeout *timeout) { + sc_mutex_lock(&timeout->mutex); + timeout->stopped = true; + sc_mutex_unlock(&timeout->mutex); +} + +void +sc_timeout_join(struct sc_timeout *timeout) { + sc_thread_join(&timeout->thread, NULL); +} + +void +sc_timeout_destroy(struct sc_timeout *timeout) { + sc_mutex_destroy(&timeout->mutex); + sc_cond_destroy(&timeout->cond); +} diff --git a/app/src/util/timeout.h b/app/src/util/timeout.h new file mode 100644 index 00000000..ae171b86 --- /dev/null +++ b/app/src/util/timeout.h @@ -0,0 +1,43 @@ +#ifndef SC_TIMEOUT_H +#define SC_TIMEOUT_H + +#include "common.h" + +#include + +#include "thread.h" +#include "tick.h" + +struct sc_timeout { + sc_thread thread; + sc_tick deadline; + + sc_mutex mutex; + sc_cond cond; + bool stopped; + + const struct sc_timeout_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_timeout_callbacks { + void (*on_timeout)(struct sc_timeout *timeout, void *userdata); +}; + +bool +sc_timeout_init(struct sc_timeout *timeout); + +void +sc_timeout_destroy(struct sc_timeout *timeout); + +bool +sc_timeout_start(struct sc_timeout *timeout, sc_tick deadline, + const struct sc_timeout_callbacks *cbs, void *cbs_userdata); + +void +sc_timeout_stop(struct sc_timeout *timeout); + +void +sc_timeout_join(struct sc_timeout *timeout); + +#endif diff --git a/doc/recording.md b/doc/recording.md index d0c33181..76a7efd6 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -61,3 +61,18 @@ It is also possible to disable video and audio playback separately: # Record both video and audio, but only play video scrcpy --record=file.mkv --no-audio-playback ``` + +## Time limit + +To limit the recording time: + +```bash +scrcpy --record=file.mkv --time-limit=20 # in seconds +``` + +The `--time-limit` option is not limited to recording, it also impacts simple +mirroring: + +``` +scrcpy --time-limit=20 +``` From 5bd75148716496755dc32daab21a322f51dc58ca Mon Sep 17 00:00:00 2001 From: wuderek Date: Fri, 9 Jun 2023 06:03:09 +0000 Subject: [PATCH 0918/1133] Add InputManagerGlobal for Android 14 beta 3 Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview. Fixes #4074 PR #4075 Signed-off-by: Romain Vimont --- .../genymobile/scrcpy/wrappers/InputManager.java | 4 ++-- .../genymobile/scrcpy/wrappers/ServiceManager.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 32bf4252..ef0a4f50 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -14,13 +14,13 @@ public final class InputManager { public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; - private final android.hardware.input.InputManager manager; + private final Object manager; private Method injectInputEventMethod; private static Method setDisplayIdMethod; private static Method setActionButtonMethod; - public InputManager(android.hardware.input.InputManager manager) { + public InputManager(Object manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index ee2f0fa9..69803971 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -62,11 +62,21 @@ public final class ServiceManager { return displayManager; } + public static Class getInputManagerClass() { + try { + // Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview + return Class.forName("android.hardware.input.InputManagerGlobal"); + } catch (ClassNotFoundException e) { + return android.hardware.input.InputManager.class; + } + } + public static InputManager getInputManager() { if (inputManager == null) { try { - Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance"); - android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null); + Class inputManagerClass = getInputManagerClass(); + Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); + Object im = getInstanceMethod.invoke(null); inputManager = new InputManager(im); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new AssertionError(e); From 3b7e2ca9c8c648b3dadbeec25cdb087772cc6c99 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 16 Jun 2023 23:24:08 +0200 Subject: [PATCH 0919/1133] Fix lint warning Suppress lint "DiscouragedPrivateApi" in Workarounds.java. --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index b343a344..9ae7983f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -106,7 +106,7 @@ public final class Workarounds { } @TargetApi(Build.VERSION_CODES.R) - @SuppressLint({"WrongConstant", "MissingPermission", "BlockedPrivateApi", "SoonBlockedPrivateApi"}) + @SuppressLint("WrongConstant,MissingPermission,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi") public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) { // Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment. // From 48a00fb481cec1d60cb41c1d7a6aa9eebf87878e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 17 Jun 2023 00:12:42 +0200 Subject: [PATCH 0920/1133] Log device BRAND The BRAND value is not always the same as the MANUFACTURER value. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index b2ee2fa9..13802275 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -87,7 +87,7 @@ public final class Server { } private static void scrcpy(Options options) throws IOException, ConfigurationException { - Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); final Device device = new Device(options); Thread initThread = startInitThread(options); From 0f1afff7a62e3fcc51a93025ed61a09408147413 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 19 Jun 2023 13:30:36 +0200 Subject: [PATCH 0921/1133] Move workarounds execution Expose a single public static method in the Workarounds class to apply all necessary workarounds. --- .../java/com/genymobile/scrcpy/Server.java | 21 +------------- .../com/genymobile/scrcpy/Workarounds.java | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 13802275..2e6e1d4a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -99,26 +99,7 @@ public final class Server { boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); - Workarounds.prepareMainLooper(); - - // Workarounds must be applied for Meizu phones: - // - - // - - // - - // - // But only apply when strictly necessary, since workarounds can cause other issues: - // - - // - - if (Build.BRAND.equalsIgnoreCase("meizu")) { - Workarounds.fillAppInfo(); - } - - // Before Android 11, audio is not supported. - // Since Android 12, we can properly set a context on the AudioRecord. - // Only on Android 11 we must fill the application context for the AudioRecord to work. - if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { - Workarounds.fillAppContext(); - } + Workarounds.apply(audio); List asyncProcessors = new ArrayList<>(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 9ae7983f..ded1d9cb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -27,8 +27,31 @@ public final class Workarounds { // not instantiable } + public static void apply(boolean audio) { + Workarounds.prepareMainLooper(); + + // Workarounds must be applied for Meizu phones: + // - + // - + // - + // + // But only apply when strictly necessary, since workarounds can cause other issues: + // - + // - + if (Build.BRAND.equalsIgnoreCase("meizu")) { + Workarounds.fillAppInfo(); + } + + // Before Android 11, audio is not supported. + // Since Android 12, we can properly set a context on the AudioRecord. + // Only on Android 11 we must fill the application context for the AudioRecord to work. + if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + Workarounds.fillAppContext(); + } + } + @SuppressWarnings("deprecation") - public static void prepareMainLooper() { + private static void prepareMainLooper() { // Some devices internally create a Handler when creating an input Surface, causing an exception: // "Can't create handler inside thread that has not called Looper.prepare()" // @@ -57,7 +80,7 @@ public final class Workarounds { } @SuppressLint("PrivateApi,DiscouragedPrivateApi") - public static void fillAppInfo() { + private static void fillAppInfo() { try { fillActivityThread(); @@ -86,7 +109,7 @@ public final class Workarounds { } @SuppressLint("PrivateApi,DiscouragedPrivateApi") - public static void fillAppContext() { + private static void fillAppContext() { try { fillActivityThread(); From fb21bbf763691407bc069d84b8e6248667a80e6f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 18 Jun 2023 17:52:43 +0200 Subject: [PATCH 0922/1133] Add workarounds for Honor devices Audio did not work on Honor devices. To make it work, a system context must be set as a base context of FakeContext (so that a PackageManager is available), and a current Application and ActivityThread must be set. These workarounds must not be applied for all devices, because they might cause other issues. Fixes #4015 Refs #3085 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- .../com/genymobile/scrcpy/FakeContext.java | 4 +- .../com/genymobile/scrcpy/Workarounds.java | 63 +++++++++++++++---- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 738203de..6501d4cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -2,11 +2,11 @@ package com.genymobile.scrcpy; import android.annotation.TargetApi; import android.content.AttributionSource; -import android.content.ContextWrapper; +import android.content.MutableContextWrapper; import android.os.Build; import android.os.Process; -public final class FakeContext extends ContextWrapper { +public final class FakeContext extends MutableContextWrapper { public static final String PACKAGE_NAME = "com.android.shell"; public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index ded1d9cb..b8294a87 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Application; import android.content.AttributionSource; +import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.media.AudioAttributes; @@ -30,22 +31,47 @@ public final class Workarounds { public static void apply(boolean audio) { Workarounds.prepareMainLooper(); - // Workarounds must be applied for Meizu phones: - // - - // - - // - - // - // But only apply when strictly necessary, since workarounds can cause other issues: - // - - // - + boolean mustFillAppInfo = false; + boolean mustFillBaseContext = false; + boolean mustFillAppContext = false; + + if (Build.BRAND.equalsIgnoreCase("meizu")) { - Workarounds.fillAppInfo(); + // Workarounds must be applied for Meizu phones: + // - + // - + // - + // + // But only apply when strictly necessary, since workarounds can cause other issues: + // - + // - + mustFillAppInfo = true; + } else if (Build.BRAND.equalsIgnoreCase("honor")) { + // More workarounds must be applied for Honor devices: + // - + // + // The system context must not be set for all devices, because it would cause other problems: + // - + // - + mustFillAppInfo = true; + mustFillBaseContext = true; + mustFillAppContext = true; } - // Before Android 11, audio is not supported. - // Since Android 12, we can properly set a context on the AudioRecord. - // Only on Android 11 we must fill the application context for the AudioRecord to work. if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { + // Before Android 11, audio is not supported. + // Since Android 12, we can properly set a context on the AudioRecord. + // Only on Android 11 we must fill the application context for the AudioRecord to work. + mustFillAppContext = true; + } + + if (mustFillAppInfo) { + Workarounds.fillAppInfo(); + } + if (mustFillBaseContext) { + Workarounds.fillBaseContext(); + } + if (mustFillAppContext) { Workarounds.fillAppContext(); } } @@ -128,6 +154,19 @@ public final class Workarounds { } } + public static void fillBaseContext() { + try { + fillActivityThread(); + + Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext"); + Context context = (Context) getSystemContextMethod.invoke(activityThread); + FakeContext.get().setBaseContext(context); + } catch (Throwable throwable) { + // this is a workaround, so failing is not an error + Ln.d("Could not fill base context: " + throwable.getMessage()); + } + } + @TargetApi(Build.VERSION_CODES.R) @SuppressLint("WrongConstant,MissingPermission,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi") public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) { From 09009c2aa71e1f748abd32b74c0f2014ad606f4e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 20 Jun 2023 21:45:14 +0200 Subject: [PATCH 0923/1133] Upgrade SDL (2.28.0) for Windows Include the latest version of SDL in Windows releases. Fixes #3825 Refs libsdl/#7478 --- app/prebuilt-deps/prepare-sdl.sh | 6 +++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index 60bffae9..b691aac5 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.26.4 +DEP_DIR=SDL2-2.28.0 -FILENAME=SDL2-devel-2.26.4-mingw.tar.gz -SHA256SUM=fe899c8642caac2f180b1ee6f786857ddcaa0adc1fa82474312b09dd47d74712 +FILENAME=SDL2-devel-2.28.0-mingw.tar.gz +SHA256SUM=b91ce59eeacd4a9db403f976fd2337d9360b21ada374124417d716065c380e20 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index f3fded40..c3f72540 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32' -prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.28.0/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 1b02b93f..66cddf83 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64' -prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.28.0/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index e41a45ad..a467352c 100644 --- a/release.mk +++ b/release.mk @@ -101,7 +101,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.26.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 @@ -119,7 +119,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.26.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 From 5061b7e02c3aa932f245adc98686bee57d6abfaf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 21 Jun 2023 23:56:06 +0200 Subject: [PATCH 0924/1133] Fix build without gradle Add missing class generation from IDisplayFoldListener.aidl. Refs 24999d0d32daf50246168dde212f01cf109569f9 --- server/build_without_gradle.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 3201034f..f13fd0dc 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -48,6 +48,7 @@ cd "$SERVER_DIR/src/main/aidl" "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \ android/content/IOnPrimaryClipChangedListener.aidl +"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl echo "Compiling java sources..." cd ../java From fae3fbc934b6b58f877439a1bffa6aaf9793f46a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 10 Jun 2023 12:26:04 +0200 Subject: [PATCH 0925/1133] Update developer documentation --- doc/develop.md | 413 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 296 insertions(+), 117 deletions(-) diff --git a/doc/develop.md b/doc/develop.md index bd409fff..67d7f9b0 100644 --- a/doc/develop.md +++ b/doc/develop.md @@ -9,16 +9,52 @@ This application is composed of two parts: The client is responsible to push the server to the device and start its execution. -Once the client and the server are connected to each other, the server initially -sends device information (name and initial screen dimensions), then starts to -send a raw H.264 video stream of the device screen. The client decodes the video -frames, and display them as soon as possible, without buffering, to minimize -latency. The client is not aware of the device rotation (which is handled by the -server), it just knows the dimensions of the video frames. +The client and the server establish communication using separate sockets for +video, audio and controls. Any of them may be disabled (but not all), so +there are 1, 2 or 3 socket(s). + +The server initially sends the device name on the first socket (it is used for +the scrcpy window title), then each socket is used for its own purpose. All +reads and writes are performed from a dedicated thread for each socket, both on +the client and on the server. + +If video is enabled, then the server sends a raw video stream (H.264 by default) +of the device screen, with some additional headers for each packet. The client +decodes the video frames, and displays them as soon as possible, without +buffering (unless `--display-buffer=delay` is specified) to minimize latency. +The client is not aware of the device rotation (which is handled by the server), +it just knows the dimensions of the video frames it receives. + +Similarly, if audio is enabled, then the server sends a raw audio stream (OPUS +by default) of the device audio output (or the microphone if +`--audio-source=mic` is specified), with some additional headers for each +packet. The client decodes the stream, attempts to keep a minimal latency by +maintaining an average buffering. The [blog post][scrcpy2] of the scrcpy v2.0 +release gives more details about the audio feature. + +If control is enabled, then the client captures relevant keyboard and mouse +events, that it transmits to the server, which injects them to the device. This +is the only socket which is used in both direction: input events are sent from +the client to the device, and when the device clipboard changes, the new content +is sent from the device to the client to support seamless copy-paste. + +[scrcpy2]: https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio/ -The client captures relevant keyboard and mouse events, that it transmits to the -server, which injects them to the device. +Note that the client-server roles are expressed at the application level: + + - the server _serves_ video and audio streams, and handle requests from the + client, + - the client _controls_ the device through the server. + +However, by default (when `--force-adb-forward` is not set), the roles are +reversed at the network level: + + - the client opens a server socket and listen on a port before starting the + server, + - the server connects to the client. +This role inversion guarantees that the connection will not fail due to race +conditions without polling. ## Server @@ -32,15 +68,14 @@ The server is a Java application (with a [`public static void main(String... args)`][main] method), compiled against the Android framework, and executed as `shell` on the Android device. -[main]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Server.java#L123 +[main]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Server.java#L193 To run such a Java application, the classes must be [_dexed_][dex] (typically, to `classes.dex`). If `my.package.MainClass` is the main class, compiled to `classes.dex`, pushed to the device in `/data/local/tmp`, then it can be run with: - adb shell CLASSPATH=/data/local/tmp/classes.dex \ - app_process / my.package.MainClass + adb shell CLASSPATH=/data/local/tmp/classes.dex app_process / my.package.MainClass _The path `/data/local/tmp` is a good candidate to push the server, since it's readable and writable by `shell`, but not world-writable, so a malicious @@ -49,7 +84,7 @@ application may not replace the server just before the client executes it._ Instead of a raw _dex_ file, `app_process` accepts a _jar_ containing `classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle build system, the server is built to an (unsigned) APK (renamed to -`scrcpy-server`). +`scrcpy-server.jar`). [dex]: https://en.wikipedia.org/wiki/Dalvik_(software) [apk]: https://en.wikipedia.org/wiki/Android_application_package @@ -65,42 +100,77 @@ They can be called using reflection though. The communication with hidden components is provided by [_wrappers_ classes][wrappers] and [aidl]. [hidden]: https://stackoverflow.com/a/31908373/1987178 -[wrappers]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers -[aidl]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/aidl/android/view +[wrappers]: https://github.com/Genymobile/scrcpy/tree/master/server/src/main/java/com/genymobile/scrcpy/wrappers +[aidl]: https://github.com/Genymobile/scrcpy/tree/master/server/src/main/aidl + + + +### Execution + +The server is started by the client basically by executing the following +commands: + +```bash +adb push scrcpy-server /data/local/tmp/scrcpy-server.jar +adb forward tcp:27183 localabstract:scrcpy +adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1 +``` +The first argument (`2.1` in the example) is the client scrcpy version. The +server fails if the client and the server do not have the exact same version. +The protocol between the client and the server may change from version to +version (see [protocol](#protocol) below), and there is no backward or forward +compatibility (there is no point to use different client and server versions). +This check allows to detect misconfiguration (running an older or newer server +by mistake). -### Threading +It is followed by any number of arguments, in the form of `key=value` pairs. +Their order is irrelevant. The possible keys and associated value types can be +found in the [server][server-options] and [client][client-options] code. -The server uses 3 threads: +[server-options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L181 +[client-options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/server.c#L226 - - the **main** thread, encoding and streaming the video to the client; - - the **controller** thread, listening for _control messages_ (typically, - keyboard and mouse events) from the client; - - the **receiver** thread (managed by the controller), sending _device messages_ - to the clients (currently, it is only used to send the device clipboard - content). +For example, if we execute `scrcpy -m1920 --no-audio`, then the server +execution will look like this: -Since the video encoding is typically hardware, there would be no benefit in -encoding and streaming in two different threads. +```bash +# scid is a random number to identify different clients running on the same device +adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1 scid=12345678 log_level=info audio=false max_size=1920 +``` + +### Components + +When executed, its [`main()`][main] method is executed (on the "main" thread). +It parses the arguments, establishes the connection with the client and starts +the other "components": + - the **video** streamer: it captures the video screen and send encoded video + packets on the _video_ socket (from the _video_ thread). + - the **audio** streamer: it uses several threads to capture raw packets, + submits them to encoding and retrieve encoded packets, which it sends on the + _audio_ socket. + - the **controller**: it receives _control messages_ (typically input events) + on the _control_ socket from one thread, and sends _device messages_ (e.g. to + transmit the device clipboard content to the client) on the same _control + socket_ from another thread. Thus, the _control_ socket is used in both + directions (contrary to the _video_ and _audio_ sockets). ### Screen video encoding The encoding is managed by [`ScreenEncoder`]. -The video is encoded using the [`MediaCodec`] API. The codec takes its input -from a [surface] associated to the display, and writes the resulting H.264 -stream to the provided output stream (the socket connected to the client). +The video is encoded using the [`MediaCodec`] API. The codec encodes the content +of a `Surface` associated to the display, and writes the encoding packets to the +client (on the _video_ socket). -[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java [`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html -[surface]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L68-L69 -On device [rotation], the codec, surface and display are reinitialized, and a -new video stream is produced. +On device rotation (or folding), the encoding session is [reset] and restarted. -New frames are produced only when changes occur on the surface. This is good -because it avoids to send unnecessary frames, but there are drawbacks: +New frames are produced only when changes occur on the surface. This avoids to +send unnecessary frames, but by default there might be drawbacks: - it does not send any frame on start if the device screen does not change, - after fast motion changes, the last frame may have poor quality. @@ -108,11 +178,24 @@ because it avoids to send unnecessary frames, but there are drawbacks: Both problems are [solved][repeat] by the flag [`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag]. +[reset]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L179 [rotation]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90 -[repeat]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148 +[repeat]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L246-L247 [repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER +### Audio encoding + +Similarly, the audio is [captured] using an [`AudioRecord`], and [encoded] using +the [`MediaCodec`] asynchronous API. + +More details are available on the [blog post][scrcpy2] introducing the audio feature. + +[captured]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +[encoded]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +[`AudioRecord`]: https://developer.android.com/reference/android/media/AudioRecord + + ### Input events injection _Control messages_ are received from the client by the [`Controller`] (run in a @@ -124,13 +207,13 @@ separate thread). There are several types of input events: - other commands (e.g. to switch the screen on or to copy the clipboard). Some of them need to inject input events to the system. To do so, they use the -_hidden_ method [`InputManager.injectInputEvent`] (exposed by our +_hidden_ method [`InputManager.injectInputEvent()`] (exposed by the [`InputManager` wrapper][inject-wrapper]). -[`Controller`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Controller.java#L81 +[`Controller`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Controller.java [`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html [`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html -[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857 +[`InputManager.injectInputEvent()`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L34 [inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27 @@ -140,126 +223,222 @@ _hidden_ method [`InputManager.injectInputEvent`] (exposed by our The client relies on [SDL], which provides cross-platform API for UI, input events, threading, etc. -The video stream is decoded by [libav] (FFmpeg). +The video and audio streams are decoded by [FFmpeg]. [SDL]: https://www.libsdl.org -[libav]: https://www.libav.org/ +[ffmpeg]: https://ffmpeg.org/ + ### Initialization -On startup, in addition to _libav_ and _SDL_ initialization, the client must -push and start the server on the device, and open two sockets (one for the video -stream, one for control) so that they may communicate. +The client parses the command line arguments, then [runs one of two code +paths][run]: + - scrcpy in "normal" mode ([`scrcpy.c`]) + - scrcpy in [OTG mode](hid-otg.md) ([`scrcpy_otg.c`]) -Note that the client-server roles are expressed at the application level: +[run]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/main.c#L81-L82 +[`scrcpy.c`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/scrcpy.c#L292-L293 +[`scrcpy_otg.c`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/usb/scrcpy_otg.c#L51-L52 - - the server _serves_ video stream and handle requests from the client, - - the client _controls_ the device through the server. +In the remaining of this document, we assume that the "normal" mode is used +(read the code for the OTG mode). -However, the roles are reversed at the network level: +On startup, the client: + - opens the _video_, _audio_ and _control_ sockets; + - pushes and starts the server on the device; + - initializes its components (demuxers, decoders, recorder…). - - the client opens a server socket and listen on a port before starting the - server, - - the server connects to the client. -This role inversion guarantees that the connection will not fail due to race -conditions, and avoids polling. +### Video and audio streams -_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb -reverse`. See commit [1038bad] and [issue #5].)_ +Depending on the arguments passed to `scrcpy`, several components may be used. +Here is an overview of the video and audio components: -Once the server is connected, it sends the device information (name and initial -screen dimensions). Thus, the client may init the window and renderer, before -the first frame is available. +``` + V4L2 sink + / + decoder + / \ + VIDEO -------------> demuxer display + \ + recorder + / + AUDIO -------------> demuxer + \ + decoder --- audio player +``` + +The _demuxer_ is responsible to extract video and audio packets (read some +header, split the video stream into packets at correct boundaries, etc.). + +The demuxed packets may be sent to a _decoder_ (one per stream, to produce +frames) and to a recorder (receiving both video and audio stream to record a +single file). The packets are encoded on the device (by `MediaCodec`), but when +recording, they are _muxed_ (asynchronously) into a container (MKV or MP4) on +the client side. + +Video frames are sent to the screen/display to be rendered in the scrcpy window. +They may also be sent to a [V4L2 sink](v4l2.md). + +Audio "frames" (an array of decoded samples) are sent to the audio player. + + +### Controller + +The _controller_ is responsible to send _control messages_ to the device. It +runs in a separate thread, to avoid I/O on the main thread. + +On SDL event, received on the main thread, the _input manager_ creates +appropriate _control messages_. It is responsible to convert SDL events to +Android events. It then pushes the _control messages_ to a queue hold by the +controller. On its own thread, the controller takes messages from the queue, +that it serializes and sends to the client. + + +## Protocol + +The protocol between the client and the server must be considered _internal_: it +may (and will) change at any time for any reason. Everything may change (the +number of sockets, the order in which the sockets must be opened, the data +format on the wire…) from version to version. A client must always be run with a +matching server version. + +This section documents the current protocol in scrcpy v2.1. -To minimize startup time, SDL initialization is performed while listening for -the connection from the server (see commit [90a46b4]). +### Connection -[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172 -[issue #5]: https://github.com/Genymobile/scrcpy/issues/5 -[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e +Firstly, the client sets up an adb tunnel: +```bash +# By default, a reverse redirection: the computer listens, the device connects +adb reverse localabstract:scrcpy_ tcp:27183 + +# As a fallback (or if --force-adb forward is set), a forward redirection: +# the device listens, the computer connects +adb forward tcp:27183 localabstract:scrcpy_ +``` + +(`` is a 31-bit random number, so that it does not fail when several +scrcpy instances start "at the same time" for the same device.) -### Threading +Then, up to 3 sockets are opened, in that order: + - a _video_ socket + - an _audio_ socket + - a _control_ socket -The client uses 4 threads: +Each one may be disabled (respectively by `--no-video`, `--no-audio` and +`--no-control`, directly or indirectly). For example, if `--no-audio` is set, +then the _video_ socket is opened first, then the _control_ socket. - - the **main** thread, executing the SDL event loop, - - the **stream** thread, receiving the video and used for decoding and - recording, - - the **controller** thread, sending _control messages_ to the server, - - the **receiver** thread (managed by the controller), receiving _device - messages_ from the server. +On the _first_ socket opened (whichever it is), if the tunnel is _forward_, then +a [dummy byte] is sent from the device to the client. This allows to detect a +connection error (the client connection does not fail as long as there is an adb +forward redirection, even if nothing is listening on the device side). -In addition, another thread can be started if necessary to handle APK -installation or file push requests (via drag&drop on the main window) or to -print the framerate regularly in the console. +Still on this _first_ socket, the device sends some [metadata][device meta] to +the client (currently only the device name, used as the window title, but there +might be other fields in the future). +[dummy byte]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java#L93 +[device meta]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java#L151 +You can read the [client][client-connection] and [server][server-connection] +code for more details. -### Stream +[client-connection]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/server.c#L465-L466 +[server-connection]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java#L63 -The video [stream] is received from the socket (connected to the server on the -device) in a separate thread. +Then each socket is used for its intended purpose. -If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_ -to decode the H.264 stream from the socket, and notifies the main thread when a -new frame is available. +### Video and audio -There are two [frames][video_buffer] simultaneously in memory: - - the **decoding** frame, written by the decoder from the decoder thread, - - the **rendering** frame, rendered in a texture from the main thread. +On the _video_ and _audio_ sockets, the device first sends some [codec +metadata]: + - On the _video_ socket, 12 bytes: + - the codec id (`u32`) (H264, H265 or AV1) + - the initial video width (`u32`) + - the initial video height (`u32`) + - On the _audio_ socket, 4 bytes: + - the codec id (`u32`) (OPUS, AAC or RAW) -When a new decoded frame is available, the decoder _swaps_ the decoding and -rendering frame (with proper synchronization). Thus, it immediately starts -to decode a new frame while the main thread renders the last one. +[codec metadata]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Streamer.java#L33-L51 -If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw -H.264 packet to the output video file. +Then each packet produced by `MediaCodec` is sent, prefixed by a 12-byte [frame +header]: + - config packet flag (`u1`) + - key frame flag (`u1`) + - PTS (`u62`) + - packet size (`u32`) -[stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h -[decoder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/decoder.h -[video_buffer]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/video_buffer.h -[recorder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/recorder.h +Here is a schema describing the frame header: ``` - +----------+ +----------+ - ---> | decoder | ---> | screen | - +---------+ / +----------+ +----------+ - socket ---> | stream | ---- - +---------+ \ +----------+ - ---> | recorder | - +----------+ + [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ... + <-------------> <-----> <-----------------------------... + PTS packet raw packet + size + <---------------------> + frame header + +The most significant bits of the PTS are used for packet flags: + + byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0 + CK...... ........ ........ ........ ........ ........ ........ ........ + ^^<-------------------------------------------------------------------> + || PTS + | `- key frame + `-- config packet ``` -### Controller +[frame header]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Streamer.java#L83 -The [controller] is responsible to send _control messages_ to the device. It -runs in a separate thread, to avoid I/O on the main thread. -On SDL event, received on the main thread, the [input manager][inputmanager] -creates appropriate [_control messages_][controlmsg]. It is responsible to -convert SDL events to Android events (using [convert]). It pushes the _control -messages_ to a queue hold by the controller. On its own thread, the controller -takes messages from the queue, that it serializes and sends to the client. +### Controls + +Controls messages are sent via a custom binary protocol. + +The only documentation for this protocol is the set of unit tests on both sides: + - `ControlMessage` (from client to device): [serialization](https://github.com/Genymobile/scrcpy/blob/master/app/tests/test_control_msg_serialize.c) | [deserialization](https://github.com/Genymobile/scrcpy/blob/master/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java) + - `DeviceMessage` (from device to client) [serialization](https://github.com/Genymobile/scrcpy/blob/master/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java) | [deserialization](https://github.com/Genymobile/scrcpy/blob/master/app/tests/test_device_msg_deserialize.c) -[controller]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/controller.h -[controlmsg]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/control_msg.h -[inputmanager]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/input_manager.h -[convert]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/convert.h +## Standalone server -### UI and event loop +Although the server is designed to work for the scrcpy client, it can be used +with any client which uses the same protocol. -Initialization, input events and rendering are all [managed][scrcpy] in the main -thread. +For simplicity, some [server-specific options] have been added to produce raw +streams easily: + - `send_device_meta=false`: disable the device metata (in practice, the device + name) sent on the _first_ socket + - `send_frame_meta=false`: disable the 12-byte header for each packet + - `send_dummy_byte`: disable the dummy byte sent on forward connections + - `send_codec_meta`: disable the codec information (and initial device size for + video) + - `raw_stream`: disable all the above -Events are handled in the [event loop], which either updates the [screen] or -delegates to the [input manager][inputmanager]. +[server-specific options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L309-L329 + +Concretely, here is how to expose a raw H.264 stream on a TCP socket: + +```bash +adb push scrcpy-server-v2.1 /data/local/tmp/scrcpy-server-manual.jar +adb forward tcp:1234 localabstract:scrcpy +adb shell CLASSPATH=/data/local/tmp/scrcpy-server-manual.jar \ + app_process / com.genymobile.scrcpy.Server 2.1 \ + tunnel_forward=true audio=false control=false cleanup=false \ + raw_stream=true max_size=1920 +``` + +As soon as a client connects over TCP on port 1234, the device will start +streaming the video. For example, VLC can play the video (although you will +experience a very high latency, more details [here][vlc-0latency]): + +``` +vlc -Idummy --demux=h264 --network-caching=0 tcp://localhost:1234 +``` -[scrcpy]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c -[event loop]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c#L201 -[screen]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/screen.h +[vlc-0latency]: https://code.videolan.org/rom1v/vlc/-/merge_requests/20 ## Hack From d046678f85c0730570e807a2d25267280beab086 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 00:10:37 +0200 Subject: [PATCH 0926/1133] Upgrade platform-tools (34.0.3) for Windows Include the latest version of adb in Windows releases. --- app/prebuilt-deps/prepare-adb.sh | 6 +++--- release.mk | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh index cc139095..f22873c0 100755 --- a/app/prebuilt-deps/prepare-adb.sh +++ b/app/prebuilt-deps/prepare-adb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=platform-tools-34.0.1 +DEP_DIR=platform-tools-34.0.3 -FILENAME=platform-tools_r34.0.1-windows.zip -SHA256SUM=5dd9c2be744c224fa3a7cbe30ba02d2cb378c763bd0f797a7e47e9f3156a5daa +FILENAME=platform-tools_r34.0.3-windows.zip +SHA256SUM=fce992e93eb786fc9f47df93d83a7b912c46742d45c39d712c02e06d05b72e2b if [[ -d "$DEP_DIR" ]] then diff --git a/release.mk b/release.mk index a467352c..4fe99c89 100644 --- a/release.mk +++ b/release.mk @@ -98,9 +98,9 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -116,9 +116,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From c0f3c080b63eb70c0941c300e461fc2ea1246cec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 00:49:11 +0200 Subject: [PATCH 0927/1133] Register DisplayFoldListener only for Android 10+ This listener does not exist on Android < 10, and it makes scrcpy fail. --- .../java/com/genymobile/scrcpy/Device.java | 34 ++++++++++--------- .../scrcpy/wrappers/WindowManager.java | 2 ++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index a3b6a270..f817a3ce 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -99,25 +99,27 @@ public final class Device { } }, displayId); - ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { - @Override - public void onDisplayFoldChanged(int displayId, boolean folded) { - synchronized (Device.this) { - DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); - if (displayInfo == null) { - Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); - return; - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { + @Override + public void onDisplayFoldChanged(int displayId, boolean folded) { + synchronized (Device.this) { + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + if (displayInfo == null) { + Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); + return; + } - screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), - options.getMaxSize(), options.getLockVideoOrientation()); - // notify - if (foldListener != null) { - foldListener.onFoldChanged(displayId, folded); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), + options.getMaxSize(), options.getLockVideoOrientation()); + // notify + if (foldListener != null) { + foldListener.onFoldChanged(displayId, folded); + } } } - } - }); + }); + } if (options.getControl() && options.getClipboardAutosync()) { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index d9fd9825..dde26e82 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; +import android.annotation.TargetApi; import android.os.IInterface; import android.view.IRotationWatcher; import android.view.IDisplayFoldListener; @@ -110,6 +111,7 @@ public final class WindowManager { } } + @TargetApi(29) public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { try { Class cls = manager.getClass(); From 0ffcfa0f5c444e2ebc919bfcd44a090c8511f02a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 00:51:14 +0200 Subject: [PATCH 0928/1133] Accept failure in rotation or fold registration Do not make scrcpy fail if rotation or display fold listeners could not be registered. --- .../java/com/genymobile/scrcpy/wrappers/WindowManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index dde26e82..ce748855 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -107,7 +107,7 @@ public final class WindowManager { cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); } } catch (Exception e) { - throw new AssertionError(e); + Ln.e("Could not register rotation watcher", e); } } @@ -117,7 +117,7 @@ public final class WindowManager { Class cls = manager.getClass(); cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener); } catch (Exception e) { - throw new AssertionError(e); + Ln.e("Could not register display fold listener", e); } } } From ea59d525bd6910583fdbf901182ff0e102223c62 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 01:07:09 +0200 Subject: [PATCH 0929/1133] Fix code style The code should fit in 80 columns. --- app/src/server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 360e7e7c..4d787ea9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -533,8 +533,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { if (audio_socket == SC_SOCKET_NONE) { goto fail; } - bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, - tunnel_port); + bool ok = net_connect_intr(&server->intr, audio_socket, + tunnel_host, tunnel_port); if (!ok) { goto fail; } From b9315620e2dfae363ba4ddb88d14badc88bd5dee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 01:13:53 +0200 Subject: [PATCH 0930/1133] Fix adb forward initialization In forward mode, the dummy byte must be written immediately after the first accept(), otherwise the client will wait indefinitely, causing a deadlock (or a timeout). Regression introduced by 8c650e53cd37a53d5c3aa746c30a71c6b742a4e2. --- .../genymobile/scrcpy/DesktopConnection.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 20ab1f9c..c3408fff 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -64,8 +64,6 @@ public final class DesktopConnection implements Closeable { throws IOException { String socketName = getSocketName(scid); - LocalSocket firstSocket = null; - LocalSocket videoSocket = null; LocalSocket audioSocket = null; LocalSocket controlSocket = null; @@ -74,24 +72,28 @@ public final class DesktopConnection implements Closeable { try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { if (video) { videoSocket = localServerSocket.accept(); - firstSocket = videoSocket; + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + sendDummyByte = false; + } } if (audio) { audioSocket = localServerSocket.accept(); - if (firstSocket == null) { - firstSocket = audioSocket; + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + audioSocket.getOutputStream().write(0); + sendDummyByte = false; } } if (control) { controlSocket = localServerSocket.accept(); - if (firstSocket == null) { - firstSocket = controlSocket; + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + controlSocket.getOutputStream().write(0); + sendDummyByte = false; } } - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - firstSocket.getOutputStream().write(0); - } } } else { if (video) { From 2dab1f7024dd7edbd3b630a1a435c14b98f368c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 00:16:46 +0200 Subject: [PATCH 0931/1133] Bump version to 2.1 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index e9eb1903..12551439 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.0" + VALUE "ProductVersion", "2.1" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index ac16c23b..983ea4ad 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.0', + version: '2.1', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index ce234d10..02e8e381 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 33 - versionCode 20000 - versionName "2.0" + versionCode 20100 + versionName "2.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index f13fd0dc..86b95895 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.0 +SCRCPY_VERSION_NAME=2.1 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} From 5764f47fee0f54f0b7e9270576588a67e7b7955f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Jun 2023 00:26:36 +0200 Subject: [PATCH 0932/1133] Update links to v2.1 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9002031d..62aa7eac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v2.0) +# scrcpy (v2.1) scrcpy diff --git a/doc/build.md b/doc/build.md index 86d9436a..2e8137dd 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.0`][direct-scrcpy-server] - SHA-256: `9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2` + - [`scrcpy-server-v2.1`][direct-scrcpy-server] + SHA-256: `5b8bf1940264b930c71a1c614c57da2247f52b2d4240bca865cc6d366dff6688` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-server-v2.1 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index 2cbd99b6..8b74c263 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.0.zip`][direct-win64] (64-bit) - SHA-256: `ae4c8d37a496b43f8974ba8f07f708e22a9570ba0cddc3dc3a36edbccd4d2a20` - - [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit) - SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c` + - [`scrcpy-win64-v2.1.zip`][direct-win64] (64-bit) + SHA-256: `57b98813322c8b5b560ada68714a2cd7b7efe64086fa61d03e389c23212c803d` + - [`scrcpy-win32-v2.1.zip`][direct-win32] (32-bit) + SHA-256: `4d261d391a60ea975440d83cdc22f8250b3c8985f2ece8c7e53d6fb26c0d74ed` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-win64-v2.1.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-win32-v2.1.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 609c9556..f21560fd 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0 -PREBUILT_SERVER_SHA256=9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-server-v2.1 +PREBUILT_SERVER_SHA256=5b8bf1940264b930c71a1c614c57da2247f52b2d4240bca865cc6d366dff6688 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 0049b3ce078d4abaad3cc94a462502951dabc243 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 23 Jun 2023 08:23:22 +0200 Subject: [PATCH 0933/1133] Remove superfluous log This line was committed by error in commit a52053421abee7c96b1071a06cb68bbeac2fd464. --- server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index ce7c2838..db15d5f3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -147,7 +147,6 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen // Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising) int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); - Ln.i("newMaxSize = " + newMaxSize); if (newMaxSize == 0) { // Must definitively fail return false; From 808bd14e301351ce63b4fc45ac54d3bf34423e94 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 27 Jun 2023 18:43:22 +0200 Subject: [PATCH 0934/1133] Ignore fold change events for other display ids Scrcpy mirrors a specific display id, it must ignore events for other display ids. Fixes #4120 --- server/src/main/java/com/genymobile/scrcpy/Device.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index f817a3ce..4ab689b0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -103,6 +103,11 @@ public final class Device { ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { @Override public void onDisplayFoldChanged(int displayId, boolean folded) { + if (Device.this.displayId != displayId) { + // Ignore events related to other display ids + return; + } + synchronized (Device.this) { DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { From 7b7076ef85684707e314b9ca0ef5066a7c9f8727 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 27 Jun 2023 21:23:42 +0200 Subject: [PATCH 0935/1133] Add direct links to donations --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 62aa7eac..ec8276d8 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,10 @@ For general questions or discussions, you can also use: I'm [@rom1v](https://github.com/rom1v), the author and maintainer of _scrcpy_. If you appreciate this application, you can [support my open source -work][donate]. +work][donate]: + - [GitHub Sponsors](https://github.com/sponsors/rom1v) + - [Liberapay](https://liberapay.com/rom1v/) + - [PayPal](https://paypal.me/rom2v) [donate]: https://blog.rom1v.com/about/#support-my-open-source-work From 85b55b3c4e3b05c155225e035c4a9544f8864268 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 27 Jun 2023 18:38:10 +0200 Subject: [PATCH 0936/1133] Fix possible division by zero On sway (a window manager), SDL_WINDOWEVENT_EXPOSED and SDL_WINDOWEVENT_SIZE_CHANGED might not be called before a mouse event is triggered. As a consequence, the "content rectangle" might not be initialized when the mouse event is processed, causing a division by zero. To avoid the problem, initialize the content rect immediately when the window is shown. Fixes #4115 --- app/src/screen.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/screen.c b/app/src/screen.c index 2724a266..5b7a8808 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -488,6 +488,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) { } SDL_ShowWindow(screen->window); + sc_screen_update_content_rect(screen); } void @@ -848,6 +849,8 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; + // screen->rect must be initialized to avoid a division by zero + assert(screen->rect.w && screen->rect.h); x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; From 625934fb1b078a5d66bc4864e8082840d57d4557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Skwar?= <64905406+benni347@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:49:46 +0000 Subject: [PATCH 0937/1133] Fix fedora package in build instructions In Fedora, the package is libusb1-devel. Fixes #4131 PR #4132 Signed-off-by: Romain Vimont --- doc/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.md b/doc/build.md index 2e8137dd..ba200cf1 100644 --- a/doc/build.md +++ b/doc/build.md @@ -77,7 +77,7 @@ pip3 install meson sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm # client build dependencies -sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make +sudo dnf install SDL2-devel ffms2-devel libusb1-devel meson gcc make # server build dependencies sudo dnf install java-devel From fe6e9acb36298a19f2e667ace724b249a04a7f30 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 4 Jul 2023 18:20:23 +0200 Subject: [PATCH 0938/1133] Log device selection at INFO level The selected device should be logged by default. --- app/src/adb/adb.c | 4 ++-- app/src/usb/scrcpy_otg.c | 4 ---- app/src/usb/usb.c | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index e8775a01..6bc6dd62 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -628,8 +628,8 @@ sc_adb_select_device(struct sc_intr *intr, return false; } - LOGD("ADB device found:"); - sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size); + LOGI("ADB device found:"); + sc_adb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size); // Move devics into out_device (do not destroy device) sc_adb_device_move(out_device, device); diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 35d8d4cc..6a7fd79b 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -105,10 +105,6 @@ scrcpy_otg(struct scrcpy_options *options) { usb_device_initialized = true; - LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial, - (unsigned) usb_device.vid, (unsigned) usb_device.pid, - usb_device.manufacturer, usb_device.product); - ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL); if (!ok) { goto end; diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 190a4108..310ed5d9 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -213,8 +213,8 @@ sc_usb_select_device(struct sc_usb *usb, const char *serial, assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 struct sc_usb_device *device = &vec.data[sel_idx]; - LOGD("USB device found:"); - sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size); + LOGI("USB device found:"); + sc_usb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size); // Move device into out_device (do not destroy device) sc_usb_device_move(out_device, device); From 01d785d9a3407dc0389abee688f1a58a5e4f8923 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 7 Jul 2023 18:21:17 +0200 Subject: [PATCH 0939/1133] Increase attempts to start AudioRecord Making the shell app foreground (specific for Android 11) may take more than 300ms on some devices, so increase the number of attempts from 3 to 5 (separated by 100ms). Fixes #4147 Refs #3796 Refs 02f4ff7534649153d6f87b05a0757431a2d0ee5f --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 7b20cce4..5575ffb6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -118,7 +118,7 @@ public final class AudioCapture { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { startWorkaroundAndroid11(); try { - tryStartRecording(3, 100); + tryStartRecording(5, 100); } finally { stopWorkaroundAndroid11(); } From 7e936fa879d9ee37608d9d0ce36f66ea06a6317d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 13 Jul 2023 21:43:52 +0200 Subject: [PATCH 0940/1133] Fix meizu deadlock Some devices (Meizu) assume that the video encoding thread has a Looper. By moving video encoding to a separate thread, commit feab87053abcceded41342d9d856763dedc09187 broke this assumption. Call Looper.prepare() from this thread to fix the problem. Fixes #4143 --- .../src/main/java/com/genymobile/scrcpy/ScreenEncoder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index db15d5f3..5a9db10d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -8,6 +8,7 @@ import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Build; import android.os.IBinder; +import android.os.Looper; import android.os.SystemClock; import android.view.Surface; @@ -285,6 +286,10 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen @Override public void start(TerminationListener listener) { thread = new Thread(() -> { + // Some devices (Meizu) deadlock if the video encoding thread has no Looper + // + Looper.prepare(); + try { streamScreen(); } catch (ConfigurationException e) { From d391fc3b695679f4770ed2e3950b135ebbda2f27 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 18:58:58 +0200 Subject: [PATCH 0941/1133] Bump version to 2.1.1 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 12551439..e3929119 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.1" + VALUE "ProductVersion", "2.1.1" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 983ea4ad..847e33a1 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.1', + version: '2.1.1', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 02e8e381..4a05d2a5 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 33 - versionCode 20100 - versionName "2.1" + versionCode 20101 + versionName "2.1.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 86b95895..543f12ab 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.1 +SCRCPY_VERSION_NAME=2.1.1 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} From 637f48f360d0dce1b0927a9df24236ad48ab2130 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 23:09:44 +0200 Subject: [PATCH 0942/1133] Update links to v2.1.1 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ec8276d8..9e49a15d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v2.1) +# scrcpy (v2.1.1) scrcpy diff --git a/doc/build.md b/doc/build.md index ba200cf1..d65cdc93 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.1`][direct-scrcpy-server] - SHA-256: `5b8bf1940264b930c71a1c614c57da2247f52b2d4240bca865cc6d366dff6688` + - [`scrcpy-server-v2.1.1`][direct-scrcpy-server] + SHA-256: `9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-server-v2.1 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index 8b74c263..7525334d 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.1.zip`][direct-win64] (64-bit) - SHA-256: `57b98813322c8b5b560ada68714a2cd7b7efe64086fa61d03e389c23212c803d` - - [`scrcpy-win32-v2.1.zip`][direct-win32] (32-bit) - SHA-256: `4d261d391a60ea975440d83cdc22f8250b3c8985f2ece8c7e53d6fb26c0d74ed` + - [`scrcpy-win64-v2.1.1.zip`][direct-win64] (64-bit) + SHA-256: `f77281e1bce2f9934617699c581f063d5b327f012eff602ee98fb2ef550c25c2` + - [`scrcpy-win32-v2.1.1.zip`][direct-win32] (32-bit) + SHA-256: `ef7ae7fbe9449f2643febdc2244fb186d1a746a3c736394150cfd14f06d3c943` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-win64-v2.1.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-win32-v2.1.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win64-v2.1.1.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win32-v2.1.1.zip and extract it. diff --git a/install_release.sh b/install_release.sh index f21560fd..24a3197b 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1/scrcpy-server-v2.1 -PREBUILT_SERVER_SHA256=5b8bf1940264b930c71a1c614c57da2247f52b2d4240bca865cc6d366dff6688 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 +PREBUILT_SERVER_SHA256=9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From c14668b17779d9fe8f3e209bbe18669c443ddf8b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 23:26:52 +0200 Subject: [PATCH 0943/1133] Move display section to video documentation --- doc/device.md | 19 ------------------- doc/video.md | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/device.md b/doc/device.md index b6ae2338..32b5ebc1 100644 --- a/doc/device.md +++ b/doc/device.md @@ -125,25 +125,6 @@ autoadb scrcpy -s '{}' [AutoAdb]: https://github.com/rom1v/autoadb -## Display - -If several displays are available on the Android device, it is possible to -select the display to mirror: - -```bash -scrcpy --display=1 -``` - -The list of display ids can be retrieved by: - -```bash -scrcpy --list-displays -``` - -A secondary display may only be controlled if the device runs at least Android -10 (otherwise it is mirrored as read-only). - - ## Actions Some command line arguments perform actions on the device itself while scrcpy is diff --git a/doc/video.md b/doc/video.md index 67372a5c..060e2778 100644 --- a/doc/video.md +++ b/doc/video.md @@ -134,6 +134,25 @@ phone, landscape for a tablet). If `--max-size` is also specified, resizing is applied after cropping. +## Display + +If several displays are available on the Android device, it is possible to +select the display to mirror: + +```bash +scrcpy --display=1 +``` + +The list of display ids can be retrieved by: + +```bash +scrcpy --list-displays +``` + +A secondary display may only be controlled if the device runs at least Android +10 (otherwise it is mirrored as read-only). + + ## Buffering By default, there is no video buffering, to get the lowest possible latency. From 328ed3650dd8abbdb138f629b4e5aa622f172cae Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 23:29:09 +0200 Subject: [PATCH 0944/1133] Extract device connection to a separate doc page Create a new "Connection" documentation page. --- README.md | 3 +- doc/connection.md | 125 +++++++++++++++++++++++++++++++++++++++++ doc/device.md | 139 ++-------------------------------------------- 3 files changed, 132 insertions(+), 135 deletions(-) create mode 100644 doc/connection.md diff --git a/README.md b/README.md index 9e49a15d..412c186b 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,11 @@ mode](doc/hid-otg.md#otg). The application provides a lot of features and configuration options. They are documented in the following pages: - - [Device](doc/device.md) + - [Connection](doc/connection.md) - [Video](doc/video.md) - [Audio](doc/audio.md) - [Control](doc/control.md) + - [Device](doc/device.md) - [Window](doc/window.md) - [Recording](doc/recording.md) - [Tunnels](doc/tunnels.md) diff --git a/doc/connection.md b/doc/connection.md new file mode 100644 index 00000000..90ced010 --- /dev/null +++ b/doc/connection.md @@ -0,0 +1,125 @@ +# Connection + +## Selection + +If exactly one device is connected (i.e. listed by `adb devices`), then it is +automatically selected. + +However, if there are multiple devices connected, you must specify the one to +use in one of 4 ways: + - by its serial: + ```bash + scrcpy --serial=0123456789abcdef + scrcpy -s 0123456789abcdef # short version + + # the serial is the ip:port if connected over TCP/IP (same behavior as adb) + scrcpy --serial=192.168.1.1:5555 + ``` + - the one connected over USB (if there is exactly one): + ```bash + scrcpy --select-usb + scrcpy -d # short version + ``` + - the one connected over TCP/IP (if there is exactly one): + ```bash + scrcpy --select-tcpip + scrcpy -e # short version + ``` + - a device already listening on TCP/IP (see [below](#tcpip-wireless)): + ```bash + scrcpy --tcpip=192.168.1.1:5555 + scrcpy --tcpip=192.168.1.1 # default port is 5555 + ``` + +The serial may also be provided via the environment variable `ANDROID_SERIAL` +(also used by `adb`): + +```bash +# in bash +export ANDROID_SERIAL=0123456789abcdef +scrcpy +``` + +```cmd +:: in cmd +set ANDROID_SERIAL=0123456789abcdef +scrcpy +``` + +```powershell +# in PowerShell +$env:ANDROID_SERIAL = '0123456789abcdef' +scrcpy +``` + + +## TCP/IP (wireless) + +_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a +device over TCP/IP. The device must be connected on the same network as the +computer. + +[connect]: https://developer.android.com/studio/command-line/adb.html#wireless + + +### Automatic + +An option `--tcpip` allows to configure the connection automatically. There are +two variants. + +If the device (accessible at 192.168.1.1 in this example) already listens on a +port (typically 5555) for incoming _adb_ connections, then run: + +```bash +scrcpy --tcpip=192.168.1.1 # default port is 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + +If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP +address), connect the device over USB, then run: + +```bash +scrcpy --tcpip # without arguments +``` + +It will automatically find the device IP address and adb port, enable TCP/IP +mode if necessary, then connect to the device before starting. + + +### Manual + +Alternatively, it is possible to enable the TCP/IP connection manually using +`adb`: + +1. Plug the device into a USB port on your computer. +2. Connect the device to the same Wi-Fi network as your computer. +3. Get your device IP address, in Settings → About phone → Status, or by + executing this command: + + ```bash + adb shell ip route | awk '{print $9}' + ``` + +4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. +5. Unplug your device. +6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` +with the device IP address you found)_. +7. Run `scrcpy` as usual. +8. Run `adb disconnect` once you're done. + +Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass +having to physically connect your device directly to your computer. + +[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line + + +## Autostart + +A small tool (by the scrcpy author) allows to run arbitrary commands whenever a +new Android device is connected: [AutoAdb]. It can be used to start scrcpy: + +```bash +autoadb scrcpy -s '{}' +``` + +[AutoAdb]: https://github.com/rom1v/autoadb diff --git a/doc/device.md b/doc/device.md index 32b5ebc1..988ad417 100644 --- a/doc/device.md +++ b/doc/device.md @@ -1,137 +1,9 @@ # Device -## Selection - -If exactly one device is connected (i.e. listed by `adb devices`), then it is -automatically selected. - -However, if there are multiple devices connected, you must specify the one to -use in one of 4 ways: - - by its serial: - ```bash - scrcpy --serial=0123456789abcdef - scrcpy -s 0123456789abcdef # short version - - # the serial is the ip:port if connected over TCP/IP (same behavior as adb) - scrcpy --serial=192.168.1.1:5555 - ``` - - the one connected over USB (if there is exactly one): - ```bash - scrcpy --select-usb - scrcpy -d # short version - ``` - - the one connected over TCP/IP (if there is exactly one): - ```bash - scrcpy --select-tcpip - scrcpy -e # short version - ``` - - a device already listening on TCP/IP (see [below](#tcpip-wireless)): - ```bash - scrcpy --tcpip=192.168.1.1:5555 - scrcpy --tcpip=192.168.1.1 # default port is 5555 - ``` - -The serial may also be provided via the environment variable `ANDROID_SERIAL` -(also used by `adb`): - -```bash -# in bash -export ANDROID_SERIAL=0123456789abcdef -scrcpy -``` - -```cmd -:: in cmd -set ANDROID_SERIAL=0123456789abcdef -scrcpy -``` - -```powershell -# in PowerShell -$env:ANDROID_SERIAL = '0123456789abcdef' -scrcpy -``` - - -## TCP/IP (wireless) - -_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a -device over TCP/IP. The device must be connected on the same network as the -computer. - -[connect]: https://developer.android.com/studio/command-line/adb.html#wireless - - -### Automatic - -An option `--tcpip` allows to configure the connection automatically. There are -two variants. - -If the device (accessible at 192.168.1.1 in this example) already listens on a -port (typically 5555) for incoming _adb_ connections, then run: - -```bash -scrcpy --tcpip=192.168.1.1 # default port is 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - -If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP -address), connect the device over USB, then run: - -```bash -scrcpy --tcpip # without arguments -``` - -It will automatically find the device IP address and adb port, enable TCP/IP -mode if necessary, then connect to the device before starting. - - -### Manual - -Alternatively, it is possible to enable the TCP/IP connection manually using -`adb`: - -1. Plug the device into a USB port on your computer. -2. Connect the device to the same Wi-Fi network as your computer. -3. Get your device IP address, in Settings → About phone → Status, or by - executing this command: - - ```bash - adb shell ip route | awk '{print $9}' - ``` - -4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. -5. Unplug your device. -6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` -with the device IP address you found)_. -7. Run `scrcpy` as usual. -8. Run `adb disconnect` once you're done. - -Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass -having to physically connect your device directly to your computer. - -[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line - - -## Autostart - -A small tool (by the scrcpy author) allows to run arbitrary commands whenever a -new Android device is connected: [AutoAdb]. It can be used to start scrcpy: - -```bash -autoadb scrcpy -s '{}' -``` - -[AutoAdb]: https://github.com/rom1v/autoadb - - -## Actions - Some command line arguments perform actions on the device itself while scrcpy is running. - -### Stay awake +## Stay awake To prevent the device from sleeping after a delay **when the device is plugged in**: @@ -147,7 +19,7 @@ If the device is not plugged in (i.e. only connected over TCP/IP), `--stay-awake` has no effect (this is the Android behavior). -### Turn screen off +## Turn screen off It is possible to turn the device screen off while mirroring on start with a command-line option: @@ -175,7 +47,7 @@ scrcpy -Sw # short version ``` -### Show touches +## Show touches For presentations, it may be useful to show physical touches (on the physical device). Android exposes this feature in _Developers options_. @@ -191,7 +63,7 @@ scrcpy -t # short version Note that it only shows _physical_ touches (by a finger on the device). -### Power off on close +## Power off on close To turn the device screen off when closing _scrcpy_: @@ -199,11 +71,10 @@ To turn the device screen off when closing _scrcpy_: scrcpy --power-off-on-close ``` -### Power on on start +## Power on on start By default, on start, the device is powered on. To prevent this behavior: ```bash scrcpy --no-power-on ``` - From ad05a018003a66b0a5f8afefb0d2f16a392d3077 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 23:33:04 +0200 Subject: [PATCH 0945/1133] Add Encoder section This will allow to reference the encoder section directly in issues. --- doc/audio.md | 12 +++++++----- doc/video.md | 13 ++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/audio.md b/doc/audio.md index 6bb17a87..357cd4ea 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -77,6 +77,13 @@ In particular, if you get the following error: then your device has no Opus encoder: try `scrcpy --audio-codec=aac`. +For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], +check `--audio-codec-options` in the manpage or in `scrcpy --help`. + +[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat + + +## Encoder Several encoders may be available on the device. They can be listed by: @@ -90,11 +97,6 @@ To select a specific encoder: scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder' ``` -For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], -check `--audio-codec-options` in the manpage or in `scrcpy --help`. - -[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat - ## Bit rate diff --git a/doc/video.md b/doc/video.md index 060e2778..57af5c9f 100644 --- a/doc/video.md +++ b/doc/video.md @@ -66,6 +66,14 @@ scrcpy --video-codec=av1 H265 may provide better quality, but H264 should provide lower latency. AV1 encoders are not common on current Android devices. +For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], +check `--video-codec-options` in the manpage or in `scrcpy --help`. + +[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat + + +## Encoder + Several encoders may be available on the device. They can be listed by: ```bash @@ -79,11 +87,6 @@ try another one: scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc' ``` -For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], -check `--video-codec-options` in the manpage or in `scrcpy --help`. - -[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat - ## Rotation From fcdf847dd39daf61b936ec7fe7d34f41705ce0ae Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 14 Jul 2023 23:37:19 +0200 Subject: [PATCH 0946/1133] Add missing syntax highlighting in audio doc --- doc/audio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/audio.md b/doc/audio.md index 357cd4ea..cb6cde95 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -93,7 +93,7 @@ scrcpy --list-encoders To select a specific encoder: -``` +```bash scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder' ``` From 1ee46970e373ea3c34c3d9b632fef34982d7a52b Mon Sep 17 00:00:00 2001 From: Aritz T <59333567+eltrevii@users.noreply.github.com> Date: Tue, 18 Jul 2023 18:09:54 +0200 Subject: [PATCH 0947/1133] Fix TCP/IP link in README Refs 328ed3650dd8abbdb138f629b4e5aa622f172cae PR #4173 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 412c186b..9f1ba6b4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ _pronounced "**scr**een **c**o**py**"_ This application mirrors Android devices (video and audio) connected via -USB or [over TCP/IP](doc/device.md#tcpip-wireless), and allows to control the +USB or [over TCP/IP](doc/connection.md#tcpip-wireless), and allows to control the device with the keyboard and the mouse of the computer. It does not require any _root_ access. It works on _Linux_, _Windows_ and _macOS_. From 110b3a16f6d02124a4567d2ab79fcb74d78f949f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 Jul 2023 14:45:33 +0200 Subject: [PATCH 0948/1133] Do not disable controls without video playback Some control messages can still be used even when video playback is disabled (i.e. there is no window), for example to turn the screen off. This reverts commit 92483fe11b6fd6bae5ef775ccaff78fefa92aad4 (semantically). Fixes #4175 --- app/src/cli.c | 6 ------ app/src/scrcpy.c | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 37a98426..09f853f5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2022,12 +2022,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->audio_playback = false; } - if (!opts->video_playback && !otg) { - // If video playback is disabled and OTG are disabled, then there is - // no way to control the device. - opts->control = false; - } - if (opts->video && !opts->video_playback && !opts->record_filename && !v4l2) { LOGI("No video playback, no recording, no V4L2 sink: video disabled"); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index fd310c46..d68a2424 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -448,9 +448,7 @@ scrcpy(struct scrcpy_options *options) { struct sc_file_pusher *fp = NULL; - // control implies video playback - assert(!options->control || options->video_playback); - if (options->control) { + if (options->video_playback && options->control) { if (!sc_file_pusher_init(&s->file_pusher, serial, options->push_target)) { goto end; From 0983f0a194db154823842a4728f3c2bb5a3b1cc3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Aug 2023 12:05:16 +0200 Subject: [PATCH 0949/1133] Report device disconnection on audio EOS If --no-video was set, then device disconnection was not reported. To avoid the problem, report device disconnection also on audio end-of-stream (EOS). If both video and audio are enabled, then a device disconnection event will be sent twice, but only the first one will be handled (since it makes scrcpy exit). Fixes #4207 --- app/src/scrcpy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d68a2424..aabb7c5a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -252,7 +252,9 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, // Contrary to the video demuxer, keep mirroring if only the audio fails // (unless --require-audio is set). - if (status == SC_DEMUXER_STATUS_ERROR + if (status == SC_DEMUXER_STATUS_EOS) { + PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); + } else if (status == SC_DEMUXER_STATUS_ERROR || (status == SC_DEMUXER_STATUS_DISABLED && options->require_audio)) { PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); From 36670dda40c405f7fbcf622a4553651284f8aa96 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Aug 2023 20:22:17 +0200 Subject: [PATCH 0950/1133] Fix warning typo A parenthesis was missing. --- app/src/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/display.c b/app/src/display.c index dabc2cb7..cf26e776 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -53,7 +53,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { display->mipmaps = true; } else { LOGW("Trilinear filtering disabled " - "(OpenGL 3.0+ or ES 2.0+ required"); + "(OpenGL 3.0+ or ES 2.0+ required)"); } } else { LOGI("Trilinear filtering disabled"); From 111d02fca46c4cff50adbbafa946eb8d451f004a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Aug 2023 18:52:03 +0200 Subject: [PATCH 0951/1133] Add missing 'final' in Java classes For consistency. --- .../java/com/genymobile/scrcpy/wrappers/ActivityManager.java | 2 +- .../java/com/genymobile/scrcpy/wrappers/ClipboardManager.java | 2 +- .../java/com/genymobile/scrcpy/wrappers/ContentProvider.java | 2 +- .../java/com/genymobile/scrcpy/wrappers/StatusBarManager.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index aaf83d66..75115618 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -17,7 +17,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") -public class ActivityManager { +public final class ActivityManager { private final IInterface manager; private Method getContentProviderExternalMethod; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 7b750975..eae66858 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -11,7 +11,7 @@ import android.os.IInterface; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -public class ClipboardManager { +public final class ClipboardManager { private final IInterface manager; private Method getPrimaryClipMethod; private Method setPrimaryClipMethod; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 4917f5eb..8171988e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -14,7 +14,7 @@ import java.io.Closeable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -public class ContentProvider implements Closeable { +public final class ContentProvider implements Closeable { public static final String TABLE_SYSTEM = "system"; public static final String TABLE_SECURE = "secure"; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java index 7a19e6e5..9126d5ed 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -7,7 +7,7 @@ import android.os.IInterface; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -public class StatusBarManager { +public final class StatusBarManager { private final IInterface manager; private Method expandNotificationsPanelMethod; From a7c3c9a54c928124e00fe127833b655bf3ea40c6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 22 Aug 2023 20:08:50 +0200 Subject: [PATCH 0952/1133] Make fillBaseContext() method private This is consistent with fillAppInfo() and fillAppContext(), which are also private. --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index b8294a87..74c0202a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -154,7 +154,7 @@ public final class Workarounds { } } - public static void fillBaseContext() { + private static void fillBaseContext() { try { fillActivityThread(); From 1650b7c0581df65e9ab5df92f2d81cca4c150ac7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Jun 2023 21:36:52 +0200 Subject: [PATCH 0953/1133] Add --pause-on-exit Add an option to make scrcpy pause on exit. Three behaviors are possible: - always pause on exit: --pause-on-exit --pause-on-exit=true - never pause on exit: (no option) --pause-on-exit=false - pause when scrcpy returns with an error (a non-zero exit code): --pause-on-exit=if-error This is useful to prevent the terminal window from automatically closing, so that error messages can be read. Refs #3817 Refs #3822 PR #4130 --- app/data/bash-completion/scrcpy | 6 +++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 10 +++++ app/src/cli.c | 80 +++++++++++++++++++++++++++++++++ app/src/cli.h | 7 +++ app/src/main.c | 28 +++++++++--- 6 files changed, 125 insertions(+), 7 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 003f9d73..a44ff6e5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -44,6 +44,8 @@ _scrcpy() { --no-video-playback --otg -p --port= + --pause-on-exit + --pause-on-exit= --power-off-on-close --prefer-text --print-fps @@ -97,6 +99,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return ;; + --pause-on-exit) + COMPREPLY=($(compgen -W 'true false if-error' -- "$cur")) + return + ;; -r|--record) COMPREPLY=($(compgen -f -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 81142851..ac40e903 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -50,6 +50,7 @@ arguments=( '--no-video-playback[Disable video playback]' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' + '--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)' '--power-off-on-close[Turn the device screen off when closing scrcpy]' '--prefer-text[Inject alpha characters and space as text events instead of key events]' '--print-fps[Start FPS counter, to print frame logs to the console]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index c1378d8b..7ae56a27 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -267,6 +267,16 @@ Set the TCP port (range) used by the client to listen. Default is 27183:27199. +.TP +\fB\-\-pause\-on\-exit\fR[=\fImode\fR] +Configure pause on exit. Possible values are "true" (always pause on exit), "false" (never pause on exit) and "if-error" (pause only if an error occured). + +This is useful to prevent the terminal window from automatically closing, so that error messages can be read. + +Default is "false". + +Passing the option without argument is equivalent to passing "true". + .TP .B \-\-power\-off\-on\-close Turn the device screen off when closing scrcpy. diff --git a/app/src/cli.c b/app/src/cli.c index 09f853f5..86b720fc 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -79,6 +79,7 @@ enum { OPT_AUDIO_SOURCE, OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, + OPT_PAUSE_ON_EXIT, }; struct sc_option { @@ -463,6 +464,20 @@ static const struct sc_option options[] = { "Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".", }, + { + .longopt_id = OPT_PAUSE_ON_EXIT, + .longopt = "pause-on-exit", + .argdesc = "mode", + .optional_arg = true, + .text = "Configure pause on exit. Possible values are \"true\" (always " + "pause on exit), \"false\" (never pause on exit) and " + "\"if-error\" (pause only if an error occured).\n" + "This is useful to prevent the terminal window from " + "automatically closing, so that error messages can be read.\n" + "Default is \"false\".\n" + "Passing the option without argument is equivalent to passing " + "\"true\".", + }, { .longopt_id = OPT_POWER_OFF_ON_CLOSE, .longopt = "power-off-on-close", @@ -1637,6 +1652,29 @@ parse_time_limit(const char *s, sc_tick *tick) { return true; } +static bool +parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) { + if (!s || !strcmp(s, "true")) { + *pause_on_exit = SC_PAUSE_ON_EXIT_TRUE; + return true; + } + + if (!strcmp(s, "false")) { + *pause_on_exit = SC_PAUSE_ON_EXIT_FALSE; + return true; + } + + if (!strcmp(s, "if-error")) { + *pause_on_exit = SC_PAUSE_ON_EXIT_IF_ERROR; + return true; + } + + LOGE("Unsupported pause on exit mode: %s " + "(expected true, false or if-error)", optarg); + return false; + +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -1977,6 +2015,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_PAUSE_ON_EXIT: + if (!parse_pause_on_exit(optarg, &args->pause_on_exit)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; @@ -2190,6 +2233,37 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return true; } +static enum sc_pause_on_exit +sc_get_pause_on_exit(int argc, char *argv[]) { + // Read arguments backwards so that the last --pause-on-exit is considered + // (same behavior as getopt()) + for (int i = argc - 1; i >= 1; --i) { + const char *arg = argv[i]; + // Starts with "--pause-on-exit" + if (!strncmp("--pause-on-exit", arg, 15)) { + if (arg[15] == '\0') { + // No argument + return SC_PAUSE_ON_EXIT_TRUE; + } + if (arg[15] != '=') { + // Invalid parameter, ignore + return SC_PAUSE_ON_EXIT_FALSE; + } + const char *value = &arg[16]; + if (!strcmp(value, "true")) { + return SC_PAUSE_ON_EXIT_TRUE; + } + if (!strcmp(value, "if-error")) { + return SC_PAUSE_ON_EXIT_IF_ERROR; + } + // Set to false, inclusing when the value is invalid + return SC_PAUSE_ON_EXIT_FALSE; + } + } + + return false; +} + bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { struct sc_getopt_adapter adapter; @@ -2203,5 +2277,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { sc_getopt_adapter_destroy(&adapter); + if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_FALSE) { + // Check if "--pause-on-exit" is present in the arguments list, because + // it must be taken into account even if command line parsing failed + args->pause_on_exit = sc_get_pause_on_exit(argc, argv); + } + return ret; } diff --git a/app/src/cli.h b/app/src/cli.h index b9361a9c..23d34fcd 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -7,10 +7,17 @@ #include "options.h" +enum sc_pause_on_exit { + SC_PAUSE_ON_EXIT_TRUE, + SC_PAUSE_ON_EXIT_FALSE, + SC_PAUSE_ON_EXIT_IF_ERROR, +}; + struct scrcpy_cli_args { struct scrcpy_options opts; bool help; bool version; + enum sc_pause_on_exit pause_on_exit; }; void diff --git a/app/src/main.c b/app/src/main.c index cc3a85a7..d582c5ae 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -39,26 +39,32 @@ main_scrcpy(int argc, char *argv[]) { .opts = scrcpy_options_default, .help = false, .version = false, + .pause_on_exit = SC_PAUSE_ON_EXIT_FALSE, }; #ifndef NDEBUG args.opts.log_level = SC_LOG_LEVEL_DEBUG; #endif + enum scrcpy_exit_code ret; + if (!scrcpy_parse_args(&args, argc, argv)) { - return SCRCPY_EXIT_FAILURE; + ret = SCRCPY_EXIT_FAILURE; + goto end; } sc_set_log_level(args.opts.log_level); if (args.help) { scrcpy_print_usage(argv[0]); - return SCRCPY_EXIT_SUCCESS; + ret = SCRCPY_EXIT_SUCCESS; + goto end; } if (args.version) { scrcpy_print_version(); - return SCRCPY_EXIT_SUCCESS; + ret = SCRCPY_EXIT_SUCCESS; + goto end; } #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL @@ -72,18 +78,26 @@ main_scrcpy(int argc, char *argv[]) { #endif if (!net_init()) { - return SCRCPY_EXIT_FAILURE; + ret = SCRCPY_EXIT_FAILURE; + goto end; } sc_log_configure(); #ifdef HAVE_USB - enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts) - : scrcpy(&args.opts); + ret = args.opts.otg ? scrcpy_otg(&args.opts) : scrcpy(&args.opts); #else - enum scrcpy_exit_code ret = scrcpy(&args.opts); + ret = scrcpy(&args.opts); #endif +end: + if (args.pause_on_exit == SC_PAUSE_ON_EXIT_TRUE || + (args.pause_on_exit == SC_PAUSE_ON_EXIT_IF_ERROR && + ret != SCRCPY_EXIT_SUCCESS)) { + printf("Press Enter to continue...\n"); + getchar(); + } + return ret; } From 1c864a88ebf113553c00928e7a7e5de94889a6e2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Jun 2023 21:40:12 +0200 Subject: [PATCH 0954/1133] Use --pause-on-exit from launchers The terminal opened by scrcpy-console (.bat or .desktop) must not close if scrcpy terminates with an error, so that error messages can be read. Refs #3817 Refs #3822 PR #4130 --- app/data/scrcpy-console.bat | 4 +--- app/data/scrcpy-console.desktop | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/data/scrcpy-console.bat b/app/data/scrcpy-console.bat index b90be29a..0ea7619f 100644 --- a/app/data/scrcpy-console.bat +++ b/app/data/scrcpy-console.bat @@ -1,4 +1,2 @@ @echo off -scrcpy.exe %* -:: if the exit code is >= 1, then pause -if errorlevel 1 pause +scrcpy.exe --pause-on-exit=if-error %* diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index 0e2f9ab0..6ca1e36a 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press Enter to quit...'" +Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy --pause-on-exit=if-error" Icon=scrcpy Terminal=true Type=Application From 90ca46ee415514513da9dff8c558d726c8e34318 Mon Sep 17 00:00:00 2001 From: AmirSina Mashayekh Date: Fri, 20 Oct 2023 13:34:19 +0330 Subject: [PATCH 0955/1133] Add scrcpy-server to .gitignore The script install_release.sh downloads a file named scrcpy-server to the repo root directory. Add it to .gitignore so that it is ignored. PR #4364 Signed-off-by: Romain Vimont --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2829d835..26d977ac 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/ .gradle/ /x/ local.properties +/scrcpy-server From 7adf98e9d491fc0cf218c661c3580827e8c89eb9 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 07:41:46 +0530 Subject: [PATCH 0956/1133] Use `void` for empty function parameter list PR #4371 Signed-off-by: Romain Vimont --- app/src/util/log.c | 2 +- app/src/util/log.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/util/log.c b/app/src/util/log.c index 0975e54a..8a347c84 100644 --- a/app/src/util/log.c +++ b/app/src/util/log.c @@ -147,7 +147,7 @@ sc_sdl_log_print(void *userdata, int category, SDL_LogPriority priority, } void -sc_log_configure() { +sc_log_configure(void) { SDL_LogSetOutputFunction(sc_sdl_log_print, NULL); // Redirect FFmpeg logs to SDL logs av_log_set_callback(sc_av_log_callback); diff --git a/app/src/util/log.h b/app/src/util/log.h index 8e1b73a2..0d79c9a4 100644 --- a/app/src/util/log.h +++ b/app/src/util/log.h @@ -36,6 +36,6 @@ sc_log_windows_error(const char *prefix, int error); #endif void -sc_log_configure(); +sc_log_configure(void); #endif From 90ba885547e74568a69b7f9cd8e917fc5ce8d0c1 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 07:53:38 +0530 Subject: [PATCH 0957/1133] Remove redundant `;` PR #4371 Signed-off-by: Romain Vimont --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 86b720fc..d334107a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1085,7 +1085,7 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { while (shortcut->shortcuts[i]) { printf(" %s\n", shortcut->shortcuts[i]); ++i; - }; + } char *text = sc_str_wrap_lines(shortcut->text, cols, 8); if (!text) { From 9ade389069a6898196dd8648461f30ea1e27aee1 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 09:08:06 +0530 Subject: [PATCH 0958/1133] Make sc_usb_devices_destroy() static It is only called from the implementation file. PR #4371 Signed-off-by: Romain Vimont --- app/src/usb/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/usb.c b/app/src/usb/usb.c index 310ed5d9..4f750581 100644 --- a/app/src/usb/usb.c +++ b/app/src/usb/usb.c @@ -93,7 +93,7 @@ sc_usb_device_move(struct sc_usb_device *dst, struct sc_usb_device *src) { src->product = NULL; } -void +static void sc_usb_devices_destroy(struct sc_vec_usb_devices *usb_devices) { for (size_t i = 0; i < usb_devices->size; ++i) { sc_usb_device_destroy(&usb_devices->data[i]); From 8e7b041f3596e119ee74fcc1a4dece2f1c1e1230 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 23 Oct 2023 21:49:42 +0200 Subject: [PATCH 0959/1133] Add missing `void`s for empty parameter list --- app/src/adb/adb.c | 2 +- app/src/icon.c | 2 +- app/src/scrcpy.c | 2 +- app/tests/test_str.c | 2 +- app/tests/test_vecdeque.c | 4 ++-- app/tests/test_vector.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 6bc6dd62..b248b8ed 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -70,7 +70,7 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) { } static void -show_adb_installation_msg() { +show_adb_installation_msg(void) { #ifndef __WINDOWS__ static const struct { const char *binary; diff --git a/app/src/icon.c b/app/src/icon.c index a8588dd8..a9aad875 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -271,7 +271,7 @@ error: } SDL_Surface * -scrcpy_icon_load() { +scrcpy_icon_load(void) { char *icon_path = get_icon_path(); if (!icon_path) { return NULL; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index aabb7c5a..968629a2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -297,7 +297,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) { // Generate a scrcpy id to differentiate multiple running scrcpy instances static uint32_t -scrcpy_generate_scid() { +scrcpy_generate_scid(void) { struct sc_rand rand; sc_rand_init(&rand); // Only use 31 bits to avoid issues with signed values on the Java-side diff --git a/app/tests/test_str.c b/app/tests/test_str.c index 4fe8a1df..f719bc98 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -358,7 +358,7 @@ static void test_index_of_column(void) { assert(sc_str_index_of_column(" a bc d", 1, " ") == 2); } -static void test_remove_trailing_cr() { +static void test_remove_trailing_cr(void) { char s[] = "abc\r"; sc_str_remove_trailing_cr(s, sizeof(s) - 1); assert(!strcmp(s, "abc")); diff --git a/app/tests/test_vecdeque.c b/app/tests/test_vecdeque.c index fa3ba963..44d33560 100644 --- a/app/tests/test_vecdeque.c +++ b/app/tests/test_vecdeque.c @@ -102,7 +102,7 @@ static void test_vecdeque_reserve(void) { sc_vecdeque_destroy(&vdq); } -static void test_vecdeque_grow() { +static void test_vecdeque_grow(void) { struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; bool ok = sc_vecdeque_reserve(&vdq, 20); @@ -142,7 +142,7 @@ static void test_vecdeque_grow() { sc_vecdeque_destroy(&vdq); } -static void test_vecdeque_push_hole() { +static void test_vecdeque_push_hole(void) { struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER; bool ok = sc_vecdeque_reserve(&vdq, 20); diff --git a/app/tests/test_vector.c b/app/tests/test_vector.c index 7ca09989..459b4e0f 100644 --- a/app/tests/test_vector.c +++ b/app/tests/test_vector.c @@ -187,7 +187,7 @@ static void test_vector_index_of(void) { sc_vector_destroy(&vec); } -static void test_vector_grow() { +static void test_vector_grow(void) { struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; bool ok; From 9fdb882509ad7c7b67f8d8ad68ce702ea7746f28 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 Oct 2023 22:53:41 +0200 Subject: [PATCH 0960/1133] Fix --pause-on-exit parsing The function incorrectly returned `false` instead of a valid (and expected) enum value. --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index d334107a..b78c84fd 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2261,7 +2261,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) { } } - return false; + return SC_PAUSE_ON_EXIT_FALSE; } bool From 0bbe8a700708ca6cf2b5112e5d4eed9a8800a8c6 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 07:56:14 +0530 Subject: [PATCH 0961/1133] Wrap macros in do-while(0) To fix the warnings of stray `;`. PR #4374 Signed-off-by: Romain Vimont --- app/src/server.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/server.c b/app/src/server.c index 4d787ea9..e49831fd 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -86,14 +86,15 @@ sc_server_params_copy(struct sc_server_params *dst, // The params reference user-allocated memory, so we must copy them to // handle them from another thread -#define COPY(FIELD) \ +#define COPY(FIELD) do { \ dst->FIELD = NULL; \ if (src->FIELD) { \ dst->FIELD = strdup(src->FIELD); \ if (!dst->FIELD) { \ goto error; \ } \ - } + } \ +} while(0) COPY(req_serial); COPY(crop); @@ -215,13 +216,13 @@ execute_server(struct sc_server *server, cmd[count++] = SCRCPY_VERSION; unsigned dyn_idx = count; // from there, the strings are allocated -#define ADD_PARAM(fmt, ...) { \ +#define ADD_PARAM(fmt, ...) do { \ char *p; \ if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \ goto end; \ } \ cmd[count++] = p; \ - } + } while(0) ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); From 8cef8bac947418435f23635711f463f9ca7e9ea6 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 09:08:35 +0530 Subject: [PATCH 0962/1133] Declare local functions as static PR #4374 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/adb/adb_parser.c | 2 +- app/src/main.c | 2 +- app/src/scrcpy.c | 2 +- app/tests/test_bytebuf.c | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index e7358403..66bb1854 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -7,7 +7,7 @@ #include "util/log.h" #include "util/str.h" -bool +static bool sc_adb_parse_device(char *line, struct sc_adb_device *device) { // One device line looks like: // "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " diff --git a/app/src/main.c b/app/src/main.c index d582c5ae..6050de11 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -23,7 +23,7 @@ #include "util/str.h" #endif -int +static int main_scrcpy(int argc, char *argv[]) { #ifdef _WIN32 // disable buffering, we want logs immediately diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 968629a2..f2893230 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -90,7 +90,7 @@ push_event(uint32_t type, const char *name) { #define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE) #ifdef _WIN32 -BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { +static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { if (ctrl_type == CTRL_C_EVENT) { PUSH_EVENT(SDL_QUIT); return TRUE; diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c index c85e79ec..8e9d7c57 100644 --- a/app/tests/test_bytebuf.c +++ b/app/tests/test_bytebuf.c @@ -5,7 +5,7 @@ #include "util/bytebuf.h" -void test_bytebuf_simple(void) { +static void test_bytebuf_simple(void) { struct sc_bytebuf buf; uint8_t data[20]; @@ -34,7 +34,7 @@ void test_bytebuf_simple(void) { sc_bytebuf_destroy(&buf); } -void test_bytebuf_boundaries(void) { +static void test_bytebuf_boundaries(void) { struct sc_bytebuf buf; uint8_t data[20]; @@ -71,7 +71,7 @@ void test_bytebuf_boundaries(void) { sc_bytebuf_destroy(&buf); } -void test_bytebuf_two_steps_write(void) { +static void test_bytebuf_two_steps_write(void) { struct sc_bytebuf buf; uint8_t data[20]; From 3c2013de10bad9e18ac81ff46e954078a1effd2d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 Oct 2023 23:03:10 +0200 Subject: [PATCH 0963/1133] Enable missing-prototypes warning Warn if a global function is defined without a previous prototype declaration. It is not enabled by default at warning_level=2. --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index 847e33a1..ed3f42bf 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,8 @@ project('scrcpy', 'c', 'b_ndebug=if-release', ]) +add_project_arguments('-Wmissing-prototypes', language: 'c') + if get_option('compile_app') subdir('app') endif From bc8913e12bf775c050a655a4d44feab5ab5a0181 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Mon, 16 Oct 2023 09:29:41 +0530 Subject: [PATCH 0964/1133] Use `char *` for pointer arithmetic PR #4374 Signed-off-by: Romain Vimont --- app/src/util/vecdeque.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/util/vecdeque.h b/app/src/util/vecdeque.h index e5372e02..ce559ee9 100644 --- a/app/src/util/vecdeque.h +++ b/app/src/util/vecdeque.h @@ -190,10 +190,10 @@ sc_vecdeque_reallocdata_(void *ptr, size_t newcap, size_t item_size, size_t right_len = MIN(size, oldcap - oldorigin); assert(right_len); - memcpy(newptr, ptr + (oldorigin * item_size), right_len * item_size); + memcpy(newptr, (char *) ptr + (oldorigin * item_size), right_len * item_size); if (size > right_len) { - memcpy(newptr + (right_len * item_size), ptr, + memcpy((char *) newptr + (right_len * item_size), ptr, (size - right_len) * item_size); } From 68b55ef2fed371f1be55f8373c14a4328cd026bf Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Tue, 24 Oct 2023 06:26:40 +0530 Subject: [PATCH 0965/1133] Replace sprintf() with safer snprintf() PR #4373 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/src/adb/adb.c | 39 ++++++++++++++++++++++++++++++++------- app/tests/test_str.c | 12 ++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index b248b8ed..54375451 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -218,8 +218,16 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, const char *device_socket_name, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME - sprintf(local, "tcp:%" PRIu16, local_port); - snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + + int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port); + assert(r >= 0 && (size_t) r < sizeof(local)); + + r = snprintf(remote, sizeof(remote), "localabstract:%s", + device_socket_name); + if (r < 0 || (size_t) r >= sizeof(remote)) { + LOGE("Could not write socket name"); + return false; + } assert(serial); const char *const argv[] = @@ -233,7 +241,9 @@ bool sc_adb_forward_remove(struct sc_intr *intr, const char *serial, uint16_t local_port, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT - sprintf(local, "tcp:%" PRIu16, local_port); + int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port); + assert(r >= 0 && (size_t) r < sizeof(local)); + (void) r; assert(serial); const char *const argv[] = @@ -249,8 +259,16 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial, unsigned flags) { char local[4 + 5 + 1]; // tcp:PORT char remote[108 + 14 + 1]; // localabstract:NAME - sprintf(local, "tcp:%" PRIu16, local_port); - snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port); + assert(r >= 0 && (size_t) r < sizeof(local)); + + r = snprintf(remote, sizeof(remote), "localabstract:%s", + device_socket_name); + if (r < 0 || (size_t) r >= sizeof(remote)) { + LOGE("Could not write socket name"); + return false; + } + assert(serial); const char *const argv[] = SC_ADB_COMMAND("-s", serial, "reverse", remote, local); @@ -263,7 +281,12 @@ bool sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, const char *device_socket_name, unsigned flags) { char remote[108 + 14 + 1]; // localabstract:NAME - snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); + int r = snprintf(remote, sizeof(remote), "localabstract:%s", + device_socket_name); + if (r < 0 || (size_t) r >= sizeof(remote)) { + LOGE("Device socket name too long"); + return false; + } assert(serial); const char *const argv[] = @@ -333,7 +356,9 @@ bool sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, unsigned flags) { char port_string[5 + 1]; - sprintf(port_string, "%" PRIu16, port); + int r = snprintf(port_string, sizeof(port_string), "%" PRIu16, port); + assert(r >= 0 && (size_t) r < sizeof(port_string)); + (void) r; assert(serial); const char *const argv[] = diff --git a/app/tests/test_str.c b/app/tests/test_str.c index f719bc98..5d365ef5 100644 --- a/app/tests/test_str.c +++ b/app/tests/test_str.c @@ -269,21 +269,25 @@ static void test_parse_integer_with_suffix(void) { char buf[32]; - sprintf(buf, "%ldk", LONG_MAX / 2000); + int r = snprintf(buf, sizeof(buf), "%ldk", LONG_MAX / 2000); + assert(r >= 0 && (size_t) r < sizeof(buf)); ok = sc_str_parse_integer_with_suffix(buf, &value); assert(ok); assert(value == LONG_MAX / 2000 * 1000); - sprintf(buf, "%ldm", LONG_MAX / 2000); + r = snprintf(buf, sizeof(buf), "%ldm", LONG_MAX / 2000); + assert(r >= 0 && (size_t) r < sizeof(buf)); ok = sc_str_parse_integer_with_suffix(buf, &value); assert(!ok); - sprintf(buf, "%ldk", LONG_MIN / 2000); + r = snprintf(buf, sizeof(buf), "%ldk", LONG_MIN / 2000); + assert(r >= 0 && (size_t) r < sizeof(buf)); ok = sc_str_parse_integer_with_suffix(buf, &value); assert(ok); assert(value == LONG_MIN / 2000 * 1000); - sprintf(buf, "%ldm", LONG_MIN / 2000); + r = snprintf(buf, sizeof(buf), "%ldm", LONG_MIN / 2000); + assert(r >= 0 && (size_t) r < sizeof(buf)); ok = sc_str_parse_integer_with_suffix(buf, &value); assert(!ok); } From 76a99a7fcdb0a03c2e6185486090ad9123a4ce81 Mon Sep 17 00:00:00 2001 From: Avinash Sonawane Date: Tue, 24 Oct 2023 12:24:43 +0530 Subject: [PATCH 0966/1133] Replace raw number by its name PR #4373 Signed-off-by: Romain Vimont --- app/src/usb/hid_keyboard.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index a12fbf3b..e717006a 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -27,7 +27,8 @@ // keyboard support, though OS could support more keys via modifying the report // desc. 6 should be enough for scrcpy. #define HID_KEYBOARD_MAX_KEYS 6 -#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS) +#define HID_KEYBOARD_EVENT_SIZE \ + (HID_KEYBOARD_INDEX_KEYS + HID_KEYBOARD_MAX_KEYS) #define HID_RESERVED 0x00 #define HID_ERROR_ROLL_OVER 0x01 From b7ad652a755fc3b4cfd42de47c71b1d67d8705e9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Oct 2023 22:42:46 +0200 Subject: [PATCH 0967/1133] Move empty string test for crop option parsing For consistency with other options. --- server/src/main/java/com/genymobile/scrcpy/Options.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index aab6fce8..49de0567 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -256,7 +256,9 @@ public class Options { options.tunnelForward = Boolean.parseBoolean(value); break; case "crop": - options.crop = parseCrop(value); + if (!value.isEmpty()) { + options.crop = parseCrop(value); + } break; case "control": options.control = Boolean.parseBoolean(value); @@ -337,9 +339,6 @@ public class Options { } private static Rect parseCrop(String crop) { - if (crop.isEmpty()) { - return null; - } // input format: "width:height:x:y" String[] tokens = crop.split(":"); if (tokens.length != 4) { From 7a2b756f1ed2c0ba582f51493043fe148f897e1b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Oct 2023 12:35:04 +0100 Subject: [PATCH 0968/1133] Fix incorrect comment about AV1 constant MediaFormat.MIMETYPE_VIDEO_AV1 has been added in API 29, not 21. --- server/src/main/java/com/genymobile/scrcpy/VideoCodec.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java index 43531f1e..fa787a99 100644 --- a/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/VideoCodec.java @@ -6,7 +6,7 @@ import android.media.MediaFormat; public enum VideoCodec implements Codec { H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC), H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC), - @SuppressLint("InlinedApi") // introduced in API 21 + @SuppressLint("InlinedApi") // introduced in API 29 AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1); private final int id; // 4-byte ASCII representation of the name From 3432029a3dd9a910f7735b216b41839e3afdcafa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 23 Aug 2023 20:22:39 +0200 Subject: [PATCH 0969/1133] Make separator configurable for parsing integers The separator was hardcoded to ':'. This will allow to reuse the function to parse sizes as WIDTHxHEIGHT. PR #4213 --- app/src/cli.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index b78c84fd..bc1aa2b5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1204,9 +1204,9 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min, } static size_t -parse_integers_arg(const char *s, size_t max_items, long *out, long min, - long max, const char *name) { - size_t count = sc_str_parse_integers(s, ':', max_items, out); +parse_integers_arg(const char *s, const char sep, size_t max_items, long *out, + long min, long max, const char *name) { + size_t count = sc_str_parse_integers(s, sep, max_items, out); if (!count) { LOGE("Could not parse %s: %s", name, s); return 0; @@ -1362,7 +1362,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) { static bool parse_port_range(const char *s, struct sc_port_range *port_range) { long values[2]; - size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port"); + size_t count = parse_integers_arg(s, ':', 2, values, 0, 0xFFFF, "port"); if (!count) { return false; } From 23e116064dc97f2af843e764f13eebd54fab486d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Oct 2023 22:57:00 +0200 Subject: [PATCH 0970/1133] Rename --display to --display-id The option is named "display id" everywhere. This will be consistent with --camera-id (there will be many camera options, so an option --camera would be confusing). PR #4213 --- app/data/bash-completion/scrcpy | 4 ++-- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 12 +++++++++++- doc/video.md | 2 +- .../main/java/com/genymobile/scrcpy/LogUtils.java | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a44ff6e5..fe118289 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -13,7 +13,7 @@ _scrcpy() { --crop= -d --select-usb --disable-screensaver - --display= + --display-id= --display-buffer= -e --select-tcpip -f --fullscreen @@ -140,7 +140,7 @@ _scrcpy() { |--audio-encoder \ |--audio-output-buffer \ |--crop \ - |--display \ + |--display-id \ |--display-buffer \ |--max-fps \ |-m|--max-size \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index ac40e903..b688c0aa 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -20,7 +20,7 @@ arguments=( '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' '--disable-screensaver[Disable screensaver while scrcpy is running]' - '--display=[Specify the display id to mirror]' + '--display-id=[Specify the display id to mirror]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7ae56a27..80613aed 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -94,7 +94,7 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). Disable screensaver while scrcpy is running. .TP -.BI "\-\-display " id +.BI "\-\-display\-id " id Specify the device display id to mirror. The available display ids can be listed by \-\-list\-displays. diff --git a/app/src/cli.c b/app/src/cli.c index bc1aa2b5..2ae0ced2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -32,6 +32,7 @@ enum { OPT_WINDOW_BORDERLESS, OPT_MAX_FPS, OPT_LOCK_VIDEO_ORIENTATION, + OPT_DISPLAY, OPT_DISPLAY_ID, OPT_ROTATION, OPT_RENDER_DRIVER, @@ -232,9 +233,15 @@ static const struct sc_option options[] = { .text = "Disable screensaver while scrcpy is running.", }, { - .longopt_id = OPT_DISPLAY_ID, + // deprecated + .longopt_id = OPT_DISPLAY, .longopt = "display", .argdesc = "id", + }, + { + .longopt_id = OPT_DISPLAY_ID, + .longopt = "display-id", + .argdesc = "id", .text = "Specify the device display id to mirror.\n" "The available display ids can be listed by:\n" " scrcpy --list-displays\n" @@ -1702,6 +1709,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_CROP: opts->crop = optarg; break; + case OPT_DISPLAY: + LOGW("--display is deprecated, use --display-id instead."); + // fall through case OPT_DISPLAY_ID: if (!parse_display_id(optarg, &opts->display_id)) { return false; diff --git a/doc/video.md b/doc/video.md index 57af5c9f..5ce749f9 100644 --- a/doc/video.md +++ b/doc/video.md @@ -143,7 +143,7 @@ If several displays are available on the Android device, it is possible to select the display to mirror: ```bash -scrcpy --display=1 +scrcpy --display-id=1 ``` The list of display ids can be retrieved by: diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 243a156b..ce1617e4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -47,7 +47,7 @@ public final class LogUtils { builder.append("\n (none)"); } else { for (int id : displayIds) { - builder.append("\n --display=").append(id).append(" ("); + builder.append("\n --display-id=").append(id).append(" ("); DisplayInfo displayInfo = displayManager.getDisplayInfo(id); if (displayInfo != null) { Size size = displayInfo.getSize(); From 41ccb5883ed3538b3f0a99c583beec16bfc893ce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Oct 2023 14:21:15 +0200 Subject: [PATCH 0971/1133] Force server exit at the end of main() By default, the Java process exits when all non-daemon threads are terminated. The Android SDK might start some non-daemon threads internally, preventing the scrcpy server to exit in some cases. So force the process to exit explicitly. PR #4213 --- .../main/java/com/genymobile/scrcpy/Server.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2e6e1d4a..dc85c965 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -170,7 +170,22 @@ public final class Server { return thread; } - public static void main(String... args) throws Exception { + public static void main(String... args) { + int status = 0; + try { + internalMain(args); + } catch (Throwable t) { + t.printStackTrace(); + status = 1; + } finally { + // By default, the Java process exits when all non-daemon threads are terminated. + // The Android SDK might start some non-daemon threads internally, preventing the scrcpy server to exit. + // So force the process to exit explicitly. + System.exit(status); + } + } + + private static void internalMain(String... args) throws Exception { Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Ln.e("Exception on thread " + t, e); }); From a2fb1b40f690a1e4556a2ac058d7c069513b7402 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:07:19 +0800 Subject: [PATCH 0972/1133] Extract SurfaceCapture from ScreenEncoder Extract an interface SurfaceCapture from ScreenEncoder, representing a video source which can be rendered to a Surface for encoding. Split ScreenEncoder into: - ScreenCapture, implementing SurfaceCapture to capture the device screen, - SurfaceEncoder, to encode any SurfaceCapture. This separation prepares the introduction of another SurfaceCapture implementation to capture the camera instead of the device screen. PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/ScreenCapture.java | 83 +++++++++++++++++ .../java/com/genymobile/scrcpy/Server.java | 3 +- .../com/genymobile/scrcpy/SurfaceCapture.java | 61 +++++++++++++ ...ScreenEncoder.java => SurfaceEncoder.java} | 90 +++++-------------- 4 files changed, 166 insertions(+), 71 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java rename server/src/main/java/com/genymobile/scrcpy/{ScreenEncoder.java => SurfaceEncoder.java} (74%) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java new file mode 100644 index 00000000..f9ac66b8 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -0,0 +1,83 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.SurfaceControl; + +import android.graphics.Rect; +import android.os.Build; +import android.os.IBinder; +import android.view.Surface; + +public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener { + + private final Device device; + private IBinder display; + + public ScreenCapture(Device device) { + this.device = device; + } + + @Override + public void init() { + display = createDisplay(); + device.setRotationListener(this); + device.setFoldListener(this); + } + + @Override + public void start(Surface surface) { + ScreenInfo screenInfo = device.getScreenInfo(); + Rect contentRect = screenInfo.getContentRect(); + + // does not include the locked video orientation + Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); + int videoRotation = screenInfo.getVideoRotation(); + int layerStack = device.getLayerStack(); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + } + + @Override + public void release() { + device.setRotationListener(null); + device.setFoldListener(null); + SurfaceControl.destroyDisplay(display); + } + + @Override + public Size getSize() { + return device.getScreenInfo().getVideoSize(); + } + + @Override + public void setMaxSize(int maxSize) { + device.setMaxSize(maxSize); + } + + @Override + public void onFoldChanged(int displayId, boolean folded) { + requestReset(); + } + + @Override + public void onRotationChanged(int rotation) { + requestReset(); + } + + private static IBinder createDisplay() { + // Since Android 12 (preview), secure displays could not be created with shell permissions anymore. + // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". + boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals( + Build.VERSION.CODENAME)); + return SurfaceControl.createDisplay("scrcpy", secure); + } + + private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { + SurfaceControl.openTransaction(); + try { + SurfaceControl.setDisplaySurface(display, surface); + SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); + SurfaceControl.setDisplayLayerStack(display, layerStack); + } finally { + SurfaceControl.closeTransaction(); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index dc85c965..0f73a19b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -132,7 +132,8 @@ public final class Server { if (video) { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), + ScreenCapture screenCapture = new ScreenCapture(device); + SurfaceEncoder screenEncoder = new SurfaceEncoder(screenCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); asyncProcessors.add(screenEncoder); } diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java new file mode 100644 index 00000000..45a0fd2f --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java @@ -0,0 +1,61 @@ +package com.genymobile.scrcpy; + +import android.view.Surface; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A video source which can be rendered on a Surface for encoding. + */ +public abstract class SurfaceCapture { + + private final AtomicBoolean resetCapture = new AtomicBoolean(); + + /** + * Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on + * device rotation for example). + */ + protected void requestReset() { + resetCapture.set(true); + } + + /** + * Consume the reset request (intended to be called by the encoder). + * + * @return {@code true} if a reset request was pending, {@code false} otherwise. + */ + public boolean consumeReset() { + return resetCapture.getAndSet(false); + } + + /** + * Called once before the capture starts. + */ + public abstract void init(); + + /** + * Called after the capture ends (if and only if {@link #init()} has been called). + */ + public abstract void release(); + + /** + * Start the capture to the target surface. + * + * @param surface the surface which will be encoded + */ + public abstract void start(Surface surface); + + /** + * Return the video size + * + * @return the video size + */ + public abstract Size getSize(); + + /** + * Set the maximum capture size (set by the encoder if it does not support the current size). + * + * @param maxSize Maximum size + */ + public abstract void setMaxSize(int maxSize); +} diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java similarity index 74% rename from server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java rename to server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java index 5a9db10d..4af31e89 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java @@ -1,13 +1,8 @@ package com.genymobile.scrcpy; -import com.genymobile.scrcpy.wrappers.SurfaceControl; - -import android.graphics.Rect; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; -import android.os.Build; -import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; import android.view.Surface; @@ -17,7 +12,7 @@ import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, AsyncProcessor { +public class SurfaceEncoder implements AsyncProcessor { private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms @@ -27,9 +22,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final int MAX_CONSECUTIVE_ERRORS = 3; - private final AtomicBoolean resetCapture = new AtomicBoolean(); - - private final Device device; + private final SurfaceCapture capture; private final Streamer streamer; private final String encoderName; private final List codecOptions; @@ -43,9 +36,9 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen private Thread thread; private final AtomicBoolean stopped = new AtomicBoolean(); - public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List codecOptions, String encoderName, + public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List codecOptions, String encoderName, boolean downsizeOnError) { - this.device = device; + this.capture = capture; this.streamer = streamer; this.videoBitRate = videoBitRate; this.maxFps = maxFps; @@ -54,51 +47,29 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen this.downsizeOnError = downsizeOnError; } - @Override - public void onFoldChanged(int displayId, boolean folded) { - resetCapture.set(true); - } - - @Override - public void onRotationChanged(int rotation) { - resetCapture.set(true); - } - - private boolean consumeResetCapture() { - return resetCapture.getAndSet(false); - } - private void streamScreen() throws IOException, ConfigurationException { Codec codec = streamer.getCodec(); MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); - IBinder display = createDisplay(); - device.setRotationListener(this); - device.setFoldListener(this); - streamer.writeVideoHeader(device.getScreenInfo().getVideoSize()); + capture.init(); - boolean alive; try { - do { - ScreenInfo screenInfo = device.getScreenInfo(); - Rect contentRect = screenInfo.getContentRect(); + streamer.writeVideoHeader(capture.getSize()); + + boolean alive; - // include the locked video orientation - Rect videoRect = screenInfo.getVideoSize().toRect(); - format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width()); - format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height()); + do { + Size size = capture.getSize(); + format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth()); + format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight()); Surface surface = null; try { mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = mediaCodec.createInputSurface(); - // does not include the locked video orientation - Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); - int videoRotation = screenInfo.getVideoRotation(); - int layerStack = device.getLayerStack(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + capture.start(surface); mediaCodec.start(); @@ -107,7 +78,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen mediaCodec.stop(); } catch (IllegalStateException | IllegalArgumentException e) { Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); - if (!prepareRetry(device, screenInfo)) { + if (!prepareRetry(size)) { throw e; } Ln.i("Retrying..."); @@ -121,13 +92,11 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen } while (alive); } finally { mediaCodec.release(); - device.setRotationListener(null); - device.setFoldListener(null); - SurfaceControl.destroyDisplay(display); + capture.release(); } } - private boolean prepareRetry(Device device, ScreenInfo screenInfo) { + private boolean prepareRetry(Size currentSize) { if (firstFrameSent) { ++consecutiveErrors; if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { @@ -147,7 +116,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen // Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising) - int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); + int newMaxSize = chooseMaxSizeFallback(currentSize); if (newMaxSize == 0) { // Must definitively fail return false; @@ -155,7 +124,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen // Retry with a smaller device size Ln.i("Retrying with -m" + newMaxSize + "..."); - device.setMaxSize(newMaxSize); + capture.setMaxSize(newMaxSize); return true; } @@ -176,14 +145,14 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen boolean alive = true; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - while (!consumeResetCapture() && !eof) { + while (!capture.consumeReset() && !eof) { if (stopped.get()) { alive = false; break; } int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); try { - if (consumeResetCapture()) { + if (capture.consumeReset()) { // must restart encoding with new size break; } @@ -264,25 +233,6 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen return format; } - private static IBinder createDisplay() { - // Since Android 12 (preview), secure displays could not be created with shell permissions anymore. - // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". - boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S" - .equals(Build.VERSION.CODENAME)); - return SurfaceControl.createDisplay("scrcpy", secure); - } - - private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { - SurfaceControl.openTransaction(); - try { - SurfaceControl.setDisplaySurface(display, surface); - SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); - SurfaceControl.setDisplayLayerStack(display, layerStack); - } finally { - SurfaceControl.closeTransaction(); - } - } - @Override public void start(TerminationListener listener) { thread = new Thread(() -> { From f085765e04a22508b237fc83b077c18d23dd26a1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 Oct 2023 23:54:56 +0200 Subject: [PATCH 0973/1133] Factorize --list- options handling This will limit code duplication as more list options will be added. PR #4213 --- app/src/cli.c | 4 ++-- app/src/options.c | 3 +-- app/src/options.h | 5 +++-- app/src/scrcpy.c | 5 ++--- app/src/server.c | 6 +++--- app/src/server.h | 3 +-- server/src/main/java/com/genymobile/scrcpy/Options.java | 4 ++++ server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 2ae0ced2..c7eebb0a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1993,10 +1993,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; #endif case OPT_LIST_ENCODERS: - opts->list_encoders = true; + opts->list |= SC_OPTION_LIST_ENCODERS; break; case OPT_LIST_DISPLAYS: - opts->list_displays = true; + opts->list |= SC_OPTION_LIST_DISPLAYS; break; case OPT_REQUIRE_AUDIO: opts->require_audio = true; diff --git a/app/src/options.c b/app/src/options.c index 530e003b..b633d762 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -79,7 +79,6 @@ const struct scrcpy_options scrcpy_options_default = { .video = true, .audio = true, .require_audio = false, - .list_encoders = false, - .list_displays = false, .kill_adb_on_close = false, + .list = 0, }; diff --git a/app/src/options.h b/app/src/options.h index 1f36ad7f..e1f693bc 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -179,9 +179,10 @@ struct scrcpy_options { bool video; bool audio; bool require_audio; - bool list_encoders; - bool list_displays; bool kill_adb_on_close; +#define SC_OPTION_LIST_ENCODERS 0x1 +#define SC_OPTION_LIST_DISPLAYS 0x2 + uint8_t list; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f2893230..5f0158f1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -379,9 +379,8 @@ scrcpy(struct scrcpy_options *options) { .tcpip_dst = options->tcpip_dst, .cleanup = options->cleanup, .power_on = options->power_on, - .list_encoders = options->list_encoders, - .list_displays = options->list_displays, .kill_adb_on_close = options->kill_adb_on_close, + .list = options->list, }; static const struct sc_server_callbacks cbs = { @@ -399,7 +398,7 @@ scrcpy(struct scrcpy_options *options) { server_started = true; - if (options->list_encoders || options->list_displays) { + if (options->list) { bool ok = await_for_server(NULL); ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE; goto end; diff --git a/app/src/server.c b/app/src/server.c index e49831fd..d52b164b 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -311,10 +311,10 @@ execute_server(struct sc_server *server, // By default, power_on is true ADD_PARAM("power_on=false"); } - if (params->list_encoders) { + if (params->list & SC_OPTION_LIST_ENCODERS) { ADD_PARAM("list_encoders=true"); } - if (params->list_displays) { + if (params->list & SC_OPTION_LIST_DISPLAYS) { ADD_PARAM("list_displays=true"); } @@ -896,7 +896,7 @@ run_server(void *data) { // If --list-* is passed, then the server just prints the requested data // then exits. - if (params->list_encoders || params->list_displays) { + if (params->list) { sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { goto error_connection_failed; diff --git a/app/src/server.h b/app/src/server.h index adba2652..04955974 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -56,9 +56,8 @@ struct sc_server_params { bool select_tcpip; bool cleanup; bool power_on; - bool list_encoders; - bool list_displays; bool kill_adb_on_close; + uint8_t list; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 49de0567..59820ea7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -153,6 +153,10 @@ public class Options { return powerOn; } + public boolean getList() { + return listEncoders || listDisplays; + } + public boolean getListEncoders() { return listEncoders; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 0f73a19b..1b56b859 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -195,7 +195,7 @@ public final class Server { Ln.initLogLevel(options.getLogLevel()); - if (options.getListEncoders() || options.getListDisplays()) { + if (options.getList()) { if (options.getCleanup()) { CleanUp.unlinkSelf(); } From cd63896d63c6a504441397706729648d48e45d96 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:07:19 +0800 Subject: [PATCH 0974/1133] Add --list-cameras Add an option to list the device cameras. PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++ app/src/cli.c | 9 ++++ app/src/options.h | 1 + app/src/server.c | 3 ++ .../java/com/genymobile/scrcpy/LogUtils.java | 43 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 10 ++++- .../java/com/genymobile/scrcpy/Server.java | 7 ++- .../com/genymobile/scrcpy/Workarounds.java | 8 +++- .../scrcpy/wrappers/ServiceManager.java | 18 ++++++++ 11 files changed, 101 insertions(+), 4 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index fe118289..b6f550c5 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -23,6 +23,7 @@ _scrcpy() { --kill-adb-on-close -K --hid-keyboard --legacy-paste + --list-cameras --list-displays --list-encoders --lock-video-orientation diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index b688c0aa..3ba2c4b8 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -30,6 +30,7 @@ arguments=( '--kill-adb-on-close[Kill adb when scrcpy terminates]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' + '--list-cameras[List cameras available on the device]' '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 80613aed..30d15b4d 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -155,6 +155,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. +.TP +.B \-\-list\-cameras +List cameras available on the device. + .TP .B \-\-list\-encoders List video and audio encoders available on the device. diff --git a/app/src/cli.c b/app/src/cli.c index c7eebb0a..c9382c5b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -81,6 +81,7 @@ enum { OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, OPT_PAUSE_ON_EXIT, + OPT_LIST_CAMERAS, }; struct sc_option { @@ -320,6 +321,11 @@ static const struct sc_option options[] = { "This is a workaround for some devices not behaving as " "expected when setting the device clipboard programmatically.", }, + { + .longopt_id = OPT_LIST_CAMERAS, + .longopt = "list-cameras", + .text = "List device cameras.", + }, { .longopt_id = OPT_LIST_DISPLAYS, .longopt = "list-displays", @@ -1998,6 +2004,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_LIST_DISPLAYS: opts->list |= SC_OPTION_LIST_DISPLAYS; break; + case OPT_LIST_CAMERAS: + opts->list |= SC_OPTION_LIST_CAMERAS; + break; case OPT_REQUIRE_AUDIO: opts->require_audio = true; break; diff --git a/app/src/options.h b/app/src/options.h index e1f693bc..e960968a 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -182,6 +182,7 @@ struct scrcpy_options { bool kill_adb_on_close; #define SC_OPTION_LIST_ENCODERS 0x1 #define SC_OPTION_LIST_DISPLAYS 0x2 +#define SC_OPTION_LIST_CAMERAS 0x4 uint8_t list; }; diff --git a/app/src/server.c b/app/src/server.c index d52b164b..571b813c 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -317,6 +317,9 @@ execute_server(struct sc_server *server, if (params->list & SC_OPTION_LIST_DISPLAYS) { ADD_PARAM("list_displays=true"); } + if (params->list & SC_OPTION_LIST_CAMERAS) { + ADD_PARAM("list_cameras=true"); + } #undef ADD_PARAM diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index ce1617e4..d609adcd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -3,6 +3,11 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; + import java.util.List; public final class LogUtils { @@ -60,4 +65,42 @@ public final class LogUtils { } return builder.toString(); } + + private static String getCameraFacingName(int facing) { + switch (facing) { + case CameraCharacteristics.LENS_FACING_FRONT: + return "front"; + case CameraCharacteristics.LENS_FACING_BACK: + return "back"; + case CameraCharacteristics.LENS_FACING_EXTERNAL: + return "external"; + default: + return "unknown"; + } + } + + public static String buildCameraListMessage() { + StringBuilder builder = new StringBuilder("List of cameras:"); + CameraManager cameraManager = ServiceManager.getCameraManager(); + try { + String[] cameraIds = cameraManager.getCameraIdList(); + if (cameraIds == null || cameraIds.length == 0) { + builder.append("\n (none)"); + } else { + for (String id : cameraIds) { + builder.append("\n --video-source=camera --camera-id=").append(id); + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); + + int facing = characteristics.get(CameraCharacteristics.LENS_FACING); + builder.append(" (").append(getCameraFacingName(facing)).append(", "); + + Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + builder.append(activeSize.width()).append("x").append(activeSize.height()).append(')'); + } + } + } catch (CameraAccessException e) { + builder.append("\n (access denied)"); + } + return builder.toString(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 59820ea7..0a3032cf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -38,6 +38,7 @@ public class Options { private boolean listEncoders; private boolean listDisplays; + private boolean listCameras; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -154,7 +155,7 @@ public class Options { } public boolean getList() { - return listEncoders || listDisplays; + return listEncoders || listDisplays || listCameras; } public boolean getListEncoders() { @@ -165,6 +166,10 @@ public class Options { return listDisplays; } + public boolean getListCameras() { + return listCameras; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } @@ -312,6 +317,9 @@ public class Options { case "list_displays": options.listDisplays = Boolean.parseBoolean(value); break; + case "list_cameras": + options.listCameras = Boolean.parseBoolean(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 1b56b859..2129dbc0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -98,8 +98,9 @@ public final class Server { boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); + boolean camera = false; - Workarounds.apply(audio); + Workarounds.apply(audio, camera); List asyncProcessors = new ArrayList<>(); @@ -207,6 +208,10 @@ public final class Server { if (options.getListDisplays()) { Ln.i(LogUtils.buildDisplayListMessage()); } + if (options.getListCameras()) { + Workarounds.apply(false, true); + Ln.i(LogUtils.buildCameraListMessage()); + } // Just print the requested data, do not mirror return; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 74c0202a..b8ee68ca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -28,14 +28,13 @@ public final class Workarounds { // not instantiable } - public static void apply(boolean audio) { + public static void apply(boolean audio, boolean camera) { Workarounds.prepareMainLooper(); boolean mustFillAppInfo = false; boolean mustFillBaseContext = false; boolean mustFillAppContext = false; - if (Build.BRAND.equalsIgnoreCase("meizu")) { // Workarounds must be applied for Meizu phones: // - @@ -65,6 +64,11 @@ public final class Workarounds { mustFillAppContext = true; } + if (camera) { + mustFillAppInfo = true; + mustFillBaseContext = true; + } + if (mustFillAppInfo) { Workarounds.fillAppInfo(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 69803971..ae04a6d2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -1,9 +1,14 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; + import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.camera2.CameraManager; import android.os.IBinder; import android.os.IInterface; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -26,6 +31,7 @@ public final class ServiceManager { private static StatusBarManager statusBarManager; private static ClipboardManager clipboardManager; private static ActivityManager activityManager; + private static CameraManager cameraManager; private ServiceManager() { /* not instantiable */ @@ -129,4 +135,16 @@ public final class ServiceManager { return activityManager; } + + public static CameraManager getCameraManager() { + if (cameraManager == null) { + try { + Constructor ctor = CameraManager.class.getDeclaredConstructor(Context.class); + cameraManager = ctor.newInstance(FakeContext.get()); + } catch (Exception e) { + throw new AssertionError(e); + } + } + return cameraManager; + } } From f032262cd76b27621813f9fe13f4e18981471d4c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 24 Oct 2023 23:46:56 +0200 Subject: [PATCH 0975/1133] Add --list-camera-sizes Add an option to list the device camera declared sizes. PR #4213 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 9 +++++++++ app/src/options.h | 1 + app/src/server.c | 3 +++ .../main/java/com/genymobile/scrcpy/LogUtils.java | 12 +++++++++++- .../src/main/java/com/genymobile/scrcpy/Options.java | 10 +++++++++- .../src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- 9 files changed, 41 insertions(+), 4 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index b6f550c5..1cf750ac 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -23,6 +23,7 @@ _scrcpy() { --kill-adb-on-close -K --hid-keyboard --legacy-paste + --list-camera-sizes --list-cameras --list-displays --list-encoders diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 3ba2c4b8..926bc0a1 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -30,6 +30,7 @@ arguments=( '--kill-adb-on-close[Kill adb when scrcpy terminates]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' + '--list-camera-sizes[List the valid camera capture sizes]' '--list-cameras[List cameras available on the device]' '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 30d15b4d..428254dd 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -155,6 +155,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. +.TP +.B \-\-list\-camera\-sizes +List the valid camera capture sizes. + .TP .B \-\-list\-cameras List cameras available on the device. diff --git a/app/src/cli.c b/app/src/cli.c index c9382c5b..788a629b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -82,6 +82,7 @@ enum { OPT_TIME_LIMIT, OPT_PAUSE_ON_EXIT, OPT_LIST_CAMERAS, + OPT_LIST_CAMERA_SIZES, }; struct sc_option { @@ -326,6 +327,11 @@ static const struct sc_option options[] = { .longopt = "list-cameras", .text = "List device cameras.", }, + { + .longopt_id = OPT_LIST_CAMERA_SIZES, + .longopt = "list-camera-sizes", + .text = "List the valid camera capture sizes.", + }, { .longopt_id = OPT_LIST_DISPLAYS, .longopt = "list-displays", @@ -2007,6 +2013,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_LIST_CAMERAS: opts->list |= SC_OPTION_LIST_CAMERAS; break; + case OPT_LIST_CAMERA_SIZES: + opts->list |= SC_OPTION_LIST_CAMERA_SIZES; + break; case OPT_REQUIRE_AUDIO: opts->require_audio = true; break; diff --git a/app/src/options.h b/app/src/options.h index e960968a..070a2b00 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -183,6 +183,7 @@ struct scrcpy_options { #define SC_OPTION_LIST_ENCODERS 0x1 #define SC_OPTION_LIST_DISPLAYS 0x2 #define SC_OPTION_LIST_CAMERAS 0x4 +#define SC_OPTION_LIST_CAMERA_SIZES 0x8 uint8_t list; }; diff --git a/app/src/server.c b/app/src/server.c index 571b813c..424c67e9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -320,6 +320,9 @@ execute_server(struct sc_server *server, if (params->list & SC_OPTION_LIST_CAMERAS) { ADD_PARAM("list_cameras=true"); } + if (params->list & SC_OPTION_LIST_CAMERA_SIZES) { + ADD_PARAM("list_camera_sizes=true"); + } #undef ADD_PARAM diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index d609adcd..7806cf51 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -7,6 +7,8 @@ import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.MediaCodec; import java.util.List; @@ -79,7 +81,7 @@ public final class LogUtils { } } - public static String buildCameraListMessage() { + public static String buildCameraListMessage(boolean includeSizes) { StringBuilder builder = new StringBuilder("List of cameras:"); CameraManager cameraManager = ServiceManager.getCameraManager(); try { @@ -96,6 +98,14 @@ public final class LogUtils { Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); builder.append(activeSize.width()).append("x").append(activeSize.height()).append(')'); + + if (includeSizes) { + StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); + for (android.util.Size size : sizes) { + builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); + } + } } } } catch (CameraAccessException e) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 0a3032cf..c9600404 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -39,6 +39,7 @@ public class Options { private boolean listEncoders; private boolean listDisplays; private boolean listCameras; + private boolean listCameraSizes; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -155,7 +156,7 @@ public class Options { } public boolean getList() { - return listEncoders || listDisplays || listCameras; + return listEncoders || listDisplays || listCameras || listCameraSizes; } public boolean getListEncoders() { @@ -170,6 +171,10 @@ public class Options { return listCameras; } + public boolean getListCameraSizes() { + return listCameraSizes; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } @@ -320,6 +325,9 @@ public class Options { case "list_cameras": options.listCameras = Boolean.parseBoolean(value); break; + case "list_camera_sizes": + options.listCameraSizes = Boolean.parseBoolean(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2129dbc0..4dbc00fe 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -208,9 +208,9 @@ public final class Server { if (options.getListDisplays()) { Ln.i(LogUtils.buildDisplayListMessage()); } - if (options.getListCameras()) { + if (options.getListCameras() || options.getListCameraSizes()) { Workarounds.apply(false, true); - Ln.i(LogUtils.buildCameraListMessage()); + Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes())); } // Just print the requested data, do not mirror return; From bfeecc01316cb04b992f56c2b5d0ea9f21a45b7d Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:07:19 +0800 Subject: [PATCH 0976/1133] Add camera mirroring Add --video-source=camera, and related options: - --camera-id=: select the camera by its id (see --list-cameras); - --camera-size=x: select the capture size. Fixed #241 PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 9 + app/data/zsh-completion/_scrcpy | 3 + app/scrcpy.1 | 18 ++ app/src/cli.c | 78 ++++++++ app/src/options.c | 3 + app/src/options.h | 8 + app/src/scrcpy.c | 3 + app/src/server.c | 12 ++ app/src/server.h | 3 + .../com/genymobile/scrcpy/CameraCapture.java | 180 ++++++++++++++++++ .../genymobile/scrcpy/HandlerExecutor.java | 23 +++ .../java/com/genymobile/scrcpy/Options.java | 43 +++++ .../com/genymobile/scrcpy/ScreenCapture.java | 3 +- .../java/com/genymobile/scrcpy/Server.java | 13 +- .../com/genymobile/scrcpy/SurfaceCapture.java | 7 +- .../com/genymobile/scrcpy/SurfaceEncoder.java | 8 +- .../com/genymobile/scrcpy/VideoSource.java | 22 +++ 17 files changed, 426 insertions(+), 10 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CameraCapture.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/HandlerExecutor.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/VideoSource.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 1cf750ac..27448baf 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -10,6 +10,8 @@ _scrcpy() { --audio-source= --audio-output-buffer= -b --video-bit-rate= + --camera-id= + --camera-size= --crop= -d --select-usb --disable-screensaver @@ -74,6 +76,7 @@ _scrcpy() { --video-codec= --video-codec-options= --video-encoder= + --video-source= -w --stay-awake --window-borderless --window-title= @@ -93,6 +96,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) return ;; + --video-source) + COMPREPLY=($(compgen -W 'display camera' -- "$cur")) + return + ;; --audio-source) COMPREPLY=($(compgen -W 'output mic' -- "$cur")) return @@ -141,6 +148,8 @@ _scrcpy() { |--audio-codec-options \ |--audio-encoder \ |--audio-output-buffer \ + |--camera-id \ + |--camera-size \ |--crop \ |--display-id \ |--display-buffer \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 926bc0a1..58c3cccc 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -17,6 +17,8 @@ arguments=( '--audio-source=[Select the audio source]:source:(output mic)' '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' + '--camera-id=[Specify the camera id to mirror]' + '--camera-size=[Specify an explicit camera capture size]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' '--disable-screensaver[Disable screensaver while scrcpy is running]' @@ -78,6 +80,7 @@ arguments=( '--video-codec=[Select the video codec]:codec:(h264 h265 av1)' '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' '--video-encoder=[Use a specific MediaCodec video encoder]' + '--video-source=[Select the video source]:source:(display camera)' {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' '--window-borderless[Disable window decorations \(display borderless window\)]' '--window-title=[Set a custom window title]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 428254dd..b108d675 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -75,6 +75,16 @@ Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are s Default is 8M (8000000). +.TP +.BI "\-\-camera\-id " id +Specify the device camera id to mirror. + +The available camera ids can be listed by \-\-list\-cameras. + +.TP +.BI "\-\-camera\-size " width\fRx\fIheight +Specify an explicit camera capture size. + .TP .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy Crop the device screen on the server. @@ -434,6 +444,14 @@ Use a specific MediaCodec video encoder (depending on the codec provided by \fB\ The available encoders can be listed by \-\-list\-encoders. +.TP +.BI "\-\-video\-source " source +Select the video source (display or camera). + +Camera mirroring requires Android 12+. + +Default is display. + .TP .B \-w, \-\-stay-awake Keep the device on while scrcpy is running, when the device is plugged in. diff --git a/app/src/cli.c b/app/src/cli.c index 788a629b..4b54b401 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -77,12 +77,15 @@ enum { OPT_NO_VIDEO, OPT_NO_AUDIO_PLAYBACK, OPT_NO_VIDEO_PLAYBACK, + OPT_VIDEO_SOURCE, OPT_AUDIO_SOURCE, OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, OPT_PAUSE_ON_EXIT, OPT_LIST_CAMERAS, OPT_LIST_CAMERA_SIZES, + OPT_CAMERA_ID, + OPT_CAMERA_SIZE, }; struct sc_option { @@ -199,6 +202,20 @@ static const struct sc_option options[] = { .longopt = "bit-rate", .argdesc = "value", }, + { + .longopt_id = OPT_CAMERA_ID, + .longopt = "camera-id", + .argdesc = "id", + .text = "Specify the device camera id to mirror.\n" + "The available camera ids can be listed by:\n" + " scrcpy --list-cameras", + }, + { + .longopt_id = OPT_CAMERA_SIZE, + .longopt = "camera-size", + .argdesc = "x", + .text = "Specify an explicit camera capture size.", + }, { // Not really deprecated (--codec has never been released), but without // declaring an explicit --codec option, getopt_long() partial matching @@ -703,6 +720,14 @@ static const struct sc_option options[] = { "codec provided by --video-codec).\n" "The available encoders can be listed by --list-encoders.", }, + { + .longopt_id = OPT_VIDEO_SOURCE, + .longopt = "video-source", + .argdesc = "source", + .text = "Select the video source (display or camera).\n" + "Camera mirroring requires Android 12+.\n" + "Default is display.", + }, { .shortopt = 'w', .longopt = "stay-awake", @@ -1643,6 +1668,22 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { return false; } +static bool +parse_video_source(const char *optarg, enum sc_video_source *source) { + if (!strcmp(optarg, "display")) { + *source = SC_VIDEO_SOURCE_DISPLAY; + return true; + } + + if (!strcmp(optarg, "camera")) { + *source = SC_VIDEO_SOURCE_CAMERA; + return true; + } + + LOGE("Unsupported video source: %s (expected display or camera)", optarg); + return false; +} + static bool parse_audio_source(const char *optarg, enum sc_audio_source *source) { if (!strcmp(optarg, "mic")) { @@ -2030,6 +2071,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_VIDEO_SOURCE: + if (!parse_video_source(optarg, &opts->video_source)) { + return false; + } + break; case OPT_AUDIO_SOURCE: if (!parse_audio_source(optarg, &opts->audio_source)) { return false; @@ -2048,6 +2094,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_CAMERA_ID: + opts->camera_id = optarg; + break; + case OPT_CAMERA_SIZE: + opts->camera_size = optarg; + break; default: // getopt prints the error message on stderr return false; @@ -2141,6 +2193,32 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->force_adb_forward = true; } + if (opts->video_source == SC_VIDEO_SOURCE_CAMERA) { + if (opts->display_id) { + LOGE("--display-id is only available with --video-source=display"); + return false; + } + + if (!opts->camera_id) { + LOGE("Camera id must be specified by --camera-id " + "(list the available ids with --list-cameras)"); + return false; + } + + if (!opts->camera_size) { + LOGE("Camera size must be specified by --camera-size"); + return false; + } + + if (opts->control) { + LOGI("Camera video source: control disabled"); + opts->control = false; + } + } else if (opts->camera_id || opts->camera_size) { + LOGE("Camera options are only available with --video-source=camera"); + return false; + } + if (opts->record_format && !opts->record_filename) { LOGE("Record format specified without recording"); return false; diff --git a/app/src/options.c b/app/src/options.c index b633d762..22be9f36 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -11,9 +11,12 @@ const struct scrcpy_options scrcpy_options_default = { .audio_codec_options = NULL, .video_encoder = NULL, .audio_encoder = NULL, + .camera_id = NULL, + .camera_size = NULL, .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, + .video_source = SC_VIDEO_SOURCE_DISPLAY, .audio_source = SC_AUDIO_SOURCE_OUTPUT, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index 070a2b00..af195793 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -44,6 +44,11 @@ enum sc_codec { SC_CODEC_RAW, }; +enum sc_video_source { + SC_VIDEO_SOURCE_DISPLAY, + SC_VIDEO_SOURCE_CAMERA, +}; + enum sc_audio_source { SC_AUDIO_SOURCE_OUTPUT, SC_AUDIO_SOURCE_MIC, @@ -117,9 +122,12 @@ struct scrcpy_options { const char *audio_codec_options; const char *video_encoder; const char *audio_encoder; + const char *camera_id; + const char *camera_size; enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; + enum sc_video_source video_source; enum sc_audio_source audio_source; enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5f0158f1..d51d573b 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -351,6 +351,7 @@ scrcpy(struct scrcpy_options *options) { .log_level = options->log_level, .video_codec = options->video_codec, .audio_codec = options->audio_codec, + .video_source = options->video_source, .audio_source = options->audio_source, .crop = options->crop, .port_range = options->port_range, @@ -371,6 +372,8 @@ scrcpy(struct scrcpy_options *options) { .audio_codec_options = options->audio_codec_options, .video_encoder = options->video_encoder, .audio_encoder = options->audio_encoder, + .camera_id = options->camera_id, + .camera_size = options->camera_size, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index 424c67e9..413103ef 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -76,6 +76,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->video_encoder); free((char *) params->audio_encoder); free((char *) params->tcpip_dst); + free((char *) params->camera_id); } static bool @@ -103,6 +104,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(video_encoder); COPY(audio_encoder); COPY(tcpip_dst); + COPY(camera_id); #undef COPY return true; @@ -247,6 +249,10 @@ execute_server(struct sc_server *server, ADD_PARAM("audio_codec=%s", sc_server_get_codec_name(params->audio_codec)); } + if (params->video_source != SC_VIDEO_SOURCE_DISPLAY) { + assert(params->video_source == SC_VIDEO_SOURCE_CAMERA); + ADD_PARAM("video_source=camera"); + } if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT) { assert(params->audio_source == SC_AUDIO_SOURCE_MIC); ADD_PARAM("audio_source=mic"); @@ -274,6 +280,12 @@ execute_server(struct sc_server *server, if (params->display_id) { ADD_PARAM("display_id=%" PRIu32, params->display_id); } + if (params->camera_id) { + ADD_PARAM("camera_id=%s", params->camera_id); + } + if (params->camera_size) { + ADD_PARAM("camera_size=%s", params->camera_size); + } if (params->show_touches) { ADD_PARAM("show_touches=true"); } diff --git a/app/src/server.h b/app/src/server.h index 04955974..92c5f22e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -26,12 +26,15 @@ struct sc_server_params { enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; + enum sc_video_source video_source; enum sc_audio_source audio_source; const char *crop; const char *video_codec_options; const char *audio_codec_options; const char *video_encoder; const char *audio_encoder; + const char *camera_id; + const char *camera_size; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java new file mode 100644 index 00000000..3efd4cb2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -0,0 +1,180 @@ +package com.genymobile.scrcpy; + +import com.genymobile.scrcpy.wrappers.ServiceManager; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.Surface; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class CameraCapture extends SurfaceCapture { + + private final String explicitCameraId; + private final Size explicitSize; + + private HandlerThread cameraThread; + private Handler cameraHandler; + private CameraDevice cameraDevice; + private Executor cameraExecutor; + + public CameraCapture(String explicitCameraId, Size explicitSize) { + this.explicitCameraId = explicitCameraId; + this.explicitSize = explicitSize; + } + + @Override + public void init() throws IOException { + cameraThread = new HandlerThread("camera"); + cameraThread.start(); + cameraHandler = new Handler(cameraThread.getLooper()); + cameraExecutor = new HandlerExecutor(cameraHandler); + + try { + cameraDevice = openCamera(explicitCameraId); + } catch (CameraAccessException | InterruptedException e) { + throw new IOException(e); + } + } + + @Override + public void start(Surface surface) throws IOException { + try { + CameraCaptureSession session = createCaptureSession(cameraDevice, surface); + CaptureRequest request = createCaptureRequest(surface); + setRepeatingRequest(session, request); + } catch (CameraAccessException | InterruptedException e) { + throw new IOException(e); + } + } + + @Override + public void release() { + if (cameraDevice != null) { + cameraDevice.close(); + } + if (cameraThread != null) { + cameraThread.quitSafely(); + } + } + + @Override + public Size getSize() { + return explicitSize; + } + + @Override + public boolean setMaxSize(int maxSize) { + return false; + } + + @SuppressLint("MissingPermission") + @TargetApi(Build.VERSION_CODES.S) + private CameraDevice openCamera(String id) throws CameraAccessException, InterruptedException { + CompletableFuture future = new CompletableFuture<>(); + ServiceManager.getCameraManager().openCamera(id, new CameraDevice.StateCallback() { + @Override + public void onOpened(CameraDevice camera) { + Ln.d("Camera opened successfully"); + future.complete(camera); + } + + @Override + public void onDisconnected(CameraDevice camera) { + Ln.w("Camera disconnected"); + // TODO + } + + @Override + public void onError(CameraDevice camera, int error) { + int cameraAccessExceptionErrorCode; + switch (error) { + case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_IN_USE; + break; + case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE: + cameraAccessExceptionErrorCode = CameraAccessException.MAX_CAMERAS_IN_USE; + break; + case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_DISABLED; + break; + case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE: + case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE: + default: + cameraAccessExceptionErrorCode = CameraAccessException.CAMERA_ERROR; + break; + } + future.completeExceptionally(new CameraAccessException(cameraAccessExceptionErrorCode)); + } + }, cameraHandler); + + try { + return future.get(); + } catch (ExecutionException e) { + throw (CameraAccessException) e.getCause(); + } + } + + @TargetApi(Build.VERSION_CODES.S) + private CameraCaptureSession createCaptureSession(CameraDevice camera, Surface surface) throws CameraAccessException, InterruptedException { + CompletableFuture future = new CompletableFuture<>(); + OutputConfiguration outputConfig = new OutputConfiguration(surface); + List outputs = Arrays.asList(outputConfig); + SessionConfiguration sessionConfig = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs, cameraExecutor, + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + future.complete(session); + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR)); + } + }); + + camera.createCaptureSession(sessionConfig); + + try { + return future.get(); + } catch (ExecutionException e) { + throw (CameraAccessException) e.getCause(); + } + } + + private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException { + CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + requestBuilder.addTarget(surface); + return requestBuilder.build(); + } + + @TargetApi(Build.VERSION_CODES.S) + private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException, InterruptedException { + session.setRepeatingRequest(request, new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) { + // Called for each frame captured, do nothing + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { + Ln.w("Camera capture failed: frame " + failure.getFrameNumber()); + } + }, cameraHandler); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/HandlerExecutor.java b/server/src/main/java/com/genymobile/scrcpy/HandlerExecutor.java new file mode 100644 index 00000000..1f5f0a4f --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/HandlerExecutor.java @@ -0,0 +1,23 @@ +package com.genymobile.scrcpy; + +import android.os.Handler; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +// Inspired from hidden android.os.HandlerExecutor + +public class HandlerExecutor implements Executor { + private final Handler handler; + + public HandlerExecutor(Handler handler) { + this.handler = handler; + } + + @Override + public void execute(Runnable command) { + if (!handler.post(command)) { + throw new RejectedExecutionException(handler + " is shutting down"); + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index c9600404..2366f9c8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -14,6 +14,7 @@ public class Options { private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; private AudioCodec audioCodec = AudioCodec.OPUS; + private VideoSource videoSource = VideoSource.DISPLAY; private AudioSource audioSource = AudioSource.OUTPUT; private int videoBitRate = 8000000; private int audioBitRate = 128000; @@ -23,6 +24,8 @@ public class Options { private Rect crop; private boolean control = true; private int displayId; + private String cameraId; + private Size cameraSize; private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; @@ -75,6 +78,10 @@ public class Options { return audioCodec; } + public VideoSource getVideoSource() { + return videoSource; + } + public AudioSource getAudioSource() { return audioSource; } @@ -111,6 +118,14 @@ public class Options { return displayId; } + public String getCameraId() { + return cameraId; + } + + public Size getCameraSize() { + return cameraSize; + } + public boolean getShowTouches() { return showTouches; } @@ -244,6 +259,13 @@ public class Options { } options.audioCodec = audioCodec; break; + case "video_source": + VideoSource videoSource = VideoSource.findByName(value); + if (videoSource == null) { + throw new IllegalArgumentException("Video source " + value + " not supported"); + } + options.videoSource = videoSource; + break; case "audio_source": AudioSource audioSource = AudioSource.findByName(value); if (audioSource == null) { @@ -328,6 +350,16 @@ public class Options { case "list_camera_sizes": options.listCameraSizes = Boolean.parseBoolean(value); break; + case "camera_id": + if (!value.isEmpty()) { + options.cameraId = value; + } + break; + case "camera_size": + if (!value.isEmpty()) { + options.cameraSize = parseSize(value); + } + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; @@ -370,4 +402,15 @@ public class Options { int y = Integer.parseInt(tokens[3]); return new Rect(x, y, x + width, y + height); } + + private static Size parseSize(String size) { + // input format: "x" + String[] tokens = size.split("x"); + if (tokens.length != 2) { + throw new IllegalArgumentException("Invalid size format (expected x): \"" + size + "\""); + } + int width = Integer.parseInt(tokens[0]); + int height = Integer.parseInt(tokens[1]); + return new Size(width, height); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index f9ac66b8..f81332f5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -48,8 +48,9 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList } @Override - public void setMaxSize(int maxSize) { + public boolean setMaxSize(int maxSize) { device.setMaxSize(maxSize); + return true; } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4dbc00fe..e43b9f0a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -98,7 +98,7 @@ public final class Server { boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); - boolean camera = false; + boolean camera = options.getVideoSource() == VideoSource.CAMERA; Workarounds.apply(audio, camera); @@ -133,10 +133,15 @@ public final class Server { if (video) { Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), options.getSendFrameMeta()); - ScreenCapture screenCapture = new ScreenCapture(device); - SurfaceEncoder screenEncoder = new SurfaceEncoder(screenCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), + SurfaceCapture surfaceCapture; + if (options.getVideoSource() == VideoSource.DISPLAY) { + surfaceCapture = new ScreenCapture(device); + } else { + surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraSize()); + } + SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); - asyncProcessors.add(screenEncoder); + asyncProcessors.add(surfaceEncoder); } Completion completion = new Completion(asyncProcessors.size()); diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java index 45a0fd2f..207cfad8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; import android.view.Surface; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -31,7 +32,7 @@ public abstract class SurfaceCapture { /** * Called once before the capture starts. */ - public abstract void init(); + public abstract void init() throws IOException; /** * Called after the capture ends (if and only if {@link #init()} has been called). @@ -43,7 +44,7 @@ public abstract class SurfaceCapture { * * @param surface the surface which will be encoded */ - public abstract void start(Surface surface); + public abstract void start(Surface surface) throws IOException; /** * Return the video size @@ -57,5 +58,5 @@ public abstract class SurfaceCapture { * * @param maxSize Maximum size */ - public abstract void setMaxSize(int maxSize); + public abstract boolean setMaxSize(int maxSize); } diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java index 4af31e89..9f90115a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java @@ -122,9 +122,13 @@ public class SurfaceEncoder implements AsyncProcessor { return false; } - // Retry with a smaller device size + boolean accepted = capture.setMaxSize(newMaxSize); + if (!accepted) { + return false; + } + + // Retry with a smaller size Ln.i("Retrying with -m" + newMaxSize + "..."); - capture.setMaxSize(newMaxSize); return true; } diff --git a/server/src/main/java/com/genymobile/scrcpy/VideoSource.java b/server/src/main/java/com/genymobile/scrcpy/VideoSource.java new file mode 100644 index 00000000..b5a74fbe --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/VideoSource.java @@ -0,0 +1,22 @@ +package com.genymobile.scrcpy; + +public enum VideoSource { + DISPLAY("display"), + CAMERA("camera"); + + private final String name; + + VideoSource(String name) { + this.name = name; + } + + static VideoSource findByName(String name) { + for (VideoSource videoSource : VideoSource.values()) { + if (name.equals(videoSource.name)) { + return videoSource; + } + } + + return null; + } +} From d544e577c086efc89443c388466b14ae0f25af59 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 25 Oct 2023 00:09:54 +0200 Subject: [PATCH 0977/1133] Automatically select audio source If --audio-source is not specified, select the default value according to the video source: - for display mirroring, use device audio by default; - for camera mirroring, use microphone by default. PR #4213 --- app/src/cli.c | 10 ++++++++++ app/src/options.c | 2 +- app/src/options.h | 1 + app/src/server.c | 3 +-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 4b54b401..7f2b3d49 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2219,6 +2219,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) { + // Select the audio source according to the video source + if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) { + opts->audio_source = SC_AUDIO_SOURCE_OUTPUT; + } else { + opts->audio_source = SC_AUDIO_SOURCE_MIC; + LOGI("Camera video source: microphone audio source selected"); + } + } + if (opts->record_format && !opts->record_filename) { LOGE("Record format specified without recording"); return false; diff --git a/app/src/options.c b/app/src/options.c index 22be9f36..96741a7d 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -17,7 +17,7 @@ const struct scrcpy_options scrcpy_options_default = { .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, .video_source = SC_VIDEO_SOURCE_DISPLAY, - .audio_source = SC_AUDIO_SOURCE_OUTPUT, + .audio_source = SC_AUDIO_SOURCE_AUTO, .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, diff --git a/app/src/options.h b/app/src/options.h index af195793..afc6aa49 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -50,6 +50,7 @@ enum sc_video_source { }; enum sc_audio_source { + SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA SC_AUDIO_SOURCE_OUTPUT, SC_AUDIO_SOURCE_MIC, }; diff --git a/app/src/server.c b/app/src/server.c index 413103ef..81a371ae 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -253,8 +253,7 @@ execute_server(struct sc_server *server, assert(params->video_source == SC_VIDEO_SOURCE_CAMERA); ADD_PARAM("video_source=camera"); } - if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT) { - assert(params->audio_source == SC_AUDIO_SOURCE_MIC); + if (params->audio_source == SC_AUDIO_SOURCE_MIC) { ADD_PARAM("audio_source=mic"); } if (params->max_size) { From 64930e71b9636544468646e61dac3d7a4a62896c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 25 Oct 2023 00:18:05 +0200 Subject: [PATCH 0978/1133] Handle camera disconnection Stop mirroring on camera disconnection. PR #4213 --- .../java/com/genymobile/scrcpy/CameraCapture.java | 11 ++++++++++- .../java/com/genymobile/scrcpy/SurfaceCapture.java | 9 +++++++++ .../java/com/genymobile/scrcpy/SurfaceEncoder.java | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index 3efd4cb2..78ad0e09 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; public class CameraCapture extends SurfaceCapture { @@ -33,6 +34,8 @@ public class CameraCapture extends SurfaceCapture { private CameraDevice cameraDevice; private Executor cameraExecutor; + private final AtomicBoolean disconnected = new AtomicBoolean(); + public CameraCapture(String explicitCameraId, Size explicitSize) { this.explicitCameraId = explicitCameraId; this.explicitSize = explicitSize; @@ -97,7 +100,8 @@ public class CameraCapture extends SurfaceCapture { @Override public void onDisconnected(CameraDevice camera) { Ln.w("Camera disconnected"); - // TODO + disconnected.set(true); + requestReset(); } @Override @@ -177,4 +181,9 @@ public class CameraCapture extends SurfaceCapture { } }, cameraHandler); } + + @Override + public boolean isClosed() { + return disconnected.get(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java index 207cfad8..e300e4d6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceCapture.java @@ -59,4 +59,13 @@ public abstract class SurfaceCapture { * @param maxSize Maximum size */ public abstract boolean setMaxSize(int maxSize); + + /** + * Indicate if the capture has been closed internally. + * + * @return {@code true} is the capture is closed, {@code false} otherwise. + */ + public boolean isClosed() { + return false; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java index 9f90115a..28435c09 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java @@ -181,6 +181,11 @@ public class SurfaceEncoder implements AsyncProcessor { } } + if (capture.isClosed()) { + // The capture might have been closed internally (for example if the camera is disconnected) + alive = false; + } + return !eof && alive; } From 7f8d079c8c5dd28942992361d2f521a554d18416 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Oct 2023 23:07:56 +0200 Subject: [PATCH 0979/1133] Make camera id optional If no camera id is provided, use the first camera available. PR #4213 --- app/src/cli.c | 6 ------ .../com/genymobile/scrcpy/CameraCapture.java | 21 ++++++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 7f2b3d49..cac54730 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2199,12 +2199,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } - if (!opts->camera_id) { - LOGE("Camera id must be specified by --camera-id " - "(list the available ids with --list-cameras)"); - return false; - } - if (!opts->camera_size) { LOGE("Camera size must be specified by --camera-size"); return false; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index 78ad0e09..5aadbae7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -7,6 +7,7 @@ import android.annotation.TargetApi; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.OutputConfiguration; @@ -49,12 +50,30 @@ public class CameraCapture extends SurfaceCapture { cameraExecutor = new HandlerExecutor(cameraHandler); try { - cameraDevice = openCamera(explicitCameraId); + String cameraId = selectCamera(explicitCameraId); + if (cameraId == null) { + throw new IOException("No matching camera found"); + } + + Ln.i("Using camera '" + cameraId + "'"); + cameraDevice = openCamera(cameraId); } catch (CameraAccessException | InterruptedException e) { throw new IOException(e); } } + private static String selectCamera(String explicitCameraId) throws CameraAccessException { + if (explicitCameraId != null) { + return explicitCameraId; + } + + CameraManager cameraManager = ServiceManager.getCameraManager(); + + String[] cameraIds = cameraManager.getCameraIdList(); + // Use the first one + return cameraIds.length > 0 ? cameraIds[0] : null; + } + @Override public void start(Surface surface) throws IOException { try { From faebb7d70ab4076fcbe84f00a1e2d07d871e41d9 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sat, 22 Jul 2023 17:17:05 +0800 Subject: [PATCH 0980/1133] Add --camera-facing Add an option to select the camera by its lens facing (front, back or external). PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 5 ++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 +++ app/src/cli.c | 50 ++++++++++++++++++- app/src/options.c | 1 + app/src/options.h | 8 +++ app/src/scrcpy.c | 1 + app/src/server.c | 18 +++++++ app/src/server.h | 1 + .../com/genymobile/scrcpy/CameraCapture.java | 27 ++++++++-- .../com/genymobile/scrcpy/CameraFacing.java | 33 ++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 14 ++++++ .../java/com/genymobile/scrcpy/Server.java | 2 +- 13 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CameraFacing.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 27448baf..339a819a 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -11,6 +11,7 @@ _scrcpy() { --audio-output-buffer= -b --video-bit-rate= --camera-id= + --camera-facing= --camera-size= --crop= -d --select-usb @@ -104,6 +105,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'output mic' -- "$cur")) return ;; + --camera-facing) + COMPREPLY=($(compgen -W 'front back external' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 58c3cccc..c92f0ac1 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -18,6 +18,7 @@ arguments=( '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--camera-id=[Specify the camera id to mirror]' + '--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)' '--camera-size=[Specify an explicit camera capture size]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b108d675..d2fb3ad5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -81,6 +81,12 @@ Specify the device camera id to mirror. The available camera ids can be listed by \-\-list\-cameras. +.TP +.BI "\-\-camera\-facing " facing +Select the device camera by its facing direction. + +Possible values are "front", "back" and "external". + .TP .BI "\-\-camera\-size " width\fRx\fIheight Specify an explicit camera capture size. diff --git a/app/src/cli.c b/app/src/cli.c index cac54730..96b8c26d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -86,6 +86,7 @@ enum { OPT_LIST_CAMERA_SIZES, OPT_CAMERA_ID, OPT_CAMERA_SIZE, + OPT_CAMERA_FACING, }; struct sc_option { @@ -210,6 +211,13 @@ static const struct sc_option options[] = { "The available camera ids can be listed by:\n" " scrcpy --list-cameras", }, + { + .longopt_id = OPT_CAMERA_FACING, + .longopt = "camera-facing", + .argdesc = "facing", + .text = "Select the device camera by its facing direction.\n" + "Possible values are \"front\", \"back\" and \"external\".", + }, { .longopt_id = OPT_CAMERA_SIZE, .longopt = "camera-size", @@ -1700,6 +1708,34 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) { return false; } +static bool +parse_camera_facing(const char *optarg, enum sc_camera_facing *facing) { + if (!strcmp(optarg, "front")) { + *facing = SC_CAMERA_FACING_FRONT; + return true; + } + + if (!strcmp(optarg, "back")) { + *facing = SC_CAMERA_FACING_BACK; + return true; + } + + if (!strcmp(optarg, "external")) { + *facing = SC_CAMERA_FACING_EXTERNAL; + return true; + } + + if (*optarg == '\0') { + // Empty string is a valid value (equivalent to not passing the option) + *facing = SC_CAMERA_FACING_ANY; + return true; + } + + LOGE("Unsupported camera facing: %s (expected front, back or external)", + optarg); + return false; +} + static bool parse_time_limit(const char *s, sc_tick *tick) { long value; @@ -2100,6 +2136,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_CAMERA_SIZE: opts->camera_size = optarg; break; + case OPT_CAMERA_FACING: + if (!parse_camera_facing(optarg, &opts->camera_facing)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; @@ -2199,6 +2240,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } + if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) { + LOGE("Could not specify both --camera-id and --camera-facing"); + return false; + } + if (!opts->camera_size) { LOGE("Camera size must be specified by --camera-size"); return false; @@ -2208,7 +2254,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], LOGI("Camera video source: control disabled"); opts->control = false; } - } else if (opts->camera_id || opts->camera_size) { + } else if (opts->camera_id + || opts->camera_facing != SC_CAMERA_FACING_ANY + || opts->camera_size) { LOGE("Camera options are only available with --video-source=camera"); return false; } diff --git a/app/src/options.c b/app/src/options.c index 96741a7d..2adb4323 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -21,6 +21,7 @@ const struct scrcpy_options scrcpy_options_default = { .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, + .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST, diff --git a/app/src/options.h b/app/src/options.h index afc6aa49..1e783bae 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -55,6 +55,13 @@ enum sc_audio_source { SC_AUDIO_SOURCE_MIC, }; +enum sc_camera_facing { + SC_CAMERA_FACING_ANY, + SC_CAMERA_FACING_FRONT, + SC_CAMERA_FACING_BACK, + SC_CAMERA_FACING_EXTERNAL, +}; + enum sc_lock_video_orientation { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, // lock the current orientation when scrcpy starts @@ -133,6 +140,7 @@ struct scrcpy_options { enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; + enum sc_camera_facing camera_facing; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d51d573b..54e794e0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -353,6 +353,7 @@ scrcpy(struct scrcpy_options *options) { .audio_codec = options->audio_codec, .video_source = options->video_source, .audio_source = options->audio_source, + .camera_facing = options->camera_facing, .crop = options->crop, .port_range = options->port_range, .tunnel_host = options->tunnel_host, diff --git a/app/src/server.c b/app/src/server.c index 81a371ae..7f8a4926 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -183,6 +183,20 @@ sc_server_get_codec_name(enum sc_codec codec) { } } +static const char * +sc_server_get_camera_facing_name(enum sc_camera_facing camera_facing) { + switch (camera_facing) { + case SC_CAMERA_FACING_FRONT: + return "front"; + case SC_CAMERA_FACING_BACK: + return "back"; + case SC_CAMERA_FACING_EXTERNAL: + return "external"; + default: + return NULL; + } +} + static sc_pid execute_server(struct sc_server *server, const struct sc_server_params *params) { @@ -285,6 +299,10 @@ execute_server(struct sc_server *server, if (params->camera_size) { ADD_PARAM("camera_size=%s", params->camera_size); } + if (params->camera_facing != SC_CAMERA_FACING_ANY) { + ADD_PARAM("camera_facing=%s", + sc_server_get_camera_facing_name(params->camera_facing)); + } if (params->show_touches) { ADD_PARAM("show_touches=true"); } diff --git a/app/src/server.h b/app/src/server.h index 92c5f22e..786aea5c 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -28,6 +28,7 @@ struct sc_server_params { enum sc_codec audio_codec; enum sc_video_source video_source; enum sc_audio_source audio_source; + enum sc_camera_facing camera_facing; const char *crop; const char *video_codec_options; const char *audio_codec_options; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index 5aadbae7..949eb343 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -6,6 +6,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; @@ -28,6 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class CameraCapture extends SurfaceCapture { private final String explicitCameraId; + private final CameraFacing cameraFacing; private final Size explicitSize; private HandlerThread cameraThread; @@ -37,8 +39,9 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); - public CameraCapture(String explicitCameraId, Size explicitSize) { + public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize) { this.explicitCameraId = explicitCameraId; + this.cameraFacing = cameraFacing; this.explicitSize = explicitSize; } @@ -50,7 +53,7 @@ public class CameraCapture extends SurfaceCapture { cameraExecutor = new HandlerExecutor(cameraHandler); try { - String cameraId = selectCamera(explicitCameraId); + String cameraId = selectCamera(explicitCameraId, cameraFacing); if (cameraId == null) { throw new IOException("No matching camera found"); } @@ -62,7 +65,7 @@ public class CameraCapture extends SurfaceCapture { } } - private static String selectCamera(String explicitCameraId) throws CameraAccessException { + private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException { if (explicitCameraId != null) { return explicitCameraId; } @@ -70,8 +73,22 @@ public class CameraCapture extends SurfaceCapture { CameraManager cameraManager = ServiceManager.getCameraManager(); String[] cameraIds = cameraManager.getCameraIdList(); - // Use the first one - return cameraIds.length > 0 ? cameraIds[0] : null; + if (cameraFacing == null) { + // Use the first one + return cameraIds.length > 0 ? cameraIds[0] : null; + } + + for (String cameraId : cameraIds) { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + + int facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (cameraFacing.value() == facing) { + return cameraId; + } + } + + // Not found + return null; } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraFacing.java b/server/src/main/java/com/genymobile/scrcpy/CameraFacing.java new file mode 100644 index 00000000..b7e8daa5 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CameraFacing.java @@ -0,0 +1,33 @@ +package com.genymobile.scrcpy; + +import android.annotation.SuppressLint; +import android.hardware.camera2.CameraCharacteristics; + +public enum CameraFacing { + FRONT("front", CameraCharacteristics.LENS_FACING_FRONT), + BACK("back", CameraCharacteristics.LENS_FACING_BACK), + @SuppressLint("InlinedApi") // introduced in API 23 + EXTERNAL("external", CameraCharacteristics.LENS_FACING_EXTERNAL); + + private final String name; + private final int value; + + CameraFacing(String name, int value) { + this.name = name; + this.value = value; + } + + int value() { + return value; + } + + static CameraFacing findByName(String name) { + for (CameraFacing facing : CameraFacing.values()) { + if (name.equals(facing.name)) { + return facing; + } + } + + return null; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 2366f9c8..11af3cca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -26,6 +26,7 @@ public class Options { private int displayId; private String cameraId; private Size cameraSize; + private CameraFacing cameraFacing; private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; @@ -126,6 +127,10 @@ public class Options { return cameraSize; } + public CameraFacing getCameraFacing() { + return cameraFacing; + } + public boolean getShowTouches() { return showTouches; } @@ -360,6 +365,15 @@ public class Options { options.cameraSize = parseSize(value); } break; + case "camera_facing": + if (!value.isEmpty()) { + CameraFacing facing = CameraFacing.findByName(value); + if (facing == null) { + throw new IllegalArgumentException("Camera facing " + value + " not supported"); + } + options.cameraFacing = facing; + } + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index e43b9f0a..1a93323a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -137,7 +137,7 @@ public final class Server { if (options.getVideoSource() == VideoSource.DISPLAY) { surfaceCapture = new ScreenCapture(device); } else { - surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraSize()); + surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize()); } SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); From dd36d6135fb08f8d3a99f9e9eb4ec8e19af0a660 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 26 Oct 2023 23:54:34 +0200 Subject: [PATCH 0981/1133] Support camera size selection using -m/--camera-ar In addition to --camera-size to specify an explicit size, make it possible to select the camera size automatically, respecting the maximum size (already used for display mirroring) and an aspect ratio. For example, "scrcpy --video-source=camera" followed by: - (no additional arguments) : mirrors at the maximum size, any a-r - -m1920 : only consider valid sizes having both dimensions not above 1920 - --camera-ar=4:3 : only consider valid sizes having an aspect ratio of 4:3 (+/- 10%) - -m2048 --camera-ar=1.6 : only consider valid sizes having both dimensions not above 2048 and an aspect ratio of 1.6 (+/- 10%) PR #4213 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- app/data/bash-completion/scrcpy | 2 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 + app/src/cli.c | 27 ++++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 5 + app/src/server.h | 1 + .../genymobile/scrcpy/CameraAspectRatio.java | 37 ++++++ .../com/genymobile/scrcpy/CameraCapture.java | 112 +++++++++++++++++- .../java/com/genymobile/scrcpy/Options.java | 26 ++++ .../java/com/genymobile/scrcpy/Server.java | 3 +- 13 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CameraAspectRatio.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 339a819a..c8b6609e 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -10,6 +10,7 @@ _scrcpy() { --audio-source= --audio-output-buffer= -b --video-bit-rate= + --camera-ar= --camera-id= --camera-facing= --camera-size= @@ -153,6 +154,7 @@ _scrcpy() { |--audio-codec-options \ |--audio-encoder \ |--audio-output-buffer \ + |--camera-ar \ |--camera-id \ |--camera-size \ |--crop \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index c92f0ac1..823e6b9e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -17,6 +17,7 @@ arguments=( '--audio-source=[Select the audio source]:source:(output mic)' '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' + '--camera-ar=[Select the camera size by its aspect ratio]' '--camera-id=[Specify the camera id to mirror]' '--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)' '--camera-size=[Specify an explicit camera capture size]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d2fb3ad5..c473adb5 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -75,6 +75,12 @@ Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are s Default is 8M (8000000). +.TP +.BI "\-\-camera\-ar " ar +Select the camera size by its aspect ratio (+/- 10%). + +Possible values are "sensor" (use the camera sensor aspect ratio), ":" (e.g. "4:3") and "" (e.g. "1.6"). + .TP .BI "\-\-camera\-id " id Specify the device camera id to mirror. diff --git a/app/src/cli.c b/app/src/cli.c index 96b8c26d..69a918c3 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -87,6 +87,7 @@ enum { OPT_CAMERA_ID, OPT_CAMERA_SIZE, OPT_CAMERA_FACING, + OPT_CAMERA_AR, }; struct sc_option { @@ -203,6 +204,15 @@ static const struct sc_option options[] = { .longopt = "bit-rate", .argdesc = "value", }, + { + .longopt_id = OPT_CAMERA_AR, + .longopt = "camera-ar", + .argdesc = "ar", + .text = "Select the camera size by its aspect ratio (+/- 10%).\n" + "Possible values are \"sensor\" (use the camera sensor aspect " + "ratio), \":\" (e.g. \"4:3\") or \"\" (e.g. " + "\"1.6\")." + }, { .longopt_id = OPT_CAMERA_ID, .longopt = "camera-id", @@ -2130,6 +2140,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_CAMERA_AR: + opts->camera_ar = optarg; + break; case OPT_CAMERA_ID: opts->camera_id = optarg; break; @@ -2245,9 +2258,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } - if (!opts->camera_size) { - LOGE("Camera size must be specified by --camera-size"); - return false; + if (opts->camera_size) { + if (opts->max_size) { + LOGE("Could not specify both --camera-size and -m/--max-size"); + return false; + } + + if (opts->camera_ar) { + LOGE("Could not specify both --camera-size and --camera-ar"); + return false; + } } if (opts->control) { @@ -2255,6 +2275,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->control = false; } } else if (opts->camera_id + || opts->camera_ar || opts->camera_facing != SC_CAMERA_FACING_ANY || opts->camera_size) { LOGE("Camera options are only available with --video-source=camera"); diff --git a/app/src/options.c b/app/src/options.c index 2adb4323..589a5a22 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -13,6 +13,7 @@ const struct scrcpy_options scrcpy_options_default = { .audio_encoder = NULL, .camera_id = NULL, .camera_size = NULL, + .camera_ar = NULL, .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, diff --git a/app/src/options.h b/app/src/options.h index 1e783bae..40f04670 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -132,6 +132,7 @@ struct scrcpy_options { const char *audio_encoder; const char *camera_id; const char *camera_size; + const char *camera_ar; enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 54e794e0..1f4c2a7c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -375,6 +375,7 @@ scrcpy(struct scrcpy_options *options) { .audio_encoder = options->audio_encoder, .camera_id = options->camera_id, .camera_size = options->camera_size, + .camera_ar = options->camera_ar, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index 7f8a4926..0c40bccb 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -77,6 +77,7 @@ sc_server_params_destroy(struct sc_server_params *params) { free((char *) params->audio_encoder); free((char *) params->tcpip_dst); free((char *) params->camera_id); + free((char *) params->camera_ar); } static bool @@ -105,6 +106,7 @@ sc_server_params_copy(struct sc_server_params *dst, COPY(audio_encoder); COPY(tcpip_dst); COPY(camera_id); + COPY(camera_ar); #undef COPY return true; @@ -303,6 +305,9 @@ execute_server(struct sc_server *server, ADD_PARAM("camera_facing=%s", sc_server_get_camera_facing_name(params->camera_facing)); } + if (params->camera_ar) { + ADD_PARAM("camera_ar=%s", params->camera_ar); + } if (params->show_touches) { ADD_PARAM("show_touches=true"); } diff --git a/app/src/server.h b/app/src/server.h index 786aea5c..71d22fe8 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -36,6 +36,7 @@ struct sc_server_params { const char *audio_encoder; const char *camera_id; const char *camera_size; + const char *camera_ar; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraAspectRatio.java b/server/src/main/java/com/genymobile/scrcpy/CameraAspectRatio.java new file mode 100644 index 00000000..4fdf4c74 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CameraAspectRatio.java @@ -0,0 +1,37 @@ +package com.genymobile.scrcpy; + +public final class CameraAspectRatio { + private static final float SENSOR = -1; + + private float ar; + + private CameraAspectRatio(float ar) { + this.ar = ar; + } + + public static CameraAspectRatio fromFloat(float ar) { + if (ar < 0) { + throw new IllegalArgumentException("Invalid aspect ratio: " + ar); + } + return new CameraAspectRatio(ar); + } + + public static CameraAspectRatio fromFraction(int w, int h) { + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("Invalid aspect ratio: " + w + ":" + h); + } + return new CameraAspectRatio((float) w / h); + } + + public static CameraAspectRatio sensorAspectRatio() { + return new CameraAspectRatio(SENSOR); + } + + public boolean isSensor() { + return ar == SENSOR; + } + + public float getAspectRatio() { + return ar; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index 949eb343..e4aba872 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -4,6 +4,7 @@ import com.genymobile.scrcpy.wrappers.ServiceManager; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; @@ -13,6 +14,8 @@ import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.MediaCodec; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; @@ -21,16 +24,23 @@ import android.view.Surface; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; public class CameraCapture extends SurfaceCapture { private final String explicitCameraId; private final CameraFacing cameraFacing; private final Size explicitSize; + private int maxSize; + private final CameraAspectRatio aspectRatio; + + private String cameraId; + private Size size; private HandlerThread cameraThread; private Handler cameraHandler; @@ -39,10 +49,12 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); - public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize) { + public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio) { this.explicitCameraId = explicitCameraId; this.cameraFacing = cameraFacing; this.explicitSize = explicitSize; + this.maxSize = maxSize; + this.aspectRatio = aspectRatio; } @Override @@ -53,11 +65,16 @@ public class CameraCapture extends SurfaceCapture { cameraExecutor = new HandlerExecutor(cameraHandler); try { - String cameraId = selectCamera(explicitCameraId, cameraFacing); + cameraId = selectCamera(explicitCameraId, cameraFacing); if (cameraId == null) { throw new IOException("No matching camera found"); } + size = selectSize(cameraId, explicitSize, maxSize, aspectRatio); + if (size == null) { + throw new IOException("Could not select camera size"); + } + Ln.i("Using camera '" + cameraId + "'"); cameraDevice = openCamera(cameraId); } catch (CameraAccessException | InterruptedException e) { @@ -91,6 +108,82 @@ public class CameraCapture extends SurfaceCapture { return null; } + @TargetApi(Build.VERSION_CODES.N) + private static Size selectSize(String cameraId, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio) throws CameraAccessException { + if (explicitSize != null) { + return explicitSize; + } + + CameraManager cameraManager = ServiceManager.getCameraManager(); + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + + StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); + Stream stream = Arrays.stream(sizes); + if (maxSize > 0) { + stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize); + } + + Float targetAspectRatio = resolveAspectRatio(aspectRatio, characteristics); + if (targetAspectRatio != null) { + stream = stream.filter(it -> { + float ar = ((float) it.getWidth() / it.getHeight()); + float arRatio = ar / targetAspectRatio; + // Accept if the aspect ratio is the target aspect ratio + or - 10% + return arRatio >= 0.9f && arRatio <= 1.1f; + }); + } + + Optional selected = stream.max((s1, s2) -> { + // Greater width is better + int cmp = Integer.compare(s1.getWidth(), s2.getWidth()); + if (cmp != 0) { + return cmp; + } + + if (targetAspectRatio != null) { + // Closer to the target aspect ratio is better + float ar1 = ((float) s1.getWidth() / s1.getHeight()); + float arRatio1 = ar1 / targetAspectRatio; + float distance1 = Math.abs(1 - arRatio1); + + float ar2 = ((float) s2.getWidth() / s2.getHeight()); + float arRatio2 = ar2 / targetAspectRatio; + float distance2 = Math.abs(1 - arRatio2); + + // Reverse the order because lower distance is better + cmp = Float.compare(distance2, distance1); + if (cmp != 0) { + return cmp; + } + } + + // Greater height is better + return Integer.compare(s1.getHeight(), s2.getHeight()); + }); + + if (selected.isPresent()) { + android.util.Size size = selected.get(); + return new Size(size.getWidth(), size.getHeight()); + } + + // Not found + return null; + } + + private static Float resolveAspectRatio(CameraAspectRatio ratio, CameraCharacteristics characteristics) { + if (ratio == null) { + return null; + } + + if (ratio.isSensor()) { + Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + return (float) activeSize.width() / activeSize.height(); + } + + return ratio.getAspectRatio(); + } + @Override public void start(Surface surface) throws IOException { try { @@ -114,12 +207,23 @@ public class CameraCapture extends SurfaceCapture { @Override public Size getSize() { - return explicitSize; + return size; } @Override public boolean setMaxSize(int maxSize) { - return false; + if (explicitSize != null) { + return false; + } + + this.maxSize = maxSize; + try { + size = selectSize(cameraId, null, maxSize, aspectRatio); + return size != null; + } catch (CameraAccessException e) { + Ln.w("Could not select camera size", e); + return false; + } } @SuppressLint("MissingPermission") diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 11af3cca..eec19e52 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -27,6 +27,7 @@ public class Options { private String cameraId; private Size cameraSize; private CameraFacing cameraFacing; + private CameraAspectRatio cameraAspectRatio; private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; @@ -131,6 +132,10 @@ public class Options { return cameraFacing; } + public CameraAspectRatio getCameraAspectRatio() { + return cameraAspectRatio; + } + public boolean getShowTouches() { return showTouches; } @@ -374,6 +379,11 @@ public class Options { options.cameraFacing = facing; } break; + case "camera_ar": + if (!value.isEmpty()) { + options.cameraAspectRatio = parseCameraAspectRatio(value); + } + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; @@ -427,4 +437,20 @@ public class Options { int height = Integer.parseInt(tokens[1]); return new Size(width, height); } + + private static CameraAspectRatio parseCameraAspectRatio(String ar) { + if ("sensor".equals(ar)) { + return CameraAspectRatio.sensorAspectRatio(); + } + + String[] tokens = ar.split(":"); + if (tokens.length == 2) { + int w = Integer.parseInt(tokens[0]); + int h = Integer.parseInt(tokens[1]); + return CameraAspectRatio.fromFraction(w, h); + } + + float floatAr = Float.parseFloat(tokens[0]); + return CameraAspectRatio.fromFloat(floatAr); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 1a93323a..4505a523 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -137,7 +137,8 @@ public final class Server { if (options.getVideoSource() == VideoSource.DISPLAY) { surfaceCapture = new ScreenCapture(device); } else { - surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize()); + surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), + options.getMaxSize(), options.getCameraAspectRatio()); } SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); From 9fc583548535ceb2d517ca522189d9814e9d2c13 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Mon, 14 Aug 2023 00:28:25 +0800 Subject: [PATCH 0982/1133] Fail-fast camera mirroring on Android 11 and older PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/Server.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4505a523..9789f7f2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -88,6 +88,12 @@ public final class Server { private static void scrcpy(Options options) throws IOException, ConfigurationException { Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && options.getVideoSource() == VideoSource.CAMERA) { + Ln.e("Camera mirroring is not supported before Android 12"); + throw new ConfigurationException("Camera mirroring is not supported"); + } + final Device device = new Device(options); Thread initThread = startInitThread(options); From 928f8b8eb39597141c12d248e74cb6cac672ff07 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 28 Oct 2023 15:19:04 +0200 Subject: [PATCH 0983/1133] Do not arbitrary limit --max-fps to 1000 Limit to the variable type size, for consistency. PR #4213 --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 69a918c3..df73edac 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1315,7 +1315,7 @@ parse_max_size(const char *s, uint16_t *max_size) { static bool parse_max_fps(const char *s, uint16_t *max_fps) { long value; - bool ok = parse_integer_arg(s, &value, false, 0, 1000, "max fps"); + bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max fps"); if (!ok) { return false; } From 4722bff42316c0ecef1d3ee69d1a327c3405d5b5 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Fri, 27 Oct 2023 20:20:19 -0400 Subject: [PATCH 0984/1133] Add --camera-fps Add a new option for specifying the camera frame rate. By default, Android's default frame rate (30 fps) is used. PR #4213 Signed-off-by: Andrew Gunnerson Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 2 ++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 +++++ app/src/cli.c | 27 +++++++++++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 3 +++ app/src/server.h | 1 + .../com/genymobile/scrcpy/CameraCapture.java | 10 ++++++- .../java/com/genymobile/scrcpy/LogUtils.java | 12 ++++++++- .../java/com/genymobile/scrcpy/Options.java | 8 ++++++ .../java/com/genymobile/scrcpy/Server.java | 2 +- 13 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index c8b6609e..9743e44a 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -13,6 +13,7 @@ _scrcpy() { --camera-ar= --camera-id= --camera-facing= + --camera-fps= --camera-size= --crop= -d --select-usb @@ -156,6 +157,7 @@ _scrcpy() { |--audio-output-buffer \ |--camera-ar \ |--camera-id \ + |--camera-fps \ |--camera-size \ |--crop \ |--display-id \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 823e6b9e..1ad96ad5 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -20,6 +20,7 @@ arguments=( '--camera-ar=[Select the camera size by its aspect ratio]' '--camera-id=[Specify the camera id to mirror]' '--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)' + '--camera-fps=[Specify the camera capture frame rate]' '--camera-size=[Specify an explicit camera capture size]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index c473adb5..e3b1b6f0 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -93,6 +93,12 @@ Select the device camera by its facing direction. Possible values are "front", "back" and "external". +.TP +.BI "\-\-camera\-fps " fps +Specify the camera capture frame rate. + +If not specified, Android's default frame rate (30 fps) is used. + .TP .BI "\-\-camera\-size " width\fRx\fIheight Specify an explicit camera capture size. diff --git a/app/src/cli.c b/app/src/cli.c index df73edac..b82d332d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -88,6 +88,7 @@ enum { OPT_CAMERA_SIZE, OPT_CAMERA_FACING, OPT_CAMERA_AR, + OPT_CAMERA_FPS, }; struct sc_option { @@ -234,6 +235,14 @@ static const struct sc_option options[] = { .argdesc = "x", .text = "Specify an explicit camera capture size.", }, + { + .longopt_id = OPT_CAMERA_FPS, + .longopt = "camera-fps", + .argdesc = "value", + .text = "Specify the camera capture frame rate.\n" + "If not specified, Android's default frame rate (30 fps) is " + "used.", + }, { // Not really deprecated (--codec has never been released), but without // declaring an explicit --codec option, getopt_long() partial matching @@ -1746,6 +1755,18 @@ parse_camera_facing(const char *optarg, enum sc_camera_facing *facing) { return false; } +static bool +parse_camera_fps(const char *s, uint16_t *camera_fps) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "camera fps"); + if (!ok) { + return false; + } + + *camera_fps = (uint16_t) value; + return true; +} + static bool parse_time_limit(const char *s, sc_tick *tick) { long value; @@ -2154,6 +2175,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_CAMERA_FPS: + if (!parse_camera_fps(optarg, &opts->camera_fps)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; @@ -2277,6 +2303,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } else if (opts->camera_id || opts->camera_ar || opts->camera_facing != SC_CAMERA_FACING_ANY + || opts->camera_fps || opts->camera_size) { LOGE("Camera options are only available with --video-source=camera"); return false; diff --git a/app/src/options.c b/app/src/options.c index 589a5a22..8601678b 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -14,6 +14,7 @@ const struct scrcpy_options scrcpy_options_default = { .camera_id = NULL, .camera_size = NULL, .camera_ar = NULL, + .camera_fps = 0, .log_level = SC_LOG_LEVEL_INFO, .video_codec = SC_CODEC_H264, .audio_codec = SC_CODEC_OPUS, diff --git a/app/src/options.h b/app/src/options.h index 40f04670..a712f443 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -133,6 +133,7 @@ struct scrcpy_options { const char *camera_id; const char *camera_size; const char *camera_ar; + uint16_t camera_fps; enum sc_log_level log_level; enum sc_codec video_codec; enum sc_codec audio_codec; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1f4c2a7c..64067cf6 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -376,6 +376,7 @@ scrcpy(struct scrcpy_options *options) { .camera_id = options->camera_id, .camera_size = options->camera_size, .camera_ar = options->camera_ar, + .camera_fps = options->camera_fps, .force_adb_forward = options->force_adb_forward, .power_off_on_close = options->power_off_on_close, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/server.c b/app/src/server.c index 0c40bccb..8a91952a 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -308,6 +308,9 @@ execute_server(struct sc_server *server, if (params->camera_ar) { ADD_PARAM("camera_ar=%s", params->camera_ar); } + if (params->camera_fps) { + ADD_PARAM("camera_fps=%" PRIu16, params->camera_fps); + } if (params->show_touches) { ADD_PARAM("show_touches=true"); } diff --git a/app/src/server.h b/app/src/server.h index 71d22fe8..ed1f307e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -37,6 +37,7 @@ struct sc_server_params { const char *camera_id; const char *camera_size; const char *camera_ar; + uint16_t camera_fps; struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index e4aba872..9edc600c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -19,6 +19,7 @@ import android.media.MediaCodec; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; +import android.util.Range; import android.view.Surface; import java.io.IOException; @@ -38,6 +39,7 @@ public class CameraCapture extends SurfaceCapture { private final Size explicitSize; private int maxSize; private final CameraAspectRatio aspectRatio; + private final int fps; private String cameraId; private Size size; @@ -49,12 +51,13 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); - public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio) { + public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps) { this.explicitCameraId = explicitCameraId; this.cameraFacing = cameraFacing; this.explicitSize = explicitSize; this.maxSize = maxSize; this.aspectRatio = aspectRatio; + this.fps = fps; } @Override @@ -304,6 +307,11 @@ public class CameraCapture extends SurfaceCapture { private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException { CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); requestBuilder.addTarget(surface); + + if (fps > 0) { + requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps)); + } + return requestBuilder.build(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 7806cf51..329f2570 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -9,8 +9,10 @@ import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.MediaCodec; +import android.util.Range; import java.util.List; +import java.util.TreeSet; public final class LogUtils { @@ -97,7 +99,15 @@ public final class LogUtils { builder.append(" (").append(getCameraFacingName(facing)).append(", "); Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - builder.append(activeSize.width()).append("x").append(activeSize.height()).append(')'); + builder.append(activeSize.width()).append("x").append(activeSize.height()).append(", "); + + // Capture frame rates for low-FPS mode are the same for every resolution + Range[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + TreeSet uniqueLowFps = new TreeSet<>(); + for (Range range : lowFpsRanges) { + uniqueLowFps.add(range.getUpper()); + } + builder.append("fps=").append(uniqueLowFps).append(')'); if (includeSizes) { StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index eec19e52..843fe9f1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -28,6 +28,7 @@ public class Options { private Size cameraSize; private CameraFacing cameraFacing; private CameraAspectRatio cameraAspectRatio; + private int cameraFps; private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; @@ -136,6 +137,10 @@ public class Options { return cameraAspectRatio; } + public int getCameraFps() { + return cameraFps; + } + public boolean getShowTouches() { return showTouches; } @@ -384,6 +389,9 @@ public class Options { options.cameraAspectRatio = parseCameraAspectRatio(value); } break; + case "camera_fps": + options.cameraFps = Integer.parseInt(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9789f7f2..ca72d584 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -144,7 +144,7 @@ public final class Server { surfaceCapture = new ScreenCapture(device); } else { surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), - options.getMaxSize(), options.getCameraAspectRatio()); + options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps()); } SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); From 6af4bd601f028d4e3d96eb90326a2330f03bc960 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Fri, 27 Oct 2023 20:20:19 -0400 Subject: [PATCH 0985/1133] Add support for high frame rate camera capture Add --camera-high-speed to enable high frame rate camera capture. If the option is enabled, then --camera-fps is mandatory. PR #4213 Co-authored-by: Romain Vimont Signed-off-by: Andrew Gunnerson Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 6 ++++ app/src/cli.c | 17 ++++++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 1 + app/src/server.c | 3 ++ app/src/server.h | 1 + .../com/genymobile/scrcpy/CameraCapture.java | 31 ++++++++++++++----- .../java/com/genymobile/scrcpy/LogUtils.java | 26 +++++++++++++--- .../java/com/genymobile/scrcpy/Options.java | 8 +++++ .../java/com/genymobile/scrcpy/Server.java | 2 +- 13 files changed, 86 insertions(+), 13 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 9743e44a..eaed88b7 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -14,6 +14,7 @@ _scrcpy() { --camera-id= --camera-facing= --camera-fps= + --camera-high-speed --camera-size= --crop= -d --select-usb diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 1ad96ad5..4b1e5868 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -18,6 +18,7 @@ arguments=( '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' '--camera-ar=[Select the camera size by its aspect ratio]' + '--camera-high-speed=[Enable high-speed camera capture mode]' '--camera-id=[Specify the camera id to mirror]' '--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)' '--camera-fps=[Specify the camera capture frame rate]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e3b1b6f0..2e5cbc60 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -81,6 +81,12 @@ Select the camera size by its aspect ratio (+/- 10%). Possible values are "sensor" (use the camera sensor aspect ratio), ":" (e.g. "4:3") and "" (e.g. "1.6"). +.TP +.B \-\-camera\-high\-speed +Enable high-speed camera capture mode. + +This mode is restricted to specific resolutions and frame rates, listed by --list-camera-sizes. + .TP .BI "\-\-camera\-id " id Specify the device camera id to mirror. diff --git a/app/src/cli.c b/app/src/cli.c index b82d332d..56b5cfb2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -89,6 +89,7 @@ enum { OPT_CAMERA_FACING, OPT_CAMERA_AR, OPT_CAMERA_FPS, + OPT_CAMERA_HIGH_SPEED, }; struct sc_option { @@ -229,6 +230,13 @@ static const struct sc_option options[] = { .text = "Select the device camera by its facing direction.\n" "Possible values are \"front\", \"back\" and \"external\".", }, + { + .longopt_id = OPT_CAMERA_HIGH_SPEED, + .longopt = "camera-high-speed", + .text = "Enable high-speed camera capture mode.\n" + "This mode is restricted to specific resolutions and frame " + "rates, listed by --list-camera-sizes.", + }, { .longopt_id = OPT_CAMERA_SIZE, .longopt = "camera-size", @@ -2180,6 +2188,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_CAMERA_HIGH_SPEED: + opts->camera_high_speed = true; + break; default: // getopt prints the error message on stderr return false; @@ -2296,6 +2307,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->camera_high_speed && !opts->camera_fps) { + LOGE("--camera-high-speed requires an explicit --camera-fps value"); + return false; + } + if (opts->control) { LOGI("Camera video source: control disabled"); opts->control = false; @@ -2304,6 +2320,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], || opts->camera_ar || opts->camera_facing != SC_CAMERA_FACING_ANY || opts->camera_fps + || opts->camera_high_speed || opts->camera_size) { LOGE("Camera options are only available with --video-source=camera"); return false; diff --git a/app/src/options.c b/app/src/options.c index 8601678b..6c72d767 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -86,5 +86,6 @@ const struct scrcpy_options scrcpy_options_default = { .audio = true, .require_audio = false, .kill_adb_on_close = false, + .camera_high_speed = false, .list = 0, }; diff --git a/app/src/options.h b/app/src/options.h index a712f443..18b437d8 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -199,6 +199,7 @@ struct scrcpy_options { bool audio; bool require_audio; bool kill_adb_on_close; + bool camera_high_speed; #define SC_OPTION_LIST_ENCODERS 0x1 #define SC_OPTION_LIST_DISPLAYS 0x2 #define SC_OPTION_LIST_CAMERAS 0x4 diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 64067cf6..1d0e90c1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -386,6 +386,7 @@ scrcpy(struct scrcpy_options *options) { .cleanup = options->cleanup, .power_on = options->power_on, .kill_adb_on_close = options->kill_adb_on_close, + .camera_high_speed = options->camera_high_speed, .list = options->list, }; diff --git a/app/src/server.c b/app/src/server.c index 8a91952a..2b3439da 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -311,6 +311,9 @@ execute_server(struct sc_server *server, if (params->camera_fps) { ADD_PARAM("camera_fps=%" PRIu16, params->camera_fps); } + if (params->camera_high_speed) { + ADD_PARAM("camera_high_speed=true"); + } if (params->show_touches) { ADD_PARAM("show_touches=true"); } diff --git a/app/src/server.h b/app/src/server.h index ed1f307e..062af0a9 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -63,6 +63,7 @@ struct sc_server_params { bool cleanup; bool power_on; bool kill_adb_on_close; + bool camera_high_speed; uint8_t list; }; diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index 9edc600c..b9da3658 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -8,6 +8,7 @@ import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; @@ -40,6 +41,7 @@ public class CameraCapture extends SurfaceCapture { private int maxSize; private final CameraAspectRatio aspectRatio; private final int fps; + private final boolean highSpeed; private String cameraId; private Size size; @@ -51,13 +53,15 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); - public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps) { + public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps, + boolean highSpeed) { this.explicitCameraId = explicitCameraId; this.cameraFacing = cameraFacing; this.explicitSize = explicitSize; this.maxSize = maxSize; this.aspectRatio = aspectRatio; this.fps = fps; + this.highSpeed = highSpeed; } @Override @@ -73,7 +77,7 @@ public class CameraCapture extends SurfaceCapture { throw new IOException("No matching camera found"); } - size = selectSize(cameraId, explicitSize, maxSize, aspectRatio); + size = selectSize(cameraId, explicitSize, maxSize, aspectRatio, highSpeed); if (size == null) { throw new IOException("Could not select camera size"); } @@ -112,7 +116,8 @@ public class CameraCapture extends SurfaceCapture { } @TargetApi(Build.VERSION_CODES.N) - private static Size selectSize(String cameraId, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio) throws CameraAccessException { + private static Size selectSize(String cameraId, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, boolean highSpeed) + throws CameraAccessException { if (explicitSize != null) { return explicitSize; } @@ -121,7 +126,7 @@ public class CameraCapture extends SurfaceCapture { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); + android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class); Stream stream = Arrays.stream(sizes); if (maxSize > 0) { stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize); @@ -221,7 +226,7 @@ public class CameraCapture extends SurfaceCapture { this.maxSize = maxSize; try { - size = selectSize(cameraId, null, maxSize, aspectRatio); + size = selectSize(cameraId, null, maxSize, aspectRatio, highSpeed); return size != null; } catch (CameraAccessException e) { Ln.w("Could not select camera size", e); @@ -282,7 +287,9 @@ public class CameraCapture extends SurfaceCapture { CompletableFuture future = new CompletableFuture<>(); OutputConfiguration outputConfig = new OutputConfiguration(surface); List outputs = Arrays.asList(outputConfig); - SessionConfiguration sessionConfig = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs, cameraExecutor, + + int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR; + SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { @@ -317,7 +324,7 @@ public class CameraCapture extends SurfaceCapture { @TargetApi(Build.VERSION_CODES.S) private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException, InterruptedException { - session.setRepeatingRequest(request, new CameraCaptureSession.CaptureCallback() { + CameraCaptureSession.CaptureCallback callback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) { // Called for each frame captured, do nothing @@ -327,7 +334,15 @@ public class CameraCapture extends SurfaceCapture { public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { Ln.w("Camera capture failed: frame " + failure.getFrameNumber()); } - }, cameraHandler); + }; + + if (highSpeed) { + CameraConstrainedHighSpeedCaptureSession highSpeedSession = (CameraConstrainedHighSpeedCaptureSession) session; + List requests = highSpeedSession.createHighSpeedRequestList(request); + highSpeedSession.setRepeatingBurst(requests, callback, cameraHandler); + } else { + session.setRepeatingRequest(request, callback, cameraHandler); + } } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 329f2570..8dc54629 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -12,6 +12,7 @@ import android.media.MediaCodec; import android.util.Range; import java.util.List; +import java.util.SortedSet; import java.util.TreeSet; public final class LogUtils { @@ -103,18 +104,27 @@ public final class LogUtils { // Capture frame rates for low-FPS mode are the same for every resolution Range[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); - TreeSet uniqueLowFps = new TreeSet<>(); - for (Range range : lowFpsRanges) { - uniqueLowFps.add(range.getUpper()); - } + SortedSet uniqueLowFps = getUniqueSet(lowFpsRanges); builder.append("fps=").append(uniqueLowFps).append(')'); if (includeSizes) { StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); for (android.util.Size size : sizes) { builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); } + + android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes(); + if (highSpeedSizes.length > 0) { + builder.append("\n High speed capture (--camera-high-speed):"); + for (android.util.Size size : highSpeedSizes) { + Range[] highFpsRanges = configs.getHighSpeedVideoFpsRanges(); + SortedSet uniqueHighFps = getUniqueSet(highFpsRanges); + builder.append("\n - ").append(size.getWidth()).append("x").append(size.getHeight()); + builder.append(" (fps=").append(uniqueHighFps).append(')'); + } + } } } } @@ -123,4 +133,12 @@ public final class LogUtils { } return builder.toString(); } + + private static SortedSet getUniqueSet(Range[] ranges) { + SortedSet set = new TreeSet<>(); + for (Range range : ranges) { + set.add(range.getUpper()); + } + return set; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 843fe9f1..9b1d8d8d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -29,6 +29,7 @@ public class Options { private CameraFacing cameraFacing; private CameraAspectRatio cameraAspectRatio; private int cameraFps; + private boolean cameraHighSpeed; private boolean showTouches; private boolean stayAwake; private List videoCodecOptions; @@ -141,6 +142,10 @@ public class Options { return cameraFps; } + public boolean getCameraHighSpeed() { + return cameraHighSpeed; + } + public boolean getShowTouches() { return showTouches; } @@ -392,6 +397,9 @@ public class Options { case "camera_fps": options.cameraFps = Integer.parseInt(value); break; + case "camera_high_speed": + options.cameraHighSpeed = Boolean.parseBoolean(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index ca72d584..a86b8130 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -144,7 +144,7 @@ public final class Server { surfaceCapture = new ScreenCapture(device); } else { surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), - options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps()); + options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed()); } SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); From 9bfc749803d79a7de5bcc46b3db376a12ef66e31 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 30 Oct 2023 23:27:11 +0100 Subject: [PATCH 0986/1133] Add camera documentation PR #4213 --- README.md | 6 +- doc/camera.md | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ doc/video.md | 9 +++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 doc/camera.md diff --git a/README.md b/README.md index 9f1ba6b4..10437b79 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,13 @@ It focuses on: [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 Its features include: - - [audio forwarding](doc/audio.md) (Android >= 11) + - [audio forwarding](doc/audio.md) (Android 11+) - [recording](doc/recording.md) - mirroring with [Android device screen off](doc/device.md#turn-screen-off) - [copy-paste](doc/control.md#copy-paste) in both directions - [configurable quality](doc/video.md) - - Android device screen [as a webcam (V4L2)](doc/v4l2.md) (Linux-only) + - [camera mirroring](doc/camera.md) (Android 12+) + - [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only) - [physical keyboard/mouse simulation (HID)](doc/hid-otg.md) - [OTG mode](doc/hid-otg.md#otg) - and more… @@ -77,6 +78,7 @@ documented in the following pages: - [Recording](doc/recording.md) - [Tunnels](doc/tunnels.md) - [HID/OTG](doc/hid-otg.md) + - [Camera](doc/camera.md) - [Video4Linux](doc/v4l2.md) - [Shortcuts](doc/shortcuts.md) diff --git a/doc/camera.md b/doc/camera.md new file mode 100644 index 00000000..d1008bda --- /dev/null +++ b/doc/camera.md @@ -0,0 +1,150 @@ +# Camera + +Camera mirroring is supported for devices with Android 12 or higher. + +To capture the camera instead of the device screen: + +``` +scrcpy --video-source=camera +``` + +By default, it automatically switches [audio source](audio.md#source) to +microphone (as if `--audio-source=mic` were also passed). + +```bash +scrcpy --video-source=display # default is --audio-source=output +scrcpy --video-source=camera # default is --audio-source=mic +scrcpy --video-source=display --audio-source=mic # force display AND microphone +scrcpy --video-source=camera --audio-source=output # force camera AND device audio output +``` + + +## List + +To list the cameras available (with their declared valid sizes and frame rates): + +``` +scrcpy --list-cameras +scrcpy --list-camera-sizes +``` + +_Note that the sizes and frame rates are declarative. They are not accurate on +all devices: some of them are declared but not supported, while some others are +not declared but supported._ + + +## Selection + +It is possible to pass an explicit camera id (as listed by `--list-cameras`): + +``` +scrcpy --video-source=camera --camera-id=0 +``` + +Alternatively, the camera may be selected automatically: + +```bash +scrcpy --video-source=camera # use the first camera +scrcpy --video-source=camera --camera-facing=front # use the first front camera +scrcpy --video-source=camera --camera-facing=back # use the first back camera +scrcpy --video-source=camera --camera-facing=external # use the first external camera +``` + +If `--camera-id` is specified, then `--camera-facing` is forbidden (the id +already determines the camera): + +```bash +scrcpy --video-source=camera --camera-id=0 --camera-facing=front # error +``` + + +### Size selection + +It is possible to pass an explicit camera size: + +``` +scrcpy --video-source=camera --camera-size=1920x1080 +``` + +The given size may be listed among the declared valid sizes +(`--list-camera-sizes`), but may also be anything else (some devices support +arbitrary sizes): + +``` +scrcpy --video-source=camera --camera-size=1840x444 +``` + +Alternatively, a declared valid size (among the ones listed by +`list-camera-sizes`) may be selected automatically. + +Two constraints are supported: + - `-m`/`--max-size` (already used for display mirroring), for example `-m1920`; + - `--camera-ar` to specify an aspect ratio (`:`, `` or + `sensor`). + +Some examples: + +```bash +scrcpy --video-source=camera # use the greatest width and the greatest associated height +scrcpy --video-source=camera -m1920 # use the greatest width not above 1920 and the greatest associated height +scrcpy --video-source=camera --camera-ar=4:3 # use the greatest size with an aspect ratio of 4:3 (+/- 10%) +scrcpy --video-source=camera --camera-ar=1.6 # use the greatest size with an aspect ratio of 1.6 (+/- 10%) +scrcpy --video-source=camera --camera-ar=sensor # use the greatest size with the aspect ratio of the camera sensor (+/- 10%) +scrcpy --video-source=camera -m1920 --camera-ar=16:9 # use the greatest width not above 1920 and the closest to 16:9 aspect ratio +``` + +If `--camera-size` is specified, then `-m`/`--max-size` and `--camera-ar` are +forbidden (the size is determined by the value given explicitly): + +```bash +scrcpy --video-source=camera --camera-size=1920x1080 -m3000 # error +``` + + +## Frame rate + +By default, camera is captured at Android's default frame rate (30 fps). + +To configure a different frame rate: + +``` +scrcpy --video-source=camera --camera-fps=60 +``` + + +## High speed capture + +The Android camera API also supports a [high speed capture mode][high speed]. + +This mode is restricted to specific resolutions and frame rates, listed by +`--list-camera-sizes`. + +``` +scrcpy --video-source=camera --camera-size=1920x1080 --camera-fps=240 +``` + +[high speed]: https://developer.android.com/reference/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession + + +## Brace expansion tip + +All camera options start with `--camera-`, so if your shell supports it, you can +benefit from [brace expansion] (for example, it is supported _bash_ and _zsh_): + +```bash +scrcpy --video-source=camera --camera-{facing=back,ar=16:9,high-speed,fps=120} +``` + +This will be expanded as: + +```bash +scrcpy --video-source=camera --camera-facing=back --camera-ar=16:9 --camera-high-speed --camera-fps=120 +``` + +[brace expansion]: https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html + + +## Webcam + +Combined with the [V4L2](v4l2.md) feature on Linux, the Android device camera +may be used as a webcam on the computer. diff --git a/doc/video.md b/doc/video.md index 5ce749f9..512e0aba 100644 --- a/doc/video.md +++ b/doc/video.md @@ -1,5 +1,14 @@ # Video +## Source + +By default, scrcpy mirrors the device screen. + +It is possible to capture the device camera instead. + +See the dedicated [camera](camera.md) page. + + ## Size By default, scrcpy attempts to mirror at the Android device resolution. From 55808034066b51f8e3c96f3a7e054898fb1f590e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Oct 2023 19:13:17 +0100 Subject: [PATCH 0987/1133] Always print device model and version Print the log before checking for --list-* options so that it is printed in all cases. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a86b8130..a8e8e36a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -87,8 +87,6 @@ public final class Server { } private static void scrcpy(Options options) throws IOException, ConfigurationException { - Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && options.getVideoSource() == VideoSource.CAMERA) { Ln.e("Camera mirroring is not supported before Android 12"); throw new ConfigurationException("Camera mirroring is not supported"); @@ -208,6 +206,8 @@ public final class Server { Ln.initLogLevel(options.getLogLevel()); + Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); + if (options.getList()) { if (options.getCleanup()) { CleanUp.unlinkSelf(); From 8350a619261d0349a7aea291cb9fe7f6b20078c8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Oct 2023 19:18:57 +0100 Subject: [PATCH 0988/1133] Simplify URLs in manpage The .UR-formatted URLs are not always rendered correctly. Use simple brackets instead. --- app/scrcpy.1 | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2e5cbc60..9af41528 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -45,9 +45,9 @@ Set a list of comma-separated key:type=value options for the device audio encode The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. -The list of possible codec options is available in the Android documentation -.UR https://d.android.com/reference/android/media/MediaFormat -.UE . +The list of possible codec options is available in the Android documentation: + + .TP .BI "\-\-audio\-encoder " name @@ -363,8 +363,7 @@ Request SDL to use the given render driver (this is just a hint). Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software". -.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER -.UE + .TP .B \-\-require\-audio @@ -458,9 +457,9 @@ Set a list of comma-separated key:type=value options for the device video encode The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'. -The list of possible codec options is available in the Android documentation -.UR https://d.android.com/reference/android/media/MediaFormat -.UE . +The list of possible codec options is available in the Android documentation: + + .TP .BI "\-\-video\-encoder " name @@ -659,23 +658,14 @@ for the Debian Project (and may be used by others). .SH "REPORTING BUGS" -Report bugs to -.UR https://github.com/Genymobile/scrcpy/issues -.UE . +Report bugs to . .SH COPYRIGHT -Copyright \(co 2018 Genymobile -.UR https://www.genymobile.com -Genymobile -.UE - -Copyright \(co 2018\-2023 -.MT rom@rom1v.com -Romain Vimont -.ME +Copyright \(co 2018 Genymobile + +Copyright \(co 2018\-2023 Romain Vimont Licensed under the Apache License, Version 2.0. .SH WWW -.UR https://github.com/Genymobile/scrcpy -.UE + From c64d1502024e3adaaf10761bd8d50149a113dcc3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 31 Oct 2023 19:20:59 +0100 Subject: [PATCH 0989/1133] Improve manpage formatting --- app/scrcpy.1 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 9af41528..2901d014 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -53,7 +53,7 @@ The list of possible codec options is available in the Android documentation: .BI "\-\-audio\-encoder " name Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR). -The available encoders can be listed by \-\-list\-encoders. +The available encoders can be listed by \fB\-\-list\-encoders\fR. .TP .BI "\-\-audio\-source " source @@ -79,19 +79,19 @@ Default is 8M (8000000). .BI "\-\-camera\-ar " ar Select the camera size by its aspect ratio (+/- 10%). -Possible values are "sensor" (use the camera sensor aspect ratio), ":" (e.g. "4:3") and "" (e.g. "1.6"). +Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6"). .TP .B \-\-camera\-high\-speed Enable high-speed camera capture mode. -This mode is restricted to specific resolutions and frame rates, listed by --list-camera-sizes. +This mode is restricted to specific resolutions and frame rates, listed by \fB\-\-list\-camera\-sizes\fR. .TP .BI "\-\-camera\-id " id Specify the device camera id to mirror. -The available camera ids can be listed by \-\-list\-cameras. +The available camera ids can be listed by \fB\-\-list\-cameras\fR. .TP .BI "\-\-camera\-facing " facing @@ -131,7 +131,7 @@ Disable screensaver while scrcpy is running. .BI "\-\-display\-id " id Specify the device display id to mirror. -The available display ids can be listed by \-\-list\-displays. +The available display ids can be listed by \fB\-\-list\-displays\fR. Default is 0. @@ -241,7 +241,7 @@ Disable device control (mirror the device in read\-only). .TP .B \-N, \-\-no\-playback -Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback). +Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video\-playback \-\-no\-audio\-playback\fR). .TP .B \-\-no\-audio @@ -411,13 +411,13 @@ Set the maximum mirroring time, in seconds. .TP .BI "\-\-tunnel\-host " ip -Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. +Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR. Default is localhost. .TP .BI "\-\-tunnel\-port " port -Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward. +Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR. Default is 0 (not forced): the local port used for establishing the tunnel will be used. @@ -465,7 +465,7 @@ The list of possible codec options is available in the Android documentation: .BI "\-\-video\-encoder " name Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR). -The available encoders can be listed by \-\-list\-encoders. +The available encoders can be listed by \fB\-\-list\-encoders\fR. .TP .BI "\-\-video\-source " source @@ -635,7 +635,7 @@ Path to adb. .TP .B ANDROID_SERIAL -Device serial to use if no selector (-s, -d, -e or --tcpip=) is specified. +Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified. .TP .B SCRCPY_ICON_PATH From b8c5853aa6ac9cfbe3fb4e46bf10978b3fa212e3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Nov 2023 10:36:28 +0100 Subject: [PATCH 0990/1133] Disable default stdout/stderr Some devices (mostly Xiaomi) print internal errors using e.printStackTrace(), flooding the console with irrelevant errors. Disable system streams used via System.out and System.err streams, to print only the logs from scrcpy. Refs #994 Refs #4213 --- .../main/java/com/genymobile/scrcpy/Ln.java | 45 ++++++++++++++++--- .../java/com/genymobile/scrcpy/Server.java | 3 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Ln.java b/server/src/main/java/com/genymobile/scrcpy/Ln.java index 199c29be..cdd57b9f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/Ln.java @@ -2,6 +2,11 @@ package com.genymobile.scrcpy; import android.util.Log; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + /** * Log both to Android logger (so that logs are visible in "adb logcat") and standard output/error (so that they are visible in the terminal * directly). @@ -11,6 +16,9 @@ public final class Ln { private static final String TAG = "scrcpy"; private static final String PREFIX = "[server] "; + private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out)); + private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err)); + enum Level { VERBOSE, DEBUG, INFO, WARN, ERROR } @@ -21,6 +29,12 @@ public final class Ln { // not instantiable } + public static void disableSystemStreams() { + PrintStream nullStream = new PrintStream(new NullOutputStream()); + System.setOut(nullStream); + System.setErr(nullStream); + } + /** * Initialize the log level. *

@@ -39,30 +53,30 @@ public final class Ln { public static void v(String message) { if (isEnabled(Level.VERBOSE)) { Log.v(TAG, message); - System.out.print(PREFIX + "VERBOSE: " + message + '\n'); + CONSOLE_OUT.print(PREFIX + "VERBOSE: " + message + '\n'); } } public static void d(String message) { if (isEnabled(Level.DEBUG)) { Log.d(TAG, message); - System.out.print(PREFIX + "DEBUG: " + message + '\n'); + CONSOLE_OUT.print(PREFIX + "DEBUG: " + message + '\n'); } } public static void i(String message) { if (isEnabled(Level.INFO)) { Log.i(TAG, message); - System.out.print(PREFIX + "INFO: " + message + '\n'); + CONSOLE_OUT.print(PREFIX + "INFO: " + message + '\n'); } } public static void w(String message, Throwable throwable) { if (isEnabled(Level.WARN)) { Log.w(TAG, message, throwable); - System.err.print(PREFIX + "WARN: " + message + '\n'); + CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n'); if (throwable != null) { - throwable.printStackTrace(); + throwable.printStackTrace(CONSOLE_ERR); } } } @@ -74,9 +88,9 @@ public final class Ln { public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); - System.err.print(PREFIX + "ERROR: " + message + "\n"); + CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n'); if (throwable != null) { - throwable.printStackTrace(); + throwable.printStackTrace(CONSOLE_ERR); } } } @@ -84,4 +98,21 @@ public final class Ln { public static void e(String message) { e(message, null); } + + static class NullOutputStream extends OutputStream { + @Override + public void write(byte[] b) { + // ignore + } + + @Override + public void write(byte[] b, int off, int len) { + // ignore + } + + @Override + public void write(int b) { + // ignore + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a8e8e36a..0126f396 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -187,7 +187,7 @@ public final class Server { try { internalMain(args); } catch (Throwable t) { - t.printStackTrace(); + Ln.e(t.getMessage(), t); status = 1; } finally { // By default, the Java process exits when all non-daemon threads are terminated. @@ -204,6 +204,7 @@ public final class Server { Options options = Options.parse(args); + Ln.disableSystemStreams(); Ln.initLogLevel(options.getLogLevel()); Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")"); From ff579990c21d73653aa74a3d8f2ad75b90504657 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Nov 2023 14:52:38 +0100 Subject: [PATCH 0991/1133] Shutdown connection before joining threads Interrupting async processors may require to shutdown the connection to wake up blocking calls. Therefore, shutdown the connection first, then join the threads, then close the connection. Refs commit 9c08eb79cb7941848882cb908cefee9933450de5 --- .../com/genymobile/scrcpy/DesktopConnection.java | 15 ++++++++++++--- .../main/java/com/genymobile/scrcpy/Server.java | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index c3408fff..8bc743f8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -132,20 +132,29 @@ public final class DesktopConnection implements Closeable { return controlSocket; } - public void close() throws IOException { + public void shutdown() throws IOException { if (videoSocket != null) { videoSocket.shutdownInput(); videoSocket.shutdownOutput(); - videoSocket.close(); } if (audioSocket != null) { audioSocket.shutdownInput(); audioSocket.shutdownOutput(); - audioSocket.close(); } if (controlSocket != null) { controlSocket.shutdownInput(); controlSocket.shutdownOutput(); + } + } + + public void close() throws IOException { + if (videoSocket != null) { + videoSocket.close(); + } + if (audioSocket != null) { + audioSocket.close(); + } + if (controlSocket != null) { controlSocket.close(); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 0126f396..a1c6090b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -163,6 +163,8 @@ public final class Server { asyncProcessor.stop(); } + connection.shutdown(); + try { initThread.join(); for (AsyncProcessor asyncProcessor : asyncProcessors) { From a8db3ec9e2ed3540bf33c2d0ad173a37613b5d8c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 29 Oct 2023 00:03:28 +0200 Subject: [PATCH 0992/1133] Upgrade platform-tools (34.0.5) for Windows Include the latest version of adb in Windows releases. --- app/prebuilt-deps/prepare-adb.sh | 6 +++--- release.mk | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh index f22873c0..4fb6fd7d 100755 --- a/app/prebuilt-deps/prepare-adb.sh +++ b/app/prebuilt-deps/prepare-adb.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=platform-tools-34.0.3 +DEP_DIR=platform-tools-34.0.5 -FILENAME=platform-tools_r34.0.3-windows.zip -SHA256SUM=fce992e93eb786fc9f47df93d83a7b912c46742d45c39d712c02e06d05b72e2b +FILENAME=platform-tools_r34.0.5-windows.zip +SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571 if [[ -d "$DEP_DIR" ]] then diff --git a/release.mk b/release.mk index 4fe99c89..8da4e528 100644 --- a/release.mk +++ b/release.mk @@ -98,9 +98,9 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -116,9 +116,9 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 855ae4adb1a55c23aca2ce9cb1a17cd6bf1ef03d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 29 Oct 2023 00:05:59 +0200 Subject: [PATCH 0993/1133] Upgrade SDL (2.28.4) for Windows Include the latest version of SDL in Windows releases. --- app/prebuilt-deps/prepare-sdl.sh | 6 +++--- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index b691aac5..645646de 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,10 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.28.0 +DEP_DIR=SDL2-2.28.4 -FILENAME=SDL2-devel-2.28.0-mingw.tar.gz -SHA256SUM=b91ce59eeacd4a9db403f976fd2337d9360b21ada374124417d716065c380e20 +FILENAME=SDL2-devel-2.28.4-mingw.tar.gz +SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index c3f72540..109bdd27 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32' -prebuilt_sdl2 = 'SDL2-2.28.0/i686-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 66cddf83..70e105ab 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -17,5 +17,5 @@ endian = 'little' [properties] prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64' -prebuilt_sdl2 = 'SDL2-2.28.0/x86_64-w64-mingw32' +prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 8da4e528..258017bc 100644 --- a/release.mk +++ b/release.mk @@ -101,7 +101,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 @@ -119,7 +119,7 @@ dist-win64: build-server build-win64 cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 From c3c7bf7af329c8a5fd9f0310eae3b4e0a5f86207 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Nov 2023 18:36:33 +0100 Subject: [PATCH 0994/1133] Bump version to v2.2 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index e3929119..832817d8 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.1.1" + VALUE "ProductVersion", "v2.2" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index ed3f42bf..d1f67e38 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.1.1', + version: 'v2.2', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 4a05d2a5..bee6509b 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 33 - versionCode 20101 - versionName "2.1.1" + versionCode 200 + versionName "v2.2" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 543f12ab..6e755272 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.1.1 +SCRCPY_VERSION_NAME=v2.2 PLATFORM=${ANDROID_PLATFORM:-33} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} From 446ea818a44eb75f03f21d08d307801b20ca2871 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 1 Nov 2023 18:47:58 +0100 Subject: [PATCH 0995/1133] Update links to v2.2 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 10437b79..b8ef9df8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v2.1.1) +# scrcpy (v2.2) scrcpy diff --git a/doc/build.md b/doc/build.md index d65cdc93..54b7410b 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.1.1`][direct-scrcpy-server] - SHA-256: `9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e` + - [`scrcpy-server-v2.2`][direct-scrcpy-server] + SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index 7525334d..bd4a69f7 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.1.1.zip`][direct-win64] (64-bit) - SHA-256: `f77281e1bce2f9934617699c581f063d5b327f012eff602ee98fb2ef550c25c2` - - [`scrcpy-win32-v2.1.1.zip`][direct-win32] (32-bit) - SHA-256: `ef7ae7fbe9449f2643febdc2244fb186d1a746a3c736394150cfd14f06d3c943` + - [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit) + SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd` + - [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit) + SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win64-v2.1.1.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win32-v2.1.1.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 24a3197b..adad85f7 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 -PREBUILT_SERVER_SHA256=9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2 +PREBUILT_SERVER_SHA256=c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 8c3e2bae7be47a0f17ddec8da25b89e0aea2617e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Nov 2023 19:02:58 +0100 Subject: [PATCH 0996/1133] Simplify Application instantiation The constructor is public. --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index b8ee68ca..e8da9540 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -143,7 +143,7 @@ public final class Workarounds { try { fillActivityThread(); - Application app = Application.class.newInstance(); + Application app = new Application(); Field baseField = ContextWrapper.class.getDeclaredField("mBase"); baseField.setAccessible(true); baseField.set(app, FakeContext.get()); From 85a0b935c9d70a7f082eb3df5f3c9f61ea48009a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Nov 2023 19:01:08 +0100 Subject: [PATCH 0997/1133] Always assign a system context as base context FakeContext used ActivityThread.getSystemContext() as base context only in some cases, because it caused problems on some devices: - warnings on Xiaomi devices [1], which are now fixed by b8c5853aa6ac9cfbe3fb4e46bf10978b3fa212e3 - issues related to Looper [2], which are solved by just calling Looper.prepare*() Therefore, we can now always assign a base context, which simplifies and helps to solve camera issues on some devices (#4392). [1] [2] Fixes #4392 --- .../com/genymobile/scrcpy/FakeContext.java | 6 +- .../com/genymobile/scrcpy/Workarounds.java | 71 ++++++++----------- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 6501d4cf..520e0378 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -2,11 +2,11 @@ package com.genymobile.scrcpy; import android.annotation.TargetApi; import android.content.AttributionSource; -import android.content.MutableContextWrapper; +import android.content.ContextWrapper; import android.os.Build; import android.os.Process; -public final class FakeContext extends MutableContextWrapper { +public final class FakeContext extends ContextWrapper { public static final String PACKAGE_NAME = "com.android.shell"; public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29 @@ -18,7 +18,7 @@ public final class FakeContext extends MutableContextWrapper { } private FakeContext() { - super(null); + super(Workarounds.getSystemContext()); } @Override diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index e8da9540..5b3a5c8c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -21,18 +21,34 @@ import java.lang.reflect.Method; public final class Workarounds { - private static Class activityThreadClass; - private static Object activityThread; + private static final Class ACTIVITY_THREAD_CLASS; + private static final Object ACTIVITY_THREAD; + + static { + prepareMainLooper(); + + try { + // ActivityThread activityThread = new ActivityThread(); + ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread"); + Constructor activityThreadConstructor = ACTIVITY_THREAD_CLASS.getDeclaredConstructor(); + activityThreadConstructor.setAccessible(true); + ACTIVITY_THREAD = activityThreadConstructor.newInstance(); + + // ActivityThread.sCurrentActivityThread = activityThread; + Field sCurrentActivityThreadField = ACTIVITY_THREAD_CLASS.getDeclaredField("sCurrentActivityThread"); + sCurrentActivityThreadField.setAccessible(true); + sCurrentActivityThreadField.set(null, ACTIVITY_THREAD); + } catch (Exception e) { + throw new AssertionError(e); + } + } private Workarounds() { // not instantiable } public static void apply(boolean audio, boolean camera) { - Workarounds.prepareMainLooper(); - boolean mustFillAppInfo = false; - boolean mustFillBaseContext = false; boolean mustFillAppContext = false; if (Build.BRAND.equalsIgnoreCase("meizu")) { @@ -53,7 +69,6 @@ public final class Workarounds { // - // - mustFillAppInfo = true; - mustFillBaseContext = true; mustFillAppContext = true; } @@ -66,15 +81,11 @@ public final class Workarounds { if (camera) { mustFillAppInfo = true; - mustFillBaseContext = true; } if (mustFillAppInfo) { Workarounds.fillAppInfo(); } - if (mustFillBaseContext) { - Workarounds.fillBaseContext(); - } if (mustFillAppContext) { Workarounds.fillAppContext(); } @@ -93,27 +104,9 @@ public final class Workarounds { Looper.prepareMainLooper(); } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") - private static void fillActivityThread() throws Exception { - if (activityThread == null) { - // ActivityThread activityThread = new ActivityThread(); - activityThreadClass = Class.forName("android.app.ActivityThread"); - Constructor activityThreadConstructor = activityThreadClass.getDeclaredConstructor(); - activityThreadConstructor.setAccessible(true); - activityThread = activityThreadConstructor.newInstance(); - - // ActivityThread.sCurrentActivityThread = activityThread; - Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); - sCurrentActivityThreadField.setAccessible(true); - sCurrentActivityThreadField.set(null, activityThread); - } - } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppInfo() { try { - fillActivityThread(); - // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); Class appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData"); Constructor appBindDataConstructor = appBindDataClass.getDeclaredConstructor(); @@ -129,9 +122,9 @@ public final class Workarounds { appInfoField.set(appBindData, applicationInfo); // activityThread.mBoundApplication = appBindData; - Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); + Field mBoundApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mBoundApplication"); mBoundApplicationField.setAccessible(true); - mBoundApplicationField.set(activityThread, appBindData); + mBoundApplicationField.set(ACTIVITY_THREAD, appBindData); } catch (Throwable throwable) { // this is a workaround, so failing is not an error Ln.d("Could not fill app info: " + throwable.getMessage()); @@ -141,33 +134,29 @@ public final class Workarounds { @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppContext() { try { - fillActivityThread(); - Application app = new Application(); Field baseField = ContextWrapper.class.getDeclaredField("mBase"); baseField.setAccessible(true); baseField.set(app, FakeContext.get()); // activityThread.mInitialApplication = app; - Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication"); + Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication"); mInitialApplicationField.setAccessible(true); - mInitialApplicationField.set(activityThread, app); + mInitialApplicationField.set(ACTIVITY_THREAD, app); } catch (Throwable throwable) { // this is a workaround, so failing is not an error Ln.d("Could not fill app context: " + throwable.getMessage()); } } - private static void fillBaseContext() { + static Context getSystemContext() { try { - fillActivityThread(); - - Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext"); - Context context = (Context) getSystemContextMethod.invoke(activityThread); - FakeContext.get().setBaseContext(context); + Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext"); + return (Context) getSystemContextMethod.invoke(ACTIVITY_THREAD); } catch (Throwable throwable) { // this is a workaround, so failing is not an error - Ln.d("Could not fill base context: " + throwable.getMessage()); + Ln.d("Could not get system context: " + throwable.getMessage()); + return null; } } From 8d76b3e06dcd4b996ef20779feaf4cd8494a4a5c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Nov 2023 19:07:08 +0100 Subject: [PATCH 0998/1133] Fill application context for camera Using the camera fails on some devices without a proper application context. Fixes #4392 --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 5b3a5c8c..77827c47 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -81,6 +81,7 @@ public final class Workarounds { if (camera) { mustFillAppInfo = true; + mustFillAppContext = true; } if (mustFillAppInfo) { From 4e4ddc499fcf571109126fbe3722eb0cc60fe1b0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 3 Nov 2023 19:07:15 +0100 Subject: [PATCH 0999/1133] Return the FakeContext as application context This avoids getApplicationContext() to return null and cause NullPointerException. Fixes #4392 --- server/src/main/java/com/genymobile/scrcpy/FakeContext.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 520e0378..2ea7bf4a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; import android.annotation.TargetApi; import android.content.AttributionSource; +import android.content.Context; import android.content.ContextWrapper; import android.os.Build; import android.os.Process; @@ -44,4 +45,9 @@ public final class FakeContext extends ContextWrapper { public int getDeviceId() { return 0; } + + @Override + public Context getApplicationContext() { + return this; + } } From ccaa832f48d0454986777d9521e1028ec0d3eb35 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 5 Nov 2023 11:51:32 +0100 Subject: [PATCH 1000/1133] Simplify --list-cameras output Remove --video-source=camera from the output of --list-cameras (this is implicit). --- server/src/main/java/com/genymobile/scrcpy/LogUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 8dc54629..70140525 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -93,7 +93,7 @@ public final class LogUtils { builder.append("\n (none)"); } else { for (String id : cameraIds) { - builder.append("\n --video-source=camera --camera-id=").append(id); + builder.append("\n --camera-id=").append(id); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); int facing = characteristics.get(CameraCharacteristics.LENS_FACING); From 11d738321f8661a46c5f211ec4285047657177cb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 5 Nov 2023 15:12:54 +0100 Subject: [PATCH 1001/1133] Recover on invalid camera FPS ranges Some devices may provide invalid ranges, causing an IllegalArgumentException "lower must be less than or equal to upper". Catch the exception to list the cameras anyway. Refs #4403 --- .../java/com/genymobile/scrcpy/LogUtils.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 70140525..efa0672b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -100,12 +100,19 @@ public final class LogUtils { builder.append(" (").append(getCameraFacingName(facing)).append(", "); Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - builder.append(activeSize.width()).append("x").append(activeSize.height()).append(", "); + builder.append(activeSize.width()).append("x").append(activeSize.height()); + + try { + // Capture frame rates for low-FPS mode are the same for every resolution + Range[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); + SortedSet uniqueLowFps = getUniqueSet(lowFpsRanges); + builder.append(", fps=").append(uniqueLowFps); + } catch (Exception e) { + // Some devices may provide invalid ranges, causing an IllegalArgumentException "lower must be less than or equal to upper" + Ln.w("Could not get available frame rates for camera " + id, e); + } - // Capture frame rates for low-FPS mode are the same for every resolution - Range[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); - SortedSet uniqueLowFps = getUniqueSet(lowFpsRanges); - builder.append("fps=").append(uniqueLowFps).append(')'); + builder.append(')'); if (includeSizes) { StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); From 3c456253242ed96796f71b2ab3dc66e2bedf6ea3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Nov 2023 10:54:13 +0100 Subject: [PATCH 1002/1133] Log recording RAW audio codec as error It is not possible to record with a RAW audio codec, so the log before exiting should be an error rather than a warning. --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index 56b5cfb2..462465fa 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2353,7 +2353,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } if (opts->audio_codec == SC_CODEC_RAW) { - LOGW("Recording does not support RAW audio codec"); + LOGE("Recording does not support RAW audio codec"); return false; } From 9d5f53caa76151e0983700e4ae6ccb3a445e1379 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Nov 2023 11:04:21 +0100 Subject: [PATCH 1003/1133] Stop capture on any RAW audio error The server was stopped only if an IOException occurred during RAW audio capture, but it did not catch RuntimeExceptions. --- .../src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 7d2adade..6108c54b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -62,8 +62,8 @@ public final class AudioRawRecorder implements AsyncProcessor { record(); } catch (AudioCaptureForegroundException e) { // Do not print stack trace, a user-friendly error-message has already been logged - } catch (IOException e) { - Ln.e("Audio recording error", e); + } catch (Throwable t) { + Ln.e("Audio recording error", t); fatalError = true; } finally { Ln.d("Audio recorder stopped"); From 420d3a40ddea61be80ef1e7026fa2edb22f66a41 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Nov 2023 11:09:47 +0100 Subject: [PATCH 1004/1133] Fix error handling in raw audio recorder It is incorret to ever call: streamer.writeDisableStream(...); after: streamer.writeAudioHeader(); Move the try-catch block so that it can never happen. --- .../java/com/genymobile/scrcpy/AudioRawRecorder.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index 6108c54b..fdac8b3a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -32,7 +32,13 @@ public final class AudioRawRecorder implements AsyncProcessor { final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); try { - capture.start(); + try { + capture.start(); + } catch (Throwable t) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(false); + throw t; + } streamer.writeAudioHeader(); while (!Thread.currentThread().isInterrupted()) { @@ -45,10 +51,6 @@ public final class AudioRawRecorder implements AsyncProcessor { streamer.writePacket(buffer, bufferInfo); } - } catch (Throwable e) { - // Notify the client that the audio could not be captured - streamer.writeDisableStream(false); - throw e; } finally { capture.stop(); } From 4eb33054cda35043983b57eb37395cbdac8724eb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Nov 2023 11:14:01 +0100 Subject: [PATCH 1005/1133] Do not log EPIPE on close for raw audio Handle EPIPE the same way in AudioRawRecorder as in AudioEncoder. This prevents useless errors on close. --- .../main/java/com/genymobile/scrcpy/AudioRawRecorder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index fdac8b3a..ce33ae85 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -51,6 +51,11 @@ public final class AudioRawRecorder implements AsyncProcessor { streamer.writePacket(buffer, bufferInfo); } + } catch (IOException e) { + // Broken pipe is expected on close, because the socket is closed by the client + if (!IO.isBrokenPipe(e)) { + Ln.e("Audio capture error", e); + } } finally { capture.stop(); } From 5e59ed31352251791679e5931d7e5abf0c2d18f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 Nov 2023 11:34:31 +0100 Subject: [PATCH 1006/1133] Always initialize SDL with the video subsystem Clipboard synchronization requires SDL_INIT_VIDEO, so always initialize the video subsystem, even if --no-video or --no-video-playback is passed. Refs caf594c90ef1b71ed844b2a9b42c3b3371215d6f Fixes #4418 --- app/src/scrcpy.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1d0e90c1..ac2b8e33 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -417,10 +417,14 @@ scrcpy(struct scrcpy_options *options) { if (options->video_playback) { sdl_set_hints(options->render_driver); - if (SDL_Init(SDL_INIT_VIDEO)) { - LOGE("Could not initialize SDL video: %s", SDL_GetError()); - goto end; - } + } + + // Initialize the video subsystem even if --no-video or --no-video-playback + // is passed so that clipboard synchronization still works. + // + if (SDL_Init(SDL_INIT_VIDEO)) { + LOGE("Could not initialize SDL video: %s", SDL_GetError()); + goto end; } if (options->audio_playback) { From e637feba51c2eac6de27ebb318a2f7a1aa54a62d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 09:08:24 +0100 Subject: [PATCH 1007/1133] Update muxers documentation Recording now supports formats other than mp4 and mkv. --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 4 ++-- app/src/cli.c | 4 ++-- doc/recording.md | 9 +++++---- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index eaed88b7..08ca29db 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -125,7 +125,7 @@ _scrcpy() { return ;; --record-format) - COMPREPLY=($(compgen -W 'mkv mp4' -- "$cur")) + COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac' -- "$cur")) return ;; --render-driver) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 4b1e5868..31706224 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -65,7 +65,7 @@ arguments=( '--push-target=[Set the target directory for pushing files to the device by drag and drop]' {-r,--record=}'[Record screen to file]:record file:_files' '--raw-key-events[Inject key events for all input keys, and ignore text events]' - '--record-format=[Force recording format]:format:(mp4 mkv)' + '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2901d014..e72cf617 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -347,7 +347,7 @@ Record screen to The format is determined by the .B \-\-record\-format -option if set, or by the file extension (.mp4 or .mkv). +option if set, or by the file extension. .TP .B \-\-raw\-key\-events @@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events. .TP .BI "\-\-record\-format " format -Force recording format (either mp4 or mkv). +Force recording format (mp4, mkv, m4a, mka, opus or aac). .TP .BI "\-\-render\-driver " name diff --git a/app/src/cli.c b/app/src/cli.c index 462465fa..078ce315 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -583,7 +583,7 @@ static const struct sc_option options[] = { .argdesc = "file.mp4", .text = "Record screen to file.\n" "The format is determined by the --record-format option if " - "set, or by the file extension (.mp4 or .mkv).", + "set, or by the file extension.", }, { .longopt_id = OPT_RAW_KEY_EVENTS, @@ -594,7 +594,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", - .text = "Force recording format (either mp4 or mkv).", + .text = "Force recording format (mp4, mkv, m4a, mka, opus or aac).", }, { .longopt_id = OPT_RENDER_DRIVER, diff --git a/doc/recording.md b/doc/recording.md index 76a7efd6..d844b368 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -31,14 +31,15 @@ course, not if you capture your scrcpy window and audio output on the computer). ## Format The video and audio streams are encoded on the device, but are muxed on the -client side. Two formats (containers) are supported: - - Matroska (`.mkv`) - - MP4 (`.mp4`) +client side. Several formats (containers) are supported: + - MP4 (`.mp4`, `.m4a`, `.aac`) + - Matroska (`.mkv`, `.mka`) + - OPUS (`.opus`) The container is automatically selected based on the filename. It is also possible to explicitly select a container (in that case the filename -needs not end with `.mkv` or `.mp4`): +needs not end with a known extension): ``` scrcpy --record=file --record-format=mkv From 80defdd8aa29a89bc656df0f2cdc8a1474f95741 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Nov 2023 12:01:10 +0100 Subject: [PATCH 1008/1133] Suppress private APIs lints to Workarounds class The whole class need them (including the static block). --- server/src/main/java/com/genymobile/scrcpy/Workarounds.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 77827c47..db9c9629 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -19,6 +19,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +@SuppressLint("PrivateApi,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi") public final class Workarounds { private static final Class ACTIVITY_THREAD_CLASS; @@ -105,7 +106,6 @@ public final class Workarounds { Looper.prepareMainLooper(); } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppInfo() { try { // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); @@ -132,7 +132,6 @@ public final class Workarounds { } } - @SuppressLint("PrivateApi,DiscouragedPrivateApi") private static void fillAppContext() { try { Application app = new Application(); @@ -162,7 +161,7 @@ public final class Workarounds { } @TargetApi(Build.VERSION_CODES.R) - @SuppressLint("WrongConstant,MissingPermission,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi") + @SuppressLint("WrongConstant,MissingPermission") public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) { // Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment. // From 783719c72e6659e20c48fd57171ac957df5a148b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Nov 2023 12:48:56 +0100 Subject: [PATCH 1009/1133] Fix OPUS packet in an endian-independent way Reading the header id as an int assumed that the current endianness was little endian. Read to a byte array to remove this assumption. --- .../src/main/java/com/genymobile/scrcpy/Streamer.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index 39f74fb6..c3f1c6ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -5,14 +5,13 @@ import android.media.MediaCodec; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; public final class Streamer { private static final long PACKET_FLAG_CONFIG = 1L << 63; private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; - private static final long AOPUSHDR = 0x5244485355504F41L; // "AOPUSHDR" in ASCII (little-endian) - private final FileDescriptor fd; private final Codec codec; private final boolean sendCodecMeta; @@ -120,11 +119,14 @@ public final class Streamer { throw new IOException("Not enough data in OPUS config packet"); } - long id = buffer.getLong(); - if (id != AOPUSHDR) { + final byte[] opusHeaderId = {'A', 'O', 'P', 'U', 'S', 'H', 'D', 'R'}; + byte[] idBuffer = new byte[8]; + buffer.get(idBuffer); + if (!Arrays.equals(idBuffer, opusHeaderId)) { throw new IOException("OPUS header not found"); } + // The size is in native byte-order long sizeLong = buffer.getLong(); if (sizeLong < 0 || sizeLong >= 0x7FFFFFFF) { throw new IOException("Invalid block size in OPUS header: " + sizeLong); From f23be823fded5090792deccdb2b892074208e9d3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Nov 2023 17:45:11 +0100 Subject: [PATCH 1010/1133] Upgrade FFmpeg build to 6.1-scrcpy Upgrade to FFmpeg 6.1, and with FLAC support enabled. --- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 16 ++++++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index 9019cc2d..12accb40 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.0-scrcpy-4 +VERSION=6.1-scrcpy DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=39274b321491ce83e76cab5d24e7cbe3f402d3ccf382f739b13be5651c146b60 +SHA256SUM=b41726e603f4624bb9ed7d2836e3e59d9d20b000e22a9ebd27055f4e99e48219 if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index 109bdd27..ef8d52ab 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32' +prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win32' prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 70e105ab..4e39773d 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64' +prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win64' prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 258017bc..00498418 100644 --- a/release.mk +++ b/release.mk @@ -94,10 +94,10 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -112,10 +112,10 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 4857c5dd5964eccd2c8f772ae570332d12f4f825 Mon Sep 17 00:00:00 2001 From: megapro17 Date: Tue, 7 Nov 2023 15:09:47 +0300 Subject: [PATCH 1011/1133] Add support for FLAC audio codec PR #4410 <#https://github.com/Genymobile/scrcpy/pull/4410> Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 4 +- app/data/zsh-completion/_scrcpy | 4 +- app/scrcpy.1 | 4 +- app/src/cli.c | 24 ++++++++-- app/src/demuxer.c | 10 +++- app/src/options.h | 5 +- app/src/recorder.c | 2 + app/src/server.c | 2 + doc/audio.md | 12 ++++- doc/recording.md | 4 +- .../com/genymobile/scrcpy/AudioCodec.java | 1 + .../java/com/genymobile/scrcpy/Streamer.java | 47 ++++++++++++++++++- 12 files changed, 103 insertions(+), 16 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 08ca29db..9d51fb18 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -97,7 +97,7 @@ _scrcpy() { return ;; --audio-codec) - COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) + COMPREPLY=($(compgen -W 'opus aac flac raw' -- "$cur")) return ;; --video-source) @@ -125,7 +125,7 @@ _scrcpy() { return ;; --record-format) - COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac' -- "$cur")) + COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac' -- "$cur")) return ;; --render-driver) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 31706224..c59ac669 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -11,7 +11,7 @@ arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' '--audio-buffer=[Configure the audio buffering delay (in milliseconds)]' - '--audio-codec=[Select the audio codec]:codec:(opus aac raw)' + '--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]' '--audio-source=[Select the audio source]:source:(output mic)' @@ -65,7 +65,7 @@ arguments=( '--push-target=[Set the target directory for pushing files to the device by drag and drop]' {-r,--record=}'[Record screen to file]:record file:_files' '--raw-key-events[Inject key events for all input keys, and ignore text events]' - '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac)' + '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index e72cf617..cfcfb227 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -35,7 +35,7 @@ Default is 50. .TP .BI "\-\-audio\-codec " name -Select an audio codec (opus, aac or raw). +Select an audio codec (opus, aac, flac or raw). Default is opus. @@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events. .TP .BI "\-\-record\-format " format -Force recording format (mp4, mkv, m4a, mka, opus or aac). +Force recording format (mp4, mkv, m4a, mka, opus, aac or flac). .TP .BI "\-\-render\-driver " name diff --git a/app/src/cli.c b/app/src/cli.c index 078ce315..edb546fa 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -152,7 +152,7 @@ static const struct sc_option options[] = { .longopt_id = OPT_AUDIO_CODEC, .longopt = "audio-codec", .argdesc = "name", - .text = "Select an audio codec (opus, aac or raw).\n" + .text = "Select an audio codec (opus, aac, flac or raw).\n" "Default is opus.", }, { @@ -594,7 +594,8 @@ static const struct sc_option options[] = { .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", - .text = "Force recording format (mp4, mkv, m4a, mka, opus or aac).", + .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac or " + "flac).", }, { .longopt_id = OPT_RENDER_DRIVER, @@ -1626,6 +1627,9 @@ get_record_format(const char *name) { if (!strcmp(name, "aac")) { return SC_RECORD_FORMAT_AAC; } + if (!strcmp(name, "flac")) { + return SC_RECORD_FORMAT_FLAC; + } return 0; } @@ -1695,11 +1699,15 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_AAC; return true; } + if (!strcmp(optarg, "flac")) { + *codec = SC_CODEC_FLAC; + return true; + } if (!strcmp(optarg, "raw")) { *codec = SC_CODEC_RAW; return true; } - LOGE("Unsupported audio codec: %s (expected opus, aac or raw)", optarg); + LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)", optarg); return false; } @@ -2376,6 +2384,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "(try with --audio-codec=aac)"); return false; } + if (opts->record_format == SC_RECORD_FORMAT_FLAC + && opts->audio_codec != SC_CODEC_FLAC) { + LOGE("Recording to FLAC file requires a FLAC audio stream " + "(try with --audio-codec=flac)"); + return false; + } + } + + if (opts->audio_codec == SC_CODEC_FLAC && opts->audio_bit_rate) { + LOGW("--audio-bit-rate is ignored for FLAC audio codec"); } if (opts->audio_codec == SC_CODEC_RAW) { diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 943f72b6..c9ee8f3c 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -25,7 +25,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { #define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII #define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII #define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII -#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII" +#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac" in ASCII +#define SC_CODEC_ID_FLAC UINT32_C(0x666c6163) // "flac" in ASCII #define SC_CODEC_ID_RAW UINT32_C(0x00726177) // "raw" in ASCII switch (codec_id) { case SC_CODEC_ID_H264: @@ -43,6 +44,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) { return AV_CODEC_ID_OPUS; case SC_CODEC_ID_AAC: return AV_CODEC_ID_AAC; + case SC_CODEC_ID_FLAC: + return AV_CODEC_ID_FLAC; case SC_CODEC_ID_RAW: return AV_CODEC_ID_PCM_S16LE; default: @@ -207,6 +210,11 @@ run_demuxer(void *data) { codec_ctx->channels = 2; #endif codec_ctx->sample_rate = 48000; + + if (raw_codec_id == SC_CODEC_ID_FLAC) { + // The sample_fmt is not set by the FLAC decoder + codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; + } } if (avcodec_open2(codec_ctx, codec, NULL) < 0) { diff --git a/app/src/options.h b/app/src/options.h index 18b437d8..91433894 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -25,6 +25,7 @@ enum sc_record_format { SC_RECORD_FORMAT_MKA, SC_RECORD_FORMAT_OPUS, SC_RECORD_FORMAT_AAC, + SC_RECORD_FORMAT_FLAC, }; static inline bool @@ -32,7 +33,8 @@ sc_record_format_is_audio_only(enum sc_record_format fmt) { return fmt == SC_RECORD_FORMAT_M4A || fmt == SC_RECORD_FORMAT_MKA || fmt == SC_RECORD_FORMAT_OPUS - || fmt == SC_RECORD_FORMAT_AAC; + || fmt == SC_RECORD_FORMAT_AAC + || fmt == SC_RECORD_FORMAT_FLAC; } enum sc_codec { @@ -41,6 +43,7 @@ enum sc_codec { SC_CODEC_AV1, SC_CODEC_OPUS, SC_CODEC_AAC, + SC_CODEC_FLAC, SC_CODEC_RAW, }; diff --git a/app/src/recorder.c b/app/src/recorder.c index 23c8b497..d13b122a 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -69,6 +69,8 @@ sc_recorder_get_format_name(enum sc_record_format format) { return "matroska"; case SC_RECORD_FORMAT_OPUS: return "opus"; + case SC_RECORD_FORMAT_FLAC: + return "flac"; default: return NULL; } diff --git a/app/src/server.c b/app/src/server.c index 2b3439da..d4726c2a 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -178,6 +178,8 @@ sc_server_get_codec_name(enum sc_codec codec) { return "opus"; case SC_CODEC_AAC: return "aac"; + case SC_CODEC_FLAC: + return "flac"; case SC_CODEC_RAW: return "raw"; default: diff --git a/doc/audio.md b/doc/audio.md index cb6cde95..ecae4468 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -62,12 +62,13 @@ scrcpy --audio-source=mic --no-video --no-playback --record=file.opus ## Codec -The audio codec can be selected. The possible values are `opus` (default), `aac` -and `raw` (uncompressed PCM 16-bit LE): +The audio codec can be selected. The possible values are `opus` (default), +`aac`, `flac` and `raw` (uncompressed PCM 16-bit LE): ```bash scrcpy --audio-codec=opus # default scrcpy --audio-codec=aac +scrcpy --audio-codec=flac scrcpy --audio-codec=raw ``` @@ -80,7 +81,14 @@ then your device has no Opus encoder: try `scrcpy --audio-codec=aac`. For advanced usage, to pass arbitrary parameters to the [`MediaFormat`], check `--audio-codec-options` in the manpage or in `scrcpy --help`. +For example, to change the [FLAC compression level]: + +```bash +scrcpy --audio-codec=flac --audio-codec-options=flac-compression-level=8 +``` + [`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat +[FLAC compression level]: https://developer.android.com/reference/android/media/MediaFormat#KEY_FLAC_COMPRESSION_LEVEL ## Encoder diff --git a/doc/recording.md b/doc/recording.md index d844b368..466cf542 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -18,7 +18,8 @@ To record only the audio: ```bash scrcpy --no-video --record=file.opus scrcpy --no-video --audio-codec=aac --record=file.aac -# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac +scrcpy --no-video --audio-codec=flac --record=file.flac +# .m4a/.mp4 and .mka/.mkv are also supported for opus, aac and flac ``` Timestamps are captured on the device, so [packet delay variation] does not @@ -35,6 +36,7 @@ client side. Several formats (containers) are supported: - MP4 (`.mp4`, `.m4a`, `.aac`) - Matroska (`.mkv`, `.mka`) - OPUS (`.opus`) + - FLAC (`.flac`) The container is automatically selected based on the filename. diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java index 1f3b07a0..b4ea3680 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCodec.java @@ -5,6 +5,7 @@ import android.media.MediaFormat; public enum AudioCodec implements Codec { OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS), AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC), + FLAC(0x66_6c_61_63, "flac", MediaFormat.MIMETYPE_AUDIO_FLAC), RAW(0x00_72_61_77, "raw", MediaFormat.MIMETYPE_AUDIO_RAW); private final int id; // 4-byte ASCII representation of the name diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index c3f1c6ee..8b6c9dcc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -5,6 +5,7 @@ import android.media.MediaCodec; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Arrays; public final class Streamer { @@ -29,6 +30,7 @@ public final class Streamer { public Codec getCodec() { return codec; } + public void writeAudioHeader() throws IOException { if (sendCodecMeta) { ByteBuffer buffer = ByteBuffer.allocate(4); @@ -61,8 +63,12 @@ public final class Streamer { } public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { - if (config && codec == AudioCodec.OPUS) { - fixOpusConfigPacket(buffer); + if (config) { + if (codec == AudioCodec.OPUS) { + fixOpusConfigPacket(buffer); + } else if (codec == AudioCodec.FLAC) { + fixFlacConfigPacket(buffer); + } } if (sendFrameMeta) { @@ -140,4 +146,41 @@ public final class Streamer { // Set the buffer to point to the OPUS header slice buffer.limit(buffer.position() + size); } + + private static void fixFlacConfigPacket(ByteBuffer buffer) throws IOException { + // 00000000 66 4c 61 43 00 00 00 22 |fLaC..." | + // -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA ------------------- + // 00000000 10 00 10 00 00 00 00 00 | ........| + // 00000010 00 00 0b b8 02 f0 00 00 00 00 00 00 00 00 00 00 |................| + // 00000020 00 00 00 00 00 00 00 00 00 00 |.......... | + // ------------------------------------------------------------------------------ + // 00000020 84 00 00 28 20 00 | ...( .| + // 00000030 00 00 72 65 66 65 72 65 6e 63 65 20 6c 69 62 46 |..reference libF| + // 00000040 4c 41 43 20 31 2e 33 2e 32 20 32 30 32 32 31 30 |LAC 1.3.2 202210| + // 00000050 32 32 00 00 00 00 |22....| + // + // + + if (buffer.remaining() < 8) { + throw new IOException("Not enough data in FLAC config packet"); + } + + final byte[] flacHeaderId = {'f', 'L', 'a', 'C'}; + byte[] idBuffer = new byte[4]; + buffer.get(idBuffer); + if (!Arrays.equals(idBuffer, flacHeaderId)) { + throw new IOException("FLAC header not found"); + } + + // The size is in big-endian + buffer.order(ByteOrder.BIG_ENDIAN); + + int size = buffer.getInt(); + if (buffer.remaining() < size) { + throw new IOException("Not enough data in FLAC header (invalid size: " + size + ")"); + } + + // Set the buffer to point to the FLAC header slice + buffer.limit(buffer.position() + size); + } } From 258eaaae2af9c1e41611f8f570bd46fe10165859 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 Nov 2023 18:34:04 +0100 Subject: [PATCH 1012/1133] Increase default audio buffer for FLAC FLAC is not low latency: the default encoder produces blocks of 4096 samples, which represent ~85.333ms. Increase the audio buffer by default so that audio playback works. --- app/src/cli.c | 13 +++++++++++++ app/src/options.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index edb546fa..0482b233 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2265,6 +2265,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->require_audio = true; } + if (opts->audio_playback && opts->audio_buffer == -1) { + if (opts->audio_codec == SC_CODEC_FLAC) { + // Use 50 ms audio buffer by default, but use a higher value for FLAC, + // which is not low latency (the default encoder produces blocks of + // 4096 samples, which represent ~85.333ms). + LOGI("FLAC audio: audio buffer increased to 120 ms (use " + "--audio-buffer to set a custom value)"); + opts->audio_buffer = SC_TICK_FROM_MS(120); + } else { + opts->audio_buffer = SC_TICK_FROM_MS(50); + } + } + #ifdef HAVE_V4L2 if (v4l2) { if (opts->lock_video_orientation == diff --git a/app/src/options.c b/app/src/options.c index 6c72d767..092fbd56 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -46,7 +46,7 @@ const struct scrcpy_options scrcpy_options_default = { .window_height = 0, .display_id = 0, .display_buffer = 0, - .audio_buffer = SC_TICK_FROM_MS(50), + .audio_buffer = -1, // depends on the audio format, .audio_output_buffer = SC_TICK_FROM_MS(5), .time_limit = 0, #ifdef HAVE_V4L2 From 3bb6b0cb9f4a67046bd96b53bef46e34818ae0f3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 08:49:10 +0100 Subject: [PATCH 1013/1133] Read audio by blocks of 1024 samples In practice, the system captures audio samples by blocks of 1024 samples. Remplace the hardcoded value of 5 milliseconds (240 samples), and let AudioRecord fill the input buffer provided by MediaCodec (or by AudioRawRecorder), with a maximum size of 1024 samples (just in case). --- .../java/com/genymobile/scrcpy/AudioCapture.java | 13 +++++++------ .../java/com/genymobile/scrcpy/AudioEncoder.java | 5 +---- .../com/genymobile/scrcpy/AudioRawRecorder.java | 7 ++----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 5575ffb6..e94b49ed 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -24,6 +24,11 @@ public final class AudioCapture { public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int BYTES_PER_SAMPLE = 2; + // Never read more than 1024 samples, even if the buffer is bigger (that would increase latency). + // A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we + // receive 4 successive blocks without waiting, then we wait for the 4 next ones). + public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE; + private final int audioSource; private AudioRecord recorder; @@ -36,10 +41,6 @@ public final class AudioCapture { this.audioSource = audioSource.value(); } - public static int millisToBytes(int millis) { - return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000; - } - private static AudioFormat createAudioFormat() { AudioFormat.Builder builder = new AudioFormat.Builder(); builder.setEncoding(ENCODING); @@ -135,8 +136,8 @@ public final class AudioCapture { } @TargetApi(Build.VERSION_CODES.N) - public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) { - int r = recorder.read(directBuffer, size); + public int read(ByteBuffer directBuffer, MediaCodec.BufferInfo outBufferInfo) { + int r = recorder.read(directBuffer, MAX_READ_SIZE); if (r <= 0) { return r; } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index bec79b05..ad8d0422 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -37,9 +37,6 @@ public final class AudioEncoder implements AsyncProcessor { private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE; private static final int CHANNELS = AudioCapture.CHANNELS; - private static final int READ_MS = 5; // milliseconds - private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); - private final AudioCapture capture; private final Streamer streamer; private final int bitRate; @@ -93,7 +90,7 @@ public final class AudioEncoder implements AsyncProcessor { while (!Thread.currentThread().isInterrupted()) { InputTask task = inputTasks.take(); ByteBuffer buffer = mediaCodec.getInputBuffer(task.index); - int r = capture.read(buffer, READ_SIZE, bufferInfo); + int r = capture.read(buffer, bufferInfo); if (r <= 0) { throw new IOException("Could not read audio: " + r); } diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java index ce33ae85..7e052f32 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioRawRecorder.java @@ -13,9 +13,6 @@ public final class AudioRawRecorder implements AsyncProcessor { private Thread thread; - private static final int READ_MS = 5; // milliseconds - private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); - public AudioRawRecorder(AudioCapture capture, Streamer streamer) { this.capture = capture; this.streamer = streamer; @@ -28,7 +25,7 @@ public final class AudioRawRecorder implements AsyncProcessor { return; } - final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); + final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioCapture.MAX_READ_SIZE); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); try { @@ -43,7 +40,7 @@ public final class AudioRawRecorder implements AsyncProcessor { streamer.writeAudioHeader(); while (!Thread.currentThread().isInterrupted()) { buffer.position(0); - int r = capture.read(buffer, READ_SIZE, bufferInfo); + int r = capture.read(buffer, bufferInfo); if (r < 0) { throw new IOException("Could not read audio: " + r); } From a402eac7f293b1f63ce1c0a9449cf3a997dc061e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 09:15:34 +0100 Subject: [PATCH 1014/1133] Compute PTS of intermediate blocks If several reads are performed for a single captured audio block (e.g. if the read size is smaller than the captured block), then the provided timestamp was the same for all packets. Recompute the timestamp for each of them. --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index e94b49ed..c05bb41d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -34,6 +34,7 @@ public final class AudioCapture { private AudioRecord recorder; private final AudioTimestamp timestamp = new AudioTimestamp(); + private long previousRecorderTimestamp = -1; private long previousPts = 0; private long nextPts = 0; @@ -145,8 +146,9 @@ public final class AudioCapture { long pts; int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC); - if (ret == AudioRecord.SUCCESS) { + if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) { pts = timestamp.nanoTime / 1000; + previousRecorderTimestamp = timestamp.nanoTime; } else { if (nextPts == 0) { Ln.w("Could not get any audio timestamp"); From 4b4f045e196fe037a841f7004eaabb09cf571942 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 09:40:42 +0100 Subject: [PATCH 1015/1133] Fix audio PTS by the duration of 1 sample If the difference of PTS between two consecutive blocks of audio is less than 1 sample, then it will be considered as non-increasing by FFmpeg muxers having a time_base of 1/sample_rate. Increase the PTS by 1 sample instead. --- .../src/main/java/com/genymobile/scrcpy/AudioCapture.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index c05bb41d..e3de50e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -29,6 +29,8 @@ public final class AudioCapture { // receive 4 successive blocks without waiting, then we wait for the 4 next ones). public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE; + private static final long ONE_SAMPLE_US = (1000000 + SAMPLE_RATE - 1) / SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS) + private final int audioSource; private AudioRecord recorder; @@ -160,13 +162,13 @@ public final class AudioCapture { long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); nextPts = pts + durationUs; - if (previousPts != 0 && pts < previousPts) { + if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { // Audio PTS may come from two sources: // - recorder.getTimestamp() if the call works; // - an estimation from the previous PTS and the packet size as a fallback. // // Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it. - pts = previousPts + 1; + pts = previousPts + ONE_SAMPLE_US; } previousPts = pts; From 1713422c13265946a15b25e9de5762727a33edfb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 09:30:42 +0100 Subject: [PATCH 1016/1133] Upgrade FFmpeg build to 6.1-scrcpy-2 Use a build with WAV muxer. --- app/prebuilt-deps/prepare-ffmpeg.sh | 4 ++-- cross_win32.txt | 2 +- cross_win64.txt | 2 +- release.mk | 16 ++++++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index 12accb40..96ea3ee7 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.1-scrcpy +VERSION=6.1-scrcpy-2 DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=b41726e603f4624bb9ed7d2836e3e59d9d20b000e22a9ebd27055f4e99e48219 +SHA256SUM=7f25f638dc24a0f5d4af07a088b6a604cf33548900bbfd2f6ce0bae050b7664d if [[ -d "$DEP_DIR" ]] then diff --git a/cross_win32.txt b/cross_win32.txt index ef8d52ab..e24f3722 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -16,6 +16,6 @@ cpu = 'i686' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win32' +prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win32' prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 4e39773d..39e79944 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -16,6 +16,6 @@ cpu = 'x86_64' endian = 'little' [properties] -prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy/win64' +prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win64' prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32' prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 00498418..57fa994e 100644 --- a/release.mk +++ b/release.mk @@ -94,10 +94,10 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -112,10 +112,10 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 200488111e9f54585c67f915265094f7f22e8888 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 13 Nov 2023 09:35:18 +0100 Subject: [PATCH 1017/1133] Add support for RAW audio (WAV) recording RAW audio forwarding was supported but not for recording. Add support for recording a raw audio stream to a `.wav` file (and `.mkv`). --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 26 +++++++++++++++++++------- app/src/options.h | 4 +++- app/src/recorder.c | 18 ++++++++++++++---- app/src/recorder.h | 2 ++ doc/recording.md | 2 ++ 8 files changed, 43 insertions(+), 15 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 9d51fb18..97dbfe70 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -125,7 +125,7 @@ _scrcpy() { return ;; --record-format) - COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac' -- "$cur")) + COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac wav' -- "$cur")) return ;; --render-driver) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index c59ac669..4b8a7737 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -65,7 +65,7 @@ arguments=( '--push-target=[Set the target directory for pushing files to the device by drag and drop]' {-r,--record=}'[Record screen to file]:record file:_files' '--raw-key-events[Inject key events for all input keys, and ignore text events]' - '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac)' + '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index cfcfb227..26f53ba4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -355,7 +355,7 @@ Inject key events for all input keys, and ignore text events. .TP .BI "\-\-record\-format " format -Force recording format (mp4, mkv, m4a, mka, opus, aac or flac). +Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav). .TP .BI "\-\-render\-driver " name diff --git a/app/src/cli.c b/app/src/cli.c index 0482b233..19097f47 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -594,8 +594,8 @@ static const struct sc_option options[] = { .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", - .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac or " - "flac).", + .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac " + "or wav).", }, { .longopt_id = OPT_RENDER_DRIVER, @@ -1630,6 +1630,9 @@ get_record_format(const char *name) { if (!strcmp(name, "flac")) { return SC_RECORD_FORMAT_FLAC; } + if (!strcmp(name, "wav")) { + return SC_RECORD_FORMAT_WAV; + } return 0; } @@ -2373,11 +2376,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } - if (opts->audio_codec == SC_CODEC_RAW) { - LOGE("Recording does not support RAW audio codec"); - return false; - } - if (opts->video && sc_record_format_is_audio_only(opts->record_format)) { LOGE("Audio container does not support video stream"); @@ -2403,6 +2401,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "(try with --audio-codec=flac)"); return false; } + + if (opts->record_format == SC_RECORD_FORMAT_WAV + && opts->audio_codec != SC_CODEC_RAW) { + LOGE("Recording to WAV file requires a RAW audio stream " + "(try with --audio-codec=raw)"); + return false; + } + + if ((opts->record_format == SC_RECORD_FORMAT_MP4 || + opts->record_format == SC_RECORD_FORMAT_M4A) + && opts->audio_codec == SC_CODEC_RAW) { + LOGE("Recording to MP4 container does not support RAW audio"); + return false; + } } if (opts->audio_codec == SC_CODEC_FLAC && opts->audio_bit_rate) { diff --git a/app/src/options.h b/app/src/options.h index 91433894..c702ceeb 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -26,6 +26,7 @@ enum sc_record_format { SC_RECORD_FORMAT_OPUS, SC_RECORD_FORMAT_AAC, SC_RECORD_FORMAT_FLAC, + SC_RECORD_FORMAT_WAV, }; static inline bool @@ -34,7 +35,8 @@ sc_record_format_is_audio_only(enum sc_record_format fmt) { || fmt == SC_RECORD_FORMAT_MKA || fmt == SC_RECORD_FORMAT_OPUS || fmt == SC_RECORD_FORMAT_AAC - || fmt == SC_RECORD_FORMAT_FLAC; + || fmt == SC_RECORD_FORMAT_FLAC + || fmt == SC_RECORD_FORMAT_WAV; } enum sc_codec { diff --git a/app/src/recorder.c b/app/src/recorder.c index d13b122a..8794442b 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -71,6 +71,8 @@ sc_recorder_get_format_name(enum sc_record_format format) { return "opus"; case SC_RECORD_FORMAT_FLAC: return "flac"; + case SC_RECORD_FORMAT_WAV: + return "wav"; default: return NULL; } @@ -168,13 +170,14 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { } static inline bool -sc_recorder_has_empty_queues(struct sc_recorder *recorder) { +sc_recorder_must_wait_for_config_packets(struct sc_recorder *recorder) { if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { // The video queue is empty return true; } - if (recorder->audio && sc_vecdeque_is_empty(&recorder->audio_queue)) { + if (recorder->audio && recorder->audio_expects_config_packet + && sc_vecdeque_is_empty(&recorder->audio_queue)) { // The audio queue is empty (when audio is enabled) return true; } @@ -190,7 +193,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { while (!recorder->stopped && ((recorder->video && !recorder->video_init) || (recorder->audio && !recorder->audio_init) - || sc_recorder_has_empty_queues(recorder))) { + || sc_recorder_must_wait_for_config_packets(recorder))) { sc_cond_wait(&recorder->cond, &recorder->mutex); } @@ -209,7 +212,8 @@ sc_recorder_process_header(struct sc_recorder *recorder) { } AVPacket *audio_pkt = NULL; - if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { + if (recorder->audio_expects_config_packet && + !sc_vecdeque_is_empty(&recorder->audio_queue)) { assert(recorder->audio); audio_pkt = sc_vecdeque_pop(&recorder->audio_queue); } @@ -597,6 +601,10 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, recorder->audio_stream.index = stream->index; + // A config packet is provided for all supported formats except raw audio + recorder->audio_expects_config_packet = + ctx->codec_id != AV_CODEC_ID_PCM_S16LE; + recorder->audio_init = true; sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); @@ -709,6 +717,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_init = false; recorder->audio_init = false; + recorder->audio_expects_config_packet = false; + sc_recorder_stream_init(&recorder->video_stream); sc_recorder_stream_init(&recorder->audio_stream); diff --git a/app/src/recorder.h b/app/src/recorder.h index 47fd3f21..16327584 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -50,6 +50,8 @@ struct sc_recorder { bool video_init; bool audio_init; + bool audio_expects_config_packet; + struct sc_recorder_stream video_stream; struct sc_recorder_stream audio_stream; diff --git a/doc/recording.md b/doc/recording.md index 466cf542..c1a8445e 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -19,6 +19,7 @@ To record only the audio: scrcpy --no-video --record=file.opus scrcpy --no-video --audio-codec=aac --record=file.aac scrcpy --no-video --audio-codec=flac --record=file.flac +scrcpy --no-video --audio-codec=raw --record=file.wav # .m4a/.mp4 and .mka/.mkv are also supported for opus, aac and flac ``` @@ -37,6 +38,7 @@ client side. Several formats (containers) are supported: - Matroska (`.mkv`, `.mka`) - OPUS (`.opus`) - FLAC (`.flac`) + - WAV (`.wav`) The container is automatically selected based on the filename. From 15a3bad4abca691d6459821bbcaf4c4f52a52f28 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 Nov 2023 09:33:59 +0100 Subject: [PATCH 1018/1133] Log PTS fixing at debug level Audio PTS are retrieved by AudioRecord.getTimestamp(), so they do not necessarily exactly match the number of samples (this allows to take drift and lag into account). As a consequence, two consecutive timestamps in microseconds may sometimes end up within the same millisecond, causing the warning. This is particularly true for the Matroska muxer which uses a timebase of 1/1000 (1 ms precision). Since this is "expected", lower the log level from warning to debug. --- app/src/recorder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/recorder.c b/app/src/recorder.c index 8794442b..c9d5f131 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -105,7 +105,7 @@ sc_recorder_write_stream(struct sc_recorder *recorder, AVStream *stream = recorder->ctx->streams[st->index]; sc_recorder_rescale_packet(stream, packet); if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) { - LOGW("Fixing PTS non monotonically increasing in stream %d " + LOGD("Fixing PTS non monotonically increasing in stream %d " "(%" PRIi64 " >= %" PRIi64 ")", st->index, st->last_pts, packet->pts); packet->pts = ++st->last_pts; From 86808e811424560c7ed188b9fd4213c3838b0724 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Nov 2023 20:59:02 +0100 Subject: [PATCH 1019/1133] Upgrade Android checkstyle to 10.12.5 Upgrade to the latest version. --- config/android-checkstyle.gradle | 2 +- server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/android-checkstyle.gradle b/config/android-checkstyle.gradle index 29c67b19..1e5ce3ba 100644 --- a/config/android-checkstyle.gradle +++ b/config/android-checkstyle.gradle @@ -2,7 +2,7 @@ apply plugin: 'checkstyle' check.dependsOn 'checkstyle' checkstyle { - toolVersion = '9.0.1' + toolVersion = '10.12.5' } task checkstyle(type: Checkstyle) { diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index ad8d0422..0b59369b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -295,7 +295,7 @@ public final class AudioEncoder implements AsyncProcessor { } } - private class EncoderCallback extends MediaCodec.Callback { + private final class EncoderCallback extends MediaCodec.Callback { @TargetApi(Build.VERSION_CODES.N) @Override public void onInputBufferAvailable(MediaCodec codec, int index) { From e8801cc3c0493f72ece976f2b1d3a3bdef8237ac Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Nov 2023 21:05:47 +0100 Subject: [PATCH 1020/1133] Upgrade AGP (8.1.3) and Gradle to 8.4 Android Gradle Plugin 8.1.3. Gradle 8.4. From now on, Java 17 is required. --- build.gradle | 6 +----- doc/build.md | 10 +++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- server/build.gradle | 6 +++++- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index f7e29b22..b27befb6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' + classpath 'com.android.tools.build:gradle:8.1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -23,7 +23,3 @@ allprojects { options.compilerArgs << "-Xlint:deprecation" } } - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/doc/build.md b/doc/build.md index 54b7410b..15c567b5 100644 --- a/doc/build.md +++ b/doc/build.md @@ -58,7 +58,7 @@ sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \ libswresample-dev libusb-1.0-0-dev # server build dependencies -sudo apt install openjdk-11-jdk +sudo apt install openjdk-17-jdk ``` On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install @@ -100,7 +100,7 @@ sudo apt install mingw-w64 mingw-w64-tools You also need the JDK to build the server: ```bash -sudo apt install openjdk-11-jdk +sudo apt install openjdk-17-jdk ``` Then generate the releases: @@ -168,13 +168,13 @@ brew install sdl2 ffmpeg libusb brew install pkg-config meson ``` -Additionally, if you want to build the server, install Java 8 from Caskroom, and +Additionally, if you want to build the server, install Java 17 from Caskroom, and make it available from the `PATH`: ```bash brew tap homebrew/cask-versions -brew install adoptopenjdk/openjdk/adoptopenjdk11 -export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)" +brew install adoptopenjdk/openjdk/adoptopenjdk17 +export JAVA_HOME="$(/usr/libexec/java_home --version 1.17)" export PATH="$JAVA_HOME/bin:$PATH" ``` diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ec77e51..e411586a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/server/build.gradle b/server/build.gradle index bee6509b..927906fc 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { namespace 'com.genymobile.scrcpy' - compileSdkVersion 33 + compileSdk 33 defaultConfig { applicationId "com.genymobile.scrcpy" minSdkVersion 21 @@ -17,6 +17,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + buildFeatures { + buildConfig true + aidl true + } } dependencies { From abcb10059749d9536c6e344f5c61466305afd2ee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 15 Nov 2023 21:08:15 +0100 Subject: [PATCH 1021/1133] Upgrade Android SDK to 34 --- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/build.gradle b/server/build.gradle index 927906fc..1bb31360 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -2,11 +2,11 @@ apply plugin: 'com.android.application' android { namespace 'com.genymobile.scrcpy' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.genymobile.scrcpy" minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode 200 versionName "v2.2" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 6e755272..5ab90a0a 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,8 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=v2.2 -PLATFORM=${ANDROID_PLATFORM:-33} -BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} +PLATFORM=${ANDROID_PLATFORM:-34} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" From 7e3b9359322fff65bd350febfdc02a76186981cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Nov 2023 08:56:04 +0100 Subject: [PATCH 1022/1133] Recreate the display on rotation On Android 14 (Pixel 8), a device rotation while the camera app was running resulted in an incorrect capture. Destroying and recreating the display fixes the issue. --- .../main/java/com/genymobile/scrcpy/ScreenCapture.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index f81332f5..e048354a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -18,7 +18,6 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList @Override public void init() { - display = createDisplay(); device.setRotationListener(this); device.setFoldListener(this); } @@ -32,6 +31,11 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); int layerStack = device.getLayerStack(); + + if (display != null) { + SurfaceControl.destroyDisplay(display); + } + display = createDisplay(); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); } @@ -39,7 +43,9 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList public void release() { device.setRotationListener(null); device.setFoldListener(null); - SurfaceControl.destroyDisplay(display); + if (display != null) { + SurfaceControl.destroyDisplay(display); + } } @Override From 45a073a333564a3c596cfe4067de51bb339e8e2b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Nov 2023 09:02:52 +0100 Subject: [PATCH 1023/1133] Do not create Device instance for camera The device instance manages the display and the injection of input events. It is not necessary for camera capture. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a1c6090b..61d3497b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -92,8 +92,6 @@ public final class Server { throw new ConfigurationException("Camera mirroring is not supported"); } - final Device device = new Device(options); - Thread initThread = startInitThread(options); int scid = options.getScid(); @@ -102,7 +100,9 @@ public final class Server { boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); - boolean camera = options.getVideoSource() == VideoSource.CAMERA; + boolean camera = video && options.getVideoSource() == VideoSource.CAMERA; + + final Device device = camera ? null : new Device(options); Workarounds.apply(audio, camera); From 4658c0e5d223d3d9aff19a37622e701261b09aef Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 16 Nov 2023 09:59:37 +0100 Subject: [PATCH 1024/1133] Update record format error message Recording now supports formats other than mp4 and mkv. Refs e637feba51c2eac6de27ebb318a2f7a1aa54a62d --- app/src/cli.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 19097f47..b402f6c5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1640,7 +1640,8 @@ static bool parse_record_format(const char *optarg, enum sc_record_format *format) { enum sc_record_format fmt = get_record_format(optarg); if (!fmt) { - LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg); + LOGE("Unsupported record format: %s (expected mp4, mkv, m4a, mka, " + "opus, aac, flac or wav)", optarg); return false; } @@ -1710,7 +1711,8 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) { *codec = SC_CODEC_RAW; return true; } - LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)", optarg); + LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)", + optarg); return false; } From 0801cf062722064506f2353c2c9bcd3504c59281 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 13:09:46 +0100 Subject: [PATCH 1025/1133] Fix options alphabetical order Renaming --display to --display-id broke the alphabetical order. Refs 23e116064dc97f2af843e764f13eebd54fab486d --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 12 ++++++------ app/src/cli.c | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 97dbfe70..f94a70a6 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -19,8 +19,8 @@ _scrcpy() { --crop= -d --select-usb --disable-screensaver - --display-id= --display-buffer= + --display-id= -e --select-tcpip -f --fullscreen --force-adb-forward diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 4b8a7737..cc58b866 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -26,8 +26,8 @@ arguments=( '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' {-d,--select-usb}'[Use USB device]' '--disable-screensaver[Disable screensaver while scrcpy is running]' - '--display-id=[Specify the display id to mirror]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' + '--display-id=[Specify the display id to mirror]' {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 26f53ba4..10c32ca1 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -127,6 +127,12 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). .BI "\-\-disable-screensaver" Disable screensaver while scrcpy is running. +.TP +.BI "\-\-display\-buffer ms +Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter. + +Default is 0 (no buffering). + .TP .BI "\-\-display\-id " id Specify the device display id to mirror. @@ -135,12 +141,6 @@ The available display ids can be listed by \fB\-\-list\-displays\fR. Default is 0. -.TP -.BI "\-\-display\-buffer ms -Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter. - -Default is 0 (no buffering). - .TP .B \-e, \-\-select\-tcpip Use TCP/IP device (if there is exactly one, like adb -e). diff --git a/app/src/cli.c b/app/src/cli.c index b402f6c5..12ed8111 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -292,6 +292,14 @@ static const struct sc_option options[] = { .longopt = "display", .argdesc = "id", }, + { + .longopt_id = OPT_DISPLAY_BUFFER, + .longopt = "display-buffer", + .argdesc = "ms", + .text = "Add a buffering delay (in milliseconds) before displaying. " + "This increases latency to compensate for jitter.\n" + "Default is 0 (no buffering).", + }, { .longopt_id = OPT_DISPLAY_ID, .longopt = "display-id", @@ -301,14 +309,6 @@ static const struct sc_option options[] = { " scrcpy --list-displays\n" "Default is 0.", }, - { - .longopt_id = OPT_DISPLAY_BUFFER, - .longopt = "display-buffer", - .argdesc = "ms", - .text = "Add a buffering delay (in milliseconds) before displaying. " - "This increases latency to compensate for jitter.\n" - "Default is 0 (no buffering).", - }, { .shortopt = 'e', .longopt = "select-tcpip", From 9df92ebe3765d2515f8e561896a239cdf82110bb Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 14:05:21 +0100 Subject: [PATCH 1026/1133] Fix manpage style syntax --- app/scrcpy.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 10c32ca1..18941190 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -26,7 +26,7 @@ Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are s Default is 128K (128000). .TP -.BI "\-\-audio\-buffer ms +.BI "\-\-audio\-buffer " ms Configure the audio buffering delay (in milliseconds). Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches). @@ -62,7 +62,7 @@ Select the audio source (output or mic). Default is output. .TP -.BI "\-\-audio\-output\-buffer ms +.BI "\-\-audio\-output\-buffer " ms Configure the size of the SDL audio output buffer (in milliseconds). If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise. @@ -128,7 +128,7 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). Disable screensaver while scrcpy is running. .TP -.BI "\-\-display\-buffer ms +.BI "\-\-display\-buffer " ms Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter. Default is 0 (no buffering). From 25e33566f5bca3b052dbb2b4262be96cd8f71caa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Nov 2023 08:41:09 +0100 Subject: [PATCH 1027/1133] Mention turning off audio in camera documentation --- doc/camera.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/camera.md b/doc/camera.md index d1008bda..e11ad521 100644 --- a/doc/camera.md +++ b/doc/camera.md @@ -18,6 +18,17 @@ scrcpy --video-source=display --audio-source=mic # force display AND micropho scrcpy --video-source=camera --audio-source=output # force camera AND device audio output ``` +Audio can be disabled: + +```bash +# audio not captured at all +scrcpy --video-source=camera --no-audio +scrcpy --video-source=camera --no-audio --record=file.mp4 + +# audio captured and recorded, but not played +scrcpy --video-source=camera --no-audio-playback --record=file.mp4 +``` + ## List From bb88b60227427959b931b5a4417202a85c5b0cdc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Nov 2023 01:06:59 +0100 Subject: [PATCH 1028/1133] Add --display-orientation Deprecate the option --rotation and introduce a new option --display-orientation with the 8 possible orientations (0, 90, 180, 270, flip0, flip90, flip180 and flip270). New shortcuts MOD+Shift+(arrow) dynamically change the display (horizontal or vertical) flip. Fixes #1380 Fixes #3819 PR #4441 --- app/data/bash-completion/scrcpy | 9 ++-- app/data/zsh-completion/_scrcpy | 2 +- app/meson.build | 4 ++ app/scrcpy.1 | 20 +++++-- app/src/cli.c | 92 +++++++++++++++++++++++++++++++-- app/src/display.c | 16 +++--- app/src/display.h | 3 +- app/src/input_manager.c | 48 +++++++++++------ app/src/options.c | 38 +++++++++++++- app/src/options.h | 64 ++++++++++++++++++++++- app/src/scrcpy.c | 2 +- app/src/screen.c | 74 +++++++++++++++----------- app/src/screen.h | 12 +++-- app/tests/test_orientation.c | 91 ++++++++++++++++++++++++++++++++ doc/shortcuts.md | 2 + 15 files changed, 403 insertions(+), 74 deletions(-) create mode 100644 app/tests/test_orientation.c diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index f94a70a6..5e359f4f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -21,6 +21,7 @@ _scrcpy() { --disable-screensaver --display-buffer= --display-id= + --display-orientation= -e --select-tcpip -f --fullscreen --force-adb-forward @@ -112,6 +113,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'front back external' -- "$cur")) return ;; + --display-orientation) + COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) return @@ -132,10 +137,6 @@ _scrcpy() { COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur")) return ;; - --rotation) - COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur")) - return - ;; --shortcut-mod) # Only auto-complete a single key COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur")) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index cc58b866..16729d2a 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -28,6 +28,7 @@ arguments=( '--disable-screensaver[Disable screensaver while scrcpy is running]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' '--display-id=[Specify the display id to mirror]' + '--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)' {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' @@ -68,7 +69,6 @@ arguments=( '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' - '--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' {-S,--turn-screen-off}'[Turn the device screen off immediately]' '--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)' diff --git a/app/meson.build b/app/meson.build index e0d92050..b1233c6b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -289,6 +289,10 @@ if get_option('buildtype') == 'debug' 'tests/test_device_msg_deserialize.c', 'src/device_msg.c', ]], + ['test_orientation', [ + 'tests/test_orientation.c', + 'src/options.c', + ]], ['test_strbuf', [ 'tests/test_strbuf.c', 'src/util/strbuf.c', diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 18941190..08a366ee 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -141,6 +141,14 @@ The available display ids can be listed by \fB\-\-list\-displays\fR. Default is 0. +.TP +.BI "\-\-display\-orientation " value +Set the initial display orientation. + +Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation. + +Default is 0. + .TP .B \-e, \-\-select\-tcpip Use TCP/IP device (if there is exactly one, like adb -e). @@ -369,10 +377,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me .B \-\-require\-audio By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work. -.TP -.BI "\-\-rotation " value -Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise. - .TP .BI "\-s, \-\-serial " number The device serial number. Mandatory only if several devices are connected to adb. @@ -534,6 +538,14 @@ Rotate display left .B MOD+Right Rotate display right +.TP +.B MOD+Shift+Left, MOD+Shift+Right +Flip display horizontally + +.TP +.B MOD+Shift+Up, MOD+Shift+Down +Flip display vertically + .TP .B MOD+g Resize window to 1:1 (pixel\-perfect) diff --git a/app/src/cli.c b/app/src/cli.c index 12ed8111..668de31d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -90,6 +90,7 @@ enum { OPT_CAMERA_AR, OPT_CAMERA_FPS, OPT_CAMERA_HIGH_SPEED, + OPT_DISPLAY_ORIENTATION, }; struct sc_option { @@ -309,6 +310,17 @@ static const struct sc_option options[] = { " scrcpy --list-displays\n" "Default is 0.", }, + { + .longopt_id = OPT_DISPLAY_ORIENTATION, + .longopt = "display-orientation", + .argdesc = "value", + .text = "Set the initial display orientation.\n" + "Possible values are 0, 90, 180, 270, flip0, flip90, flip180 " + "and flip270. The number represents the clockwise rotation " + "in degrees; the \"flip\" keyword applies a horizontal flip " + "before the rotation.\n" + "Default is 0.", + }, { .shortopt = 'e', .longopt = "select-tcpip", @@ -615,12 +627,10 @@ static const struct sc_option options[] = { "is enabled but does not work." }, { + // deprecated .longopt_id = OPT_ROTATION, .longopt = "rotation", .argdesc = "value", - .text = "Set the initial display rotation.\n" - "Possible values are 0, 1, 2 and 3. Each increment adds a 90 " - "degrees rotation counterclockwise.", }, { .shortopt = 's', @@ -824,6 +834,14 @@ static const struct sc_shortcut shortcuts[] = { .shortcuts = { "MOD+Right" }, .text = "Rotate display right", }, + { + .shortcuts = { "MOD+Shift+Left", "MOD+Shift+Right" }, + .text = "Flip display horizontally", + }, + { + .shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" }, + .text = "Flip display vertically", + }, { .shortcuts = { "MOD+g" }, .text = "Resize window to 1:1 (pixel-perfect)", @@ -1405,6 +1423,45 @@ parse_rotation(const char *s, uint8_t *rotation) { return true; } +static bool +parse_orientation(const char *s, enum sc_orientation *orientation) { + if (!strcmp(s, "0")) { + *orientation = SC_ORIENTATION_0; + return true; + } + if (!strcmp(s, "90")) { + *orientation = SC_ORIENTATION_90; + return true; + } + if (!strcmp(s, "180")) { + *orientation = SC_ORIENTATION_180; + return true; + } + if (!strcmp(s, "270")) { + *orientation = SC_ORIENTATION_270; + return true; + } + if (!strcmp(s, "flip0")) { + *orientation = SC_ORIENTATION_FLIP_0; + return true; + } + if (!strcmp(s, "flip90")) { + *orientation = SC_ORIENTATION_FLIP_90; + return true; + } + if (!strcmp(s, "flip180")) { + *orientation = SC_ORIENTATION_FLIP_180; + return true; + } + if (!strcmp(s, "flip270")) { + *orientation = SC_ORIENTATION_FLIP_270; + return true; + } + LOGE("Unsupported orientation: %s (expected 0, 90, 180, 270, flip0, " + "flip90, flip180 or flip270)", optarg); + return false; +} + static bool parse_window_position(const char *s, int16_t *position) { // special value for "auto" @@ -2008,7 +2065,34 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW; break; case OPT_ROTATION: - if (!parse_rotation(optarg, &opts->rotation)) { + LOGW("--rotation is deprecated, use --display-orientation " + "instead."); + uint8_t rotation; + if (!parse_rotation(optarg, &rotation)) { + return false; + } + assert(rotation <= 3); + switch (rotation) { + case 0: + opts->display_orientation = SC_ORIENTATION_0; + break; + case 1: + // rotation 1 was 90° counterclockwise, but orientation + // is expressed clockwise + opts->display_orientation = SC_ORIENTATION_270; + break; + case 2: + opts->display_orientation = SC_ORIENTATION_180; + break; + case 3: + // rotation 3 was 270° counterclockwise, but orientation + // is expressed clockwise + opts->display_orientation = SC_ORIENTATION_90; + break; + } + break; + case OPT_DISPLAY_ORIENTATION: + if (!parse_orientation(optarg, &opts->display_orientation)) { return false; } break; diff --git a/app/src/display.c b/app/src/display.c index cf26e776..906b5d65 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -234,7 +234,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) { enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - unsigned rotation) { + enum sc_orientation orientation) { SDL_RenderClear(display->renderer); if (display->pending.flags) { @@ -247,33 +247,33 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry, SDL_Renderer *renderer = display->renderer; SDL_Texture *texture = display->texture; - if (rotation == 0) { + if (orientation == SC_ORIENTATION_0) { int ret = SDL_RenderCopy(renderer, texture, NULL, geometry); if (ret) { LOGE("Could not render texture: %s", SDL_GetError()); return SC_DISPLAY_RESULT_ERROR; } } else { - // rotation in RenderCopyEx() is clockwise, while screen->rotation is - // counterclockwise (to be consistent with --lock-video-orientation) - int cw_rotation = (4 - rotation) % 4; + unsigned cw_rotation = sc_orientation_get_rotation(orientation); double angle = 90 * cw_rotation; const SDL_Rect *dstrect = NULL; SDL_Rect rect; - if (rotation & 1) { + if (sc_orientation_is_swap(orientation)) { rect.x = geometry->x + (geometry->w - geometry->h) / 2; rect.y = geometry->y + (geometry->h - geometry->w) / 2; rect.w = geometry->h; rect.h = geometry->w; dstrect = ▭ } else { - assert(rotation == 2); dstrect = geometry; } + SDL_RendererFlip flip = sc_orientation_is_mirror(orientation) + ? SDL_FLIP_HORIZONTAL : 0; + int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle, - NULL, 0); + NULL, flip); if (ret) { LOGE("Could not render texture: %s", SDL_GetError()); return SC_DISPLAY_RESULT_ERROR; diff --git a/app/src/display.h b/app/src/display.h index 6b83a5c9..643ce73c 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -9,6 +9,7 @@ #include "coords.h" #include "opengl.h" +#include "options.h" #ifdef __APPLE__ # define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE @@ -54,6 +55,6 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame); enum sc_display_result sc_display_render(struct sc_display *display, const SDL_Rect *geometry, - unsigned rotation); + enum sc_orientation orientation); #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c9e83d48..9a487836 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -293,15 +293,11 @@ rotate_device(struct sc_controller *controller) { } static void -rotate_client_left(struct sc_screen *screen) { - unsigned new_rotation = (screen->rotation + 1) % 4; - sc_screen_set_rotation(screen, new_rotation); -} - -static void -rotate_client_right(struct sc_screen *screen) { - unsigned new_rotation = (screen->rotation + 3) % 4; - sc_screen_set_rotation(screen, new_rotation); +apply_orientation_transform(struct sc_screen *screen, + enum sc_orientation transform) { + enum sc_orientation new_orientation = + sc_orientation_apply(screen->orientation, transform); + sc_screen_set_orientation(screen, new_orientation); } static void @@ -421,25 +417,47 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_DOWN: - if (controller && !shift) { + if (shift) { + if (!repeat & down) { + apply_orientation_transform(im->screen, + SC_ORIENTATION_FLIP_180); + } + } else if (controller) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (controller && !shift) { + if (shift) { + if (!repeat & down) { + apply_orientation_transform(im->screen, + SC_ORIENTATION_FLIP_180); + } + } else if (controller) { // forward repeated events action_volume_up(controller, action); } return; case SDLK_LEFT: - if (!shift && !repeat && down) { - rotate_client_left(im->screen); + if (!repeat && down) { + if (shift) { + apply_orientation_transform(im->screen, + SC_ORIENTATION_FLIP_0); + } else { + apply_orientation_transform(im->screen, + SC_ORIENTATION_270); + } } return; case SDLK_RIGHT: - if (!shift && !repeat && down) { - rotate_client_right(im->screen); + if (!repeat && down) { + if (shift) { + apply_orientation_transform(im->screen, + SC_ORIENTATION_FLIP_0); + } else { + apply_orientation_transform(im->screen, + SC_ORIENTATION_90); + } } return; case SDLK_c: diff --git a/app/src/options.c b/app/src/options.c index 092fbd56..1454147a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -39,7 +39,7 @@ const struct scrcpy_options scrcpy_options_default = { .audio_bit_rate = 0, .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, - .rotation = 0, + .display_orientation = SC_ORIENTATION_0, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, @@ -89,3 +89,39 @@ const struct scrcpy_options scrcpy_options_default = { .camera_high_speed = false, .list = 0, }; + +enum sc_orientation +sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) { + assert(!(src & ~7)); + assert(!(transform & ~7)); + + unsigned transform_hflip = transform & 4; + unsigned transform_rotation = transform & 3; + unsigned src_hflip = src & 4; + unsigned src_rotation = src & 3; + unsigned src_swap = src & 1; + if (src_swap && transform_hflip) { + // If the src is rotated by 90 or 270 degrees, applying a flipped + // transformation requires an additional 180 degrees rotation to + // compensate for the inversion of the order of multiplication: + // + // hflip1 × rotate1 × hflip2 × rotate2 + // `--------------' `--------------' + // src transform + // + // In the final result, we want all the hflips then all the rotations, + // so we must move hflip2 to the left: + // + // hflip1 × hflip2 × rotate1' × rotate2 + // + // with rotate1' = | rotate1 if src is 0° or 180° + // | rotate1 + 180° if src is 90° or 270° + + src_rotation += 2; + } + + unsigned result_hflip = src_hflip ^ transform_hflip; + unsigned result_rotation = (transform_rotation + src_rotation) % 4; + enum sc_orientation result = result_hflip | result_rotation; + return result; +} diff --git a/app/src/options.h b/app/src/options.h index c702ceeb..5a6c3276 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -3,6 +3,7 @@ #include "common.h" +#include #include #include #include @@ -67,6 +68,67 @@ enum sc_camera_facing { SC_CAMERA_FACING_EXTERNAL, }; + // ,----- hflip (applied before the rotation) + // | ,--- 180° + // | | ,- 90° clockwise + // | | | +enum sc_orientation { // v v v + SC_ORIENTATION_0, // 0 0 0 + SC_ORIENTATION_90, // 0 0 1 + SC_ORIENTATION_180, // 0 1 0 + SC_ORIENTATION_270, // 0 1 1 + SC_ORIENTATION_FLIP_0, // 1 0 0 + SC_ORIENTATION_FLIP_90, // 1 0 1 + SC_ORIENTATION_FLIP_180, // 1 1 0 + SC_ORIENTATION_FLIP_270, // 1 1 1 +}; + +static inline bool +sc_orientation_is_mirror(enum sc_orientation orientation) { + assert(!(orientation & ~7)); + return orientation & 4; +} + +// Does the orientation swap width and height? +static inline bool +sc_orientation_is_swap(enum sc_orientation orientation) { + assert(!(orientation & ~7)); + return orientation & 1; +} + +static inline enum sc_orientation +sc_orientation_get_rotation(enum sc_orientation orientation) { + assert(!(orientation & ~7)); + return orientation & 3; +} + +enum sc_orientation +sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform); + +static inline const char * +sc_orientation_get_name(enum sc_orientation orientation) { + switch (orientation) { + case SC_ORIENTATION_0: + return "0"; + case SC_ORIENTATION_90: + return "90"; + case SC_ORIENTATION_180: + return "180"; + case SC_ORIENTATION_270: + return "270"; + case SC_ORIENTATION_FLIP_0: + return "flip0"; + case SC_ORIENTATION_FLIP_90: + return "flip90"; + case SC_ORIENTATION_FLIP_180: + return "flip180"; + case SC_ORIENTATION_FLIP_270: + return "flip270"; + default: + return "(unknown)"; + } +} + enum sc_lock_video_orientation { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, // lock the current orientation when scrcpy starts @@ -157,7 +219,7 @@ struct scrcpy_options { uint32_t audio_bit_rate; uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; - uint8_t rotation; + enum sc_orientation display_orientation; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index ac2b8e33..9bbe14b8 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -691,7 +691,7 @@ aoa_hid_end: .window_width = options->window_width, .window_height = options->window_height, .window_borderless = options->window_borderless, - .rotation = options->rotation, + .orientation = options->display_orientation, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, .start_fps_counter = options->start_fps_counter, diff --git a/app/src/screen.c b/app/src/screen.c index 5b7a8808..091001bc 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -14,16 +14,16 @@ #define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink) static inline struct sc_size -get_rotated_size(struct sc_size size, int rotation) { - struct sc_size rotated_size; - if (rotation & 1) { - rotated_size.width = size.height; - rotated_size.height = size.width; +get_oriented_size(struct sc_size size, enum sc_orientation orientation) { + struct sc_size oriented_size; + if (sc_orientation_is_swap(orientation)) { + oriented_size.width = size.height; + oriented_size.height = size.width; } else { - rotated_size.width = size.width; - rotated_size.height = size.height; + oriented_size.width = size.width; + oriented_size.height = size.height; } - return rotated_size; + return oriented_size; } // get the window size in a struct sc_size @@ -251,7 +251,7 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { } enum sc_display_result res = - sc_display_render(&screen->display, &screen->rect, screen->rotation); + sc_display_render(&screen->display, &screen->rect, screen->orientation); (void) res; // any error already logged } @@ -379,9 +379,10 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_frame_buffer; } - screen->rotation = params->rotation; - if (screen->rotation) { - LOGI("Initial display rotation set to %u", screen->rotation); + screen->orientation = params->orientation; + if (screen->orientation != SC_ORIENTATION_0) { + LOGI("Initial display orientation set to %s", + sc_orientation_get_name(screen->orientation)); } uint32_t window_flags = SDL_WINDOW_HIDDEN @@ -559,19 +560,19 @@ apply_pending_resize(struct sc_screen *screen) { } void -sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) { - assert(rotation < 4); - if (rotation == screen->rotation) { +sc_screen_set_orientation(struct sc_screen *screen, + enum sc_orientation orientation) { + if (orientation == screen->orientation) { return; } struct sc_size new_content_size = - get_rotated_size(screen->frame_size, rotation); + get_oriented_size(screen->frame_size, orientation); set_content_size(screen, new_content_size); - screen->rotation = rotation; - LOGI("Display rotation set to %u", rotation); + screen->orientation = orientation; + LOGI("Display orientation set to %s", sc_orientation_get_name(orientation)); sc_screen_render(screen, true); } @@ -584,7 +585,7 @@ sc_screen_init_size(struct sc_screen *screen) { // The requested size is passed via screen->frame_size struct sc_size content_size = - get_rotated_size(screen->frame_size, screen->rotation); + get_oriented_size(screen->frame_size, screen->orientation); screen->content_size = content_size; enum sc_display_result res = @@ -604,7 +605,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { screen->frame_size = new_frame_size; struct sc_size new_content_size = - get_rotated_size(new_frame_size, screen->rotation); + get_oriented_size(new_frame_size, screen->orientation); set_content_size(screen, new_content_size); sc_screen_update_content_rect(screen); @@ -843,8 +844,7 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { struct sc_point sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t x, int32_t y) { - unsigned rotation = screen->rotation; - assert(rotation < 4); + enum sc_orientation orientation = screen->orientation; int32_t w = screen->content_size.width; int32_t h = screen->content_size.height; @@ -855,27 +855,43 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; - // rotate struct sc_point result; - switch (rotation) { - case 0: + switch (orientation) { + case SC_ORIENTATION_0: result.x = x; result.y = y; break; - case 1: + case SC_ORIENTATION_90: + result.x = y; + result.y = w - x; + break; + case SC_ORIENTATION_180: + result.x = w - x; + result.y = h - y; + break; + case SC_ORIENTATION_270: result.x = h - y; result.y = x; break; - case 2: + case SC_ORIENTATION_FLIP_0: result.x = w - x; + result.y = y; + break; + case SC_ORIENTATION_FLIP_90: + result.x = h - y; + result.y = w - x; + break; + case SC_ORIENTATION_FLIP_180: + result.x = x; result.y = h - y; break; default: - assert(rotation == 3); + assert(orientation == SC_ORIENTATION_FLIP_270); result.x = y; - result.y = w - x; + result.y = x; break; } + return result; } diff --git a/app/src/screen.h b/app/src/screen.h index acbaab4b..46591be5 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -14,6 +14,7 @@ #include "frame_buffer.h" #include "input_manager.h" #include "opengl.h" +#include "options.h" #include "trait/key_processor.h" #include "trait/frame_sink.h" #include "trait/mouse_processor.h" @@ -49,8 +50,8 @@ struct sc_screen { // fullscreen (meaningful only when resize_pending is true) struct sc_size windowed_content_size; - // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) - unsigned rotation; + // client orientation + enum sc_orientation orientation; // rectangle of the content (excluding black borders) struct SDL_Rect rect; bool has_frame; @@ -86,7 +87,7 @@ struct sc_screen_params { bool window_borderless; - uint8_t rotation; + enum sc_orientation orientation; bool mipmaps; bool fullscreen; @@ -129,9 +130,10 @@ sc_screen_resize_to_fit(struct sc_screen *screen); void sc_screen_resize_to_pixel_perfect(struct sc_screen *screen); -// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise) +// set the display orientation void -sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); +sc_screen_set_orientation(struct sc_screen *screen, + enum sc_orientation orientation); // react to SDL events // If this function returns false, scrcpy must exit with an error. diff --git a/app/tests/test_orientation.c b/app/tests/test_orientation.c new file mode 100644 index 00000000..153211fa --- /dev/null +++ b/app/tests/test_orientation.c @@ -0,0 +1,91 @@ +#include "common.h" + +#include + +#include "options.h" + +static void test_transforms(void) { + #define O(X) SC_ORIENTATION_ ## X + #define ASSERT_TRANSFORM(SRC, TR, RES) \ + assert(sc_orientation_apply(O(SRC), O(TR)) == O(RES)); + + ASSERT_TRANSFORM(0, 0, 0); + ASSERT_TRANSFORM(0, 90, 90); + ASSERT_TRANSFORM(0, 180, 180); + ASSERT_TRANSFORM(0, 270, 270); + ASSERT_TRANSFORM(0, FLIP_0, FLIP_0); + ASSERT_TRANSFORM(0, FLIP_90, FLIP_90); + ASSERT_TRANSFORM(0, FLIP_180, FLIP_180); + ASSERT_TRANSFORM(0, FLIP_270, FLIP_270); + + ASSERT_TRANSFORM(90, 0, 90); + ASSERT_TRANSFORM(90, 90, 180); + ASSERT_TRANSFORM(90, 180, 270); + ASSERT_TRANSFORM(90, 270, 0); + ASSERT_TRANSFORM(90, FLIP_0, FLIP_270); + ASSERT_TRANSFORM(90, FLIP_90, FLIP_0); + ASSERT_TRANSFORM(90, FLIP_180, FLIP_90); + ASSERT_TRANSFORM(90, FLIP_270, FLIP_180); + + ASSERT_TRANSFORM(180, 0, 180); + ASSERT_TRANSFORM(180, 90, 270); + ASSERT_TRANSFORM(180, 180, 0); + ASSERT_TRANSFORM(180, 270, 90); + ASSERT_TRANSFORM(180, FLIP_0, FLIP_180); + ASSERT_TRANSFORM(180, FLIP_90, FLIP_270); + ASSERT_TRANSFORM(180, FLIP_180, FLIP_0); + ASSERT_TRANSFORM(180, FLIP_270, FLIP_90); + + ASSERT_TRANSFORM(270, 0, 270); + ASSERT_TRANSFORM(270, 90, 0); + ASSERT_TRANSFORM(270, 180, 90); + ASSERT_TRANSFORM(270, 270, 180); + ASSERT_TRANSFORM(270, FLIP_0, FLIP_90); + ASSERT_TRANSFORM(270, FLIP_90, FLIP_180); + ASSERT_TRANSFORM(270, FLIP_180, FLIP_270); + ASSERT_TRANSFORM(270, FLIP_270, FLIP_0); + + ASSERT_TRANSFORM(FLIP_0, 0, FLIP_0); + ASSERT_TRANSFORM(FLIP_0, 90, FLIP_90); + ASSERT_TRANSFORM(FLIP_0, 180, FLIP_180); + ASSERT_TRANSFORM(FLIP_0, 270, FLIP_270); + ASSERT_TRANSFORM(FLIP_0, FLIP_0, 0); + ASSERT_TRANSFORM(FLIP_0, FLIP_90, 90); + ASSERT_TRANSFORM(FLIP_0, FLIP_180, 180); + ASSERT_TRANSFORM(FLIP_0, FLIP_270, 270); + + ASSERT_TRANSFORM(FLIP_90, 0, FLIP_90); + ASSERT_TRANSFORM(FLIP_90, 90, FLIP_180); + ASSERT_TRANSFORM(FLIP_90, 180, FLIP_270); + ASSERT_TRANSFORM(FLIP_90, 270, FLIP_0); + ASSERT_TRANSFORM(FLIP_90, FLIP_0, 270); + ASSERT_TRANSFORM(FLIP_90, FLIP_90, 0); + ASSERT_TRANSFORM(FLIP_90, FLIP_180, 90); + ASSERT_TRANSFORM(FLIP_90, FLIP_270, 180); + + ASSERT_TRANSFORM(FLIP_180, 0, FLIP_180); + ASSERT_TRANSFORM(FLIP_180, 90, FLIP_270); + ASSERT_TRANSFORM(FLIP_180, 180, FLIP_0); + ASSERT_TRANSFORM(FLIP_180, 270, FLIP_90); + ASSERT_TRANSFORM(FLIP_180, FLIP_0, 180); + ASSERT_TRANSFORM(FLIP_180, FLIP_90, 270); + ASSERT_TRANSFORM(FLIP_180, FLIP_180, 0); + ASSERT_TRANSFORM(FLIP_180, FLIP_270, 90); + + ASSERT_TRANSFORM(FLIP_270, 0, FLIP_270); + ASSERT_TRANSFORM(FLIP_270, 90, FLIP_0); + ASSERT_TRANSFORM(FLIP_270, 180, FLIP_90); + ASSERT_TRANSFORM(FLIP_270, 270, FLIP_180); + ASSERT_TRANSFORM(FLIP_270, FLIP_0, 90); + ASSERT_TRANSFORM(FLIP_270, FLIP_90, 180); + ASSERT_TRANSFORM(FLIP_270, FLIP_180, 270); + ASSERT_TRANSFORM(FLIP_270, FLIP_270, 0); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_transforms(); + return 0; +} diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 5e706402..c0fc2842 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -26,6 +26,8 @@ _[Super] is typically the Windows or Cmd key._ | Switch fullscreen mode | MOD+f | Rotate display left | MOD+ _(left)_ | Rotate display right | MOD+ _(right)_ + | Flip display horizontally | MOD+Shift+ _(left)_ \| MOD+Shift+ _(right)_ + | Flip display vertically | MOD+Shift+ _(up)_ \| MOD+Shift+ _(down)_ | Resize window to 1:1 (pixel-perfect) | MOD+g | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ | Click on `HOME` | MOD+h \| _Middle-click_ From 2f926869300fdcdc1c668994739d0c379a9d525d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 13:49:14 +0100 Subject: [PATCH 1029/1133] Pass --lock-video-orientation argument in degrees For consistency with the new --display-orientation option, express the --lock-video-orientation in degrees clockwise: * --lock-video-orientation=0 -> --lock-video-orientation=0 * --lock-video-orientation=3 -> --lock-video-orientation=90 * --lock-video-orientation=2 -> --lock-video-orientation=180 * --lock-video-orientation=1 -> --lock-video-orientation=270 PR #4441 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 4 ++- app/src/cli.c | 57 ++++++++++++++++++++++++++------- app/src/options.h | 6 ++-- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 5e359f4f..0ecace96 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -118,7 +118,7 @@ _scrcpy() { return ;; --lock-video-orientation) - COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) + COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur")) return ;; --pause-on-exit) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 16729d2a..3f65cb4e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -41,7 +41,7 @@ arguments=( '--list-cameras[List cameras available on the device]' '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' - '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' + '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' {-m,--max-size=}'[Limit both the width and height of the video to value]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' '--max-fps=[Limit the frame rate of screen capture]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 08a366ee..266ba1f4 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -215,7 +215,9 @@ List displays available on the device. .TP \fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR] -Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise. +Lock capture video orientation to \fIvalue\fR. + +Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees. Default is "unlocked". diff --git a/app/src/cli.c b/app/src/cli.c index 668de31d..37c2274c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -411,11 +411,11 @@ static const struct sc_option options[] = { .longopt = "lock-video-orientation", .argdesc = "value", .optional_arg = true, - .text = "Lock video orientation to value.\n" + .text = "Lock capture video orientation to value.\n" "Possible values are \"unlocked\", \"initial\" (locked to the " - "initial orientation), 0, 1, 2 and 3. Natural device " - "orientation is 0, and each increment adds a 90 degrees " - "rotation counterclockwise.\n" + "initial orientation), 0, 90, 180 and 270. The values " + "represent the clockwise rotation from the natural device " + "orientation, in degrees.\n" "Default is \"unlocked\".\n" "Passing the option without argument is equivalent to passing " "\"initial\".", @@ -1400,15 +1400,50 @@ parse_lock_video_orientation(const char *s, return true; } - long value; - bool ok = parse_integer_arg(s, &value, false, 0, 3, - "lock video orientation"); - if (!ok) { - return false; + if (!strcmp(s, "0")) { + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_0; + return true; } - *lock_mode = (enum sc_lock_video_orientation) value; - return true; + if (!strcmp(s, "90")) { + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_90; + return true; + } + + if (!strcmp(s, "180")) { + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_180; + return true; + } + + if (!strcmp(s, "270")) { + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_270; + return true; + } + + if (!strcmp(s, "1")) { + LOGW("--lock-video-orientation=1 is deprecated, use " + "--lock-video-orientation=270 instead."); + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_270; + return true; + } + + if (!strcmp(s, "2")) { + LOGW("--lock-video-orientation=2 is deprecated, use " + "--lock-video-orientation=180 instead."); + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_180; + return true; + } + + if (!strcmp(s, "3")) { + LOGW("--lock-video-orientation=3 is deprecated, use " + "--lock-video-orientation=90 instead."); + *lock_mode = SC_LOCK_VIDEO_ORIENTATION_90; + return true; + } + + LOGE("Unsupported --lock-video-orientation value: %s (expected initial, " + "unlocked, 0, 90, 180 or 270).", s); + return false; } static bool diff --git a/app/src/options.h b/app/src/options.h index 5a6c3276..4fb45840 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -134,9 +134,9 @@ enum sc_lock_video_orientation { // 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, + SC_LOCK_VIDEO_ORIENTATION_90 = 3, + SC_LOCK_VIDEO_ORIENTATION_180 = 2, + SC_LOCK_VIDEO_ORIENTATION_270 = 1, }; enum sc_keyboard_input_mode { From a9d6cb58374e27d34988db2245bfddf9402ad57d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 14:02:46 +0100 Subject: [PATCH 1030/1133] Add --record-orientation Add an option to store the orientation to apply in a recorded file. Only rotations are supported (not flips). PR #4441 --- app/data/bash-completion/scrcpy | 5 ++++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 8 +++++ app/src/cli.c | 24 +++++++++++++++ app/src/compat.h | 11 +++++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/recorder.c | 52 +++++++++++++++++++++++++++++++++ app/src/recorder.h | 3 ++ app/src/scrcpy.c | 3 +- 10 files changed, 108 insertions(+), 1 deletion(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0ecace96..f08df996 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -62,6 +62,7 @@ _scrcpy() { -r --record= --raw-key-events --record-format= + --record-orientation= --render-driver= --require-audio --rotation= @@ -117,6 +118,10 @@ _scrcpy() { COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return ;; + --record-orientation) + COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur")) + return + ;; --lock-video-orientation) COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 3f65cb4e..0e39f96b 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -67,6 +67,7 @@ arguments=( {-r,--record=}'[Record screen to file]:record file:_files' '--raw-key-events[Inject key events for all input keys, and ignore text events]' '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' + '--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 266ba1f4..1a9386ee 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -367,6 +367,14 @@ Inject key events for all input keys, and ignore text events. .BI "\-\-record\-format " format Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav). +.TP +.BI "\-\-record\-orientation " value +Set the record orientation. + +Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees. + +Default is 0. + .TP .BI "\-\-render\-driver " name Request SDL to use the given render driver (this is just a hint). diff --git a/app/src/cli.c b/app/src/cli.c index 37c2274c..fb0f43d5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -91,6 +91,7 @@ enum { OPT_CAMERA_FPS, OPT_CAMERA_HIGH_SPEED, OPT_DISPLAY_ORIENTATION, + OPT_RECORD_ORIENTATION, }; struct sc_option { @@ -609,6 +610,15 @@ static const struct sc_option options[] = { .text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac " "or wav).", }, + { + .longopt_id = OPT_RECORD_ORIENTATION, + .longopt = "record-orientation", + .argdesc = "value", + .text = "Set the record orientation.\n" + "Possible values are 0, 90, 180 and 270. The number represents " + "the clockwise rotation in degrees.\n" + "Default is 0.", + }, { .longopt_id = OPT_RENDER_DRIVER, .longopt = "render-driver", @@ -2131,6 +2141,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_RECORD_ORIENTATION: + if (!parse_orientation(optarg, &opts->record_orientation)) { + return false; + } + break; case OPT_RENDER_DRIVER: opts->render_driver = optarg; break; @@ -2497,6 +2512,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->record_orientation != SC_ORIENTATION_0) { + if (sc_orientation_is_mirror(opts->record_orientation)) { + LOGE("Record orientation only supports rotation, not " + "flipping: %s", + sc_orientation_get_name(opts->record_orientation)); + return false; + } + } + if (opts->video && sc_record_format_is_audio_only(opts->record_format)) { LOGE("Audio container does not support video stream"); diff --git a/app/src/compat.h b/app/src/compat.h index e80a9dd2..fd610c02 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -3,7 +3,9 @@ #include "config.h" +#include #include +#include #include #ifndef __WIN32 @@ -50,6 +52,15 @@ # define SCRCPY_LAVU_HAS_CHLAYOUT #endif +// In ffmpeg/doc/APIchanges: +// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h +// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(), +// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields +// from AVFormatContext.codecpar should be used from now on. +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100) +# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA +#endif + #if SDL_VERSION_ATLEAST(2, 0, 6) // # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS diff --git a/app/src/options.c b/app/src/options.c index 1454147a..a13df585 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -40,6 +40,7 @@ const struct scrcpy_options scrcpy_options_default = { .max_fps = 0, .lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, .display_orientation = SC_ORIENTATION_0, + .record_orientation = SC_ORIENTATION_0, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, diff --git a/app/src/options.h b/app/src/options.h index 4fb45840..11e64fa1 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -220,6 +220,7 @@ struct scrcpy_options { uint16_t max_fps; enum sc_lock_video_orientation lock_video_orientation; enum sc_orientation display_orientation; + enum sc_orientation record_orientation; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/recorder.c b/app/src/recorder.c index c9d5f131..9e0b3395 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "util/log.h" #include "util/str.h" @@ -493,6 +494,42 @@ run_recorder(void *data) { return 0; } +static bool +sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) { + assert(!sc_orientation_is_mirror(orientation)); + + uint8_t *raw_data; +#ifdef SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA + AVPacketSideData *sd = + av_packet_side_data_new(&stream->codecpar->coded_side_data, + &stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX, + sizeof(int32_t) * 9, 0); + if (!sd) { + LOG_OOM(); + return false; + } + + raw_data = sd->data; +#else + raw_data = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, + sizeof(int32_t) * 9); + if (!raw_data) { + LOG_OOM(); + return false; + } +#endif + + int32_t *matrix = (int32_t *) raw_data; + + unsigned rotation = orientation; + unsigned angle = rotation * 90; + + av_display_rotation_set(matrix, angle); + + return true; +} + static bool sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) { @@ -520,6 +557,16 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink, recorder->video_stream.index = stream->index; + if (recorder->orientation != SC_ORIENTATION_0) { + if (!sc_recorder_set_orientation(stream, recorder->orientation)) { + sc_mutex_unlock(&recorder->mutex); + return false; + } + + LOGI("Record orientation set to %s", + sc_orientation_get_name(recorder->orientation)); + } + recorder->video_init = true; sc_cond_signal(&recorder->cond); sc_mutex_unlock(&recorder->mutex); @@ -689,7 +736,10 @@ sc_recorder_stream_init(struct sc_recorder_stream *stream) { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool video, bool audio, + enum sc_orientation orientation, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { + assert(!sc_orientation_is_mirror(orientation)); + recorder->filename = strdup(filename); if (!recorder->filename) { LOG_OOM(); @@ -710,6 +760,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video = video; recorder->audio = audio; + recorder->orientation = orientation; + sc_vecdeque_init(&recorder->video_queue); sc_vecdeque_init(&recorder->audio_queue); recorder->stopped = false; diff --git a/app/src/recorder.h b/app/src/recorder.h index 16327584..d096e79a 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -34,6 +34,8 @@ struct sc_recorder { bool audio; bool video; + enum sc_orientation orientation; + char *filename; enum sc_record_format format; AVFormatContext *ctx; @@ -67,6 +69,7 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool video, bool audio, + enum sc_orientation orientation, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9bbe14b8..d62a5f52 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -507,7 +507,8 @@ scrcpy(struct scrcpy_options *options) { }; if (!sc_recorder_init(&s->recorder, options->record_filename, options->record_format, options->video, - options->audio, &recorder_cbs, NULL)) { + options->audio, options->record_orientation, + &recorder_cbs, NULL)) { goto end; } recorder_initialized = true; From b43a9e8e7a70e3b847d13eeb17ebf18708c4d7fc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 17:49:04 +0100 Subject: [PATCH 1031/1133] Add --orientation Add a shortcut to set both the display and record orientations. PR #4441 --- app/data/bash-completion/scrcpy | 2 ++ app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 16 ++++++++++++++++ 4 files changed, 23 insertions(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index f08df996..0c854310 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -51,6 +51,7 @@ _scrcpy() { --no-power-on --no-video --no-video-playback + --orientation= --otg -p --port= --pause-on-exit @@ -114,6 +115,7 @@ _scrcpy() { COMPREPLY=($(compgen -W 'front back external' -- "$cur")) return ;; + --orientation --display-orientation) COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 0e39f96b..3c7ca217 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -57,6 +57,7 @@ arguments=( '--no-power-on[Do not power on the device on start]' '--no-video[Disable video forwarding]' '--no-video-playback[Disable video playback]' + '--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' '--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1a9386ee..0c34b4e2 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -299,6 +299,10 @@ Disable video forwarding. .B \-\-no\-video\-playback Disable video playback on the computer. +.TP +.BI "\-\-orientation " value +Same as --display-orientation=value --record-orientation=value. + .TP .B \-\-otg Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. diff --git a/app/src/cli.c b/app/src/cli.c index fb0f43d5..f57b75ef 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -92,6 +92,7 @@ enum { OPT_CAMERA_HIGH_SPEED, OPT_DISPLAY_ORIENTATION, OPT_RECORD_ORIENTATION, + OPT_ORIENTATION, }; struct sc_option { @@ -525,6 +526,13 @@ static const struct sc_option options[] = { .longopt = "no-video-playback", .text = "Disable video playback on the computer.", }, + { + .longopt_id = OPT_ORIENTATION, + .longopt = "orientation", + .argdesc = "value", + .text = "Same as --display-orientation=value " + "--record-orientation=value.", + }, { .longopt_id = OPT_OTG, .longopt = "otg", @@ -2146,6 +2154,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_ORIENTATION: + enum sc_orientation orientation; + if (!parse_orientation(optarg, &orientation)) { + return false; + } + opts->display_orientation = orientation; + opts->record_orientation = orientation; + break; case OPT_RENDER_DRIVER: opts->render_driver = optarg; break; From 94031dfe97402d322e50c4d156456057fad53da0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 20 Nov 2023 20:56:36 +0100 Subject: [PATCH 1032/1133] Update documentation about video orientation PR #4441 --- doc/camera.md | 10 ++++++++++ doc/recording.md | 6 ++++++ doc/video.md | 45 ++++++++++++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/doc/camera.md b/doc/camera.md index d1008bda..fcc410fa 100644 --- a/doc/camera.md +++ b/doc/camera.md @@ -101,6 +101,16 @@ scrcpy --video-source=camera --camera-size=1920x1080 -m3000 # error ``` +## Rotation + +To rotate the captured video, use the [video orientation](video.md#orientation) +option: + +``` +scrcpy --video-source=camera --camera-size=1920x1080 --orientation=90 +``` + + ## Frame rate By default, camera is captured at Android's default frame rate (30 fps). diff --git a/doc/recording.md b/doc/recording.md index c1a8445e..216542e9 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -50,6 +50,12 @@ scrcpy --record=file --record-format=mkv ``` +## Rotation + +The video can be recorded rotated. See [video +orientation](video.md#orientation). + + ## No playback To disable playback while recording: diff --git a/doc/video.md b/doc/video.md index 512e0aba..ed92cb22 100644 --- a/doc/video.md +++ b/doc/video.md @@ -97,39 +97,50 @@ scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc' ``` -## Rotation +## Orientation -The rotation may be applied at 3 different levels: +The orientation may be applied at 3 different levels: - The [shortcut](shortcuts.md) MOD+r requests the device to switch between portrait and landscape (the current running app may refuse, if it does not support the requested orientation). - `--lock-video-orientation` changes the mirroring orientation (the orientation of the video sent from the device to the computer). This affects the recording. - - `--rotation` rotates only the window content. This only affects the display, - not the recording. It may be changed dynamically at any time using the - [shortcuts](shortcuts.md) MOD+ and - MOD+. + - `--orientation` is applied on the client side, and affects display and + recording. For the display, it can be changed dynamically using + [shortcuts](shortcuts.md). -To lock the mirroring orientation: +To lock the mirroring orientation (on the capture side): ```bash -scrcpy --lock-video-orientation # initial (current) orientation -scrcpy --lock-video-orientation=0 # natural orientation -scrcpy --lock-video-orientation=1 # 90° counterclockwise -scrcpy --lock-video-orientation=2 # 180° -scrcpy --lock-video-orientation=3 # 90° clockwise +scrcpy --lock-video-orientation # initial (current) orientation +scrcpy --lock-video-orientation=0 # natural orientation +scrcpy --lock-video-orientation=90 # 90° clockwise +scrcpy --lock-video-orientation=180 # 180° +scrcpy --lock-video-orientation=270 # 270° clockwise ``` -To set an initial window rotation: +To orient the video (on the rendering side): ```bash -scrcpy --rotation=0 # no rotation -scrcpy --rotation=1 # 90 degrees counterclockwise -scrcpy --rotation=2 # 180 degrees -scrcpy --rotation=3 # 90 degrees clockwise +scrcpy --orientation=0 +scrcpy --orientation=90 # 90° clockwise +scrcpy --orientation=180 # 180° +scrcpy --orientation=270 # 270° clockwise +scrcpy --orientation=flip0 # hflip +scrcpy --orientation=flip90 # hflip + 90° clockwise +scrcpy --orientation=flip180 # vflip (hflip + 180°) +scrcpy --orientation=flip270 # hflip + 270° clockwise ``` +The orientation can be set separately for display and record if necessary, via +`--display-orientation` and `--record-orientation`. + +The rotation is applied to a recorded file by writing a display transformation +to the MP4 or MKV target file. Flipping is not supported, so only the 4 first +values are allowed when recording. + + ## Crop The device screen may be cropped to mirror only part of the screen. From 85a94dd4b563e961304b2d9082932c5c1cc2e582 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 21 Nov 2023 14:59:24 +0100 Subject: [PATCH 1033/1133] Fix meson deprecated 'pkgconfig' to 'pkg-config' When running ./release.sh: > DEPRECATION: "pkgconfig" entry is deprecated and should be replaced by > "pkg-config" --- cross_win32.txt | 2 +- cross_win64.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cross_win32.txt b/cross_win32.txt index e24f3722..bf3c118e 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -6,7 +6,7 @@ c = 'i686-w64-mingw32-gcc' cpp = 'i686-w64-mingw32-g++' ar = 'i686-w64-mingw32-ar' strip = 'i686-w64-mingw32-strip' -pkgconfig = 'i686-w64-mingw32-pkg-config' +pkg-config = 'i686-w64-mingw32-pkg-config' windres = 'i686-w64-mingw32-windres' [host_machine] diff --git a/cross_win64.txt b/cross_win64.txt index 39e79944..81bb0309 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -6,7 +6,7 @@ c = 'x86_64-w64-mingw32-gcc' cpp = 'x86_64-w64-mingw32-g++' ar = 'x86_64-w64-mingw32-ar' strip = 'x86_64-w64-mingw32-strip' -pkgconfig = 'x86_64-w64-mingw32-pkg-config' +pkg-config = 'x86_64-w64-mingw32-pkg-config' windres = 'x86_64-w64-mingw32-windres' [host_machine] From acb29888377580d21ab67c805576f97d5bda8bc7 Mon Sep 17 00:00:00 2001 From: sam80180 Date: Sat, 11 Nov 2023 02:01:51 +0800 Subject: [PATCH 1034/1133] Do not hardcode server path on the device The path can be retrieved from the classpath. PR #4416 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- server/src/main/java/com/genymobile/scrcpy/CleanUp.java | 6 ++---- server/src/main/java/com/genymobile/scrcpy/Server.java | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 0bcd1a54..b3a1aac1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -14,8 +14,6 @@ import java.io.IOException; */ public final class CleanUp { - public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar"; - // A simple struct to be passed from the main process to the cleanup process public static class Config implements Parcelable { @@ -135,13 +133,13 @@ public final class CleanUp { String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()}; ProcessBuilder builder = new ProcessBuilder(cmd); - builder.environment().put("CLASSPATH", SERVER_PATH); + builder.environment().put("CLASSPATH", Server.SERVER_PATH); builder.start(); } public static void unlinkSelf() { try { - new File(SERVER_PATH).delete(); + new File(Server.SERVER_PATH).delete(); } catch (Exception e) { Ln.e("Could not unlink server", e); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 61d3497b..2a8387e0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -3,12 +3,20 @@ package com.genymobile.scrcpy; import android.os.BatteryManager; import android.os.Build; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public final class Server { + public static final String SERVER_PATH; + static { + String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator); + // By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath + SERVER_PATH = classPaths[0]; + } + private static class Completion { private int running; private boolean fatalError; From c573bd2a33c8fcd64005daca85a67a26bc958dfe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Nov 2023 23:50:00 +0100 Subject: [PATCH 1035/1133] Fix java code style --- .../com/genymobile/scrcpy/AsyncProcessor.java | 2 ++ .../com/genymobile/scrcpy/CameraCapture.java | 23 +++++++++---------- .../com/genymobile/scrcpy/Controller.java | 10 ++++---- .../java/com/genymobile/scrcpy/Device.java | 2 +- .../scrcpy/DeviceMessageSender.java | 1 + .../java/com/genymobile/scrcpy/Server.java | 1 + .../java/com/genymobile/scrcpy/Settings.java | 2 +- .../scrcpy/wrappers/ClipboardManager.java | 4 ++-- .../scrcpy/wrappers/PowerManager.java | 2 +- .../scrcpy/wrappers/ServiceManager.java | 1 + .../scrcpy/wrappers/WindowManager.java | 2 +- 11 files changed, 26 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java index b9b6745c..d5da6a90 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java +++ b/server/src/main/java/com/genymobile/scrcpy/AsyncProcessor.java @@ -11,6 +11,8 @@ public interface AsyncProcessor { } void start(TerminationListener listener); + void stop(); + void join() throws InterruptedException; } diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index b9da3658..a1003829 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -289,18 +289,17 @@ public class CameraCapture extends SurfaceCapture { List outputs = Arrays.asList(outputConfig); int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR; - SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - future.complete(session); - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR)); - } - }); + SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + future.complete(session); + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR)); + } + }); camera.createCaptureSession(sessionConfig); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 733a2032..3b0e9031 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -318,9 +318,8 @@ public class Controller implements AsyncProcessor { } } - MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, - 0); + MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, + DEFAULT_DEVICE_ID, 0, source, 0); return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } @@ -341,9 +340,8 @@ public class Controller implements AsyncProcessor { coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); - MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, - InputDevice.SOURCE_MOUSE, 0); + MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, + DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 4ab689b0..9fbe9239 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -11,8 +11,8 @@ import android.graphics.Rect; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; -import android.view.IRotationWatcher; import android.view.IDisplayFoldListener; +import android.view.IRotationWatcher; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyCharacterMap; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 628c1d3c..94e842ee 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -51,6 +51,7 @@ public final class DeviceMessageSender { } } } + public void start() { thread = new Thread(() -> { try { diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2a8387e0..e4a95140 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -11,6 +11,7 @@ import java.util.List; public final class Server { public static final String SERVER_PATH; + static { String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator); // By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath diff --git a/server/src/main/java/com/genymobile/scrcpy/Settings.java b/server/src/main/java/com/genymobile/scrcpy/Settings.java index 1d38814d..1b5e5f98 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Settings.java +++ b/server/src/main/java/com/genymobile/scrcpy/Settings.java @@ -75,7 +75,7 @@ public final class Settings { String oldValue = getValue(table, key); if (!value.equals(oldValue)) { - putValue(table, key, value); + putValue(table, key, value); } return oldValue; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index eae66858..783a3407 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -138,8 +138,8 @@ public final class ClipboardManager { } } - private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, - IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException { + private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener) + throws InvocationTargetException, IllegalAccessException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, listener, FakeContext.PACKAGE_NAME); return; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java index 8ff074b3..93722687 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java @@ -20,7 +20,7 @@ public final class PowerManager { private Method getIsScreenOnMethod() throws NoSuchMethodException { if (isScreenOnMethod == null) { @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future - String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; + String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; isScreenOnMethod = manager.getClass().getMethod(methodName); } return isScreenOnMethod; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index ae04a6d2..85602c19 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; public final class ServiceManager { private static final Method GET_SERVICE_METHOD; + static { try { GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index ce748855..a746be5c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -4,8 +4,8 @@ import com.genymobile.scrcpy.Ln; import android.annotation.TargetApi; import android.os.IInterface; -import android.view.IRotationWatcher; import android.view.IDisplayFoldListener; +import android.view.IRotationWatcher; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; From 67f356f881898e21edabd4ecd2fb509ad4c92362 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Nov 2023 21:25:13 +0100 Subject: [PATCH 1036/1133] Improve crossbuild Install all the prebuilt dependencies for Windows to a specific folder, and use meson command line options to specify their location. This removes crossbuild-specific code from the meson scripts and will simplify dependency upgrades. PR #4460 --- app/meson.build | 79 +++++------------------------ app/prebuilt-deps/prepare-ffmpeg.sh | 4 +- app/prebuilt-deps/prepare-libusb.sh | 12 +++-- cross_win32.txt | 5 -- cross_win64.txt | 5 -- release.mk | 68 +++++++++++++------------ 6 files changed, 59 insertions(+), 114 deletions(-) diff --git a/app/meson.build b/app/meson.build index b1233c6b..88e2df9a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -98,77 +98,24 @@ endif cc = meson.get_compiler('c') -crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows' - -if not crossbuild_windows - - # native build - dependencies = [ - dependency('libavformat', version: '>= 57.33'), - dependency('libavcodec', version: '>= 57.37'), - dependency('libavutil'), - dependency('libswresample'), - dependency('sdl2', version: '>= 2.0.5'), - ] - - if v4l2_support - dependencies += dependency('libavdevice') - endif - - if usb_support - dependencies += dependency('libusb-1.0') - endif - -else - # cross-compile mingw32 build (from Linux to Windows) - prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') - sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin' - sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib' - sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include' - - sdl2 = declare_dependency( - dependencies: [ - cc.find_library('SDL2', dirs: sdl2_bin_dir), - cc.find_library('SDL2main', dirs: sdl2_lib_dir), - ], - include_directories: include_directories(sdl2_include_dir) - ) - - prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') - ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' - ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' - - ffmpeg = declare_dependency( - dependencies: [ - cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir), - cc.find_library('avformat-60', dirs: ffmpeg_bin_dir), - cc.find_library('avutil-58', dirs: ffmpeg_bin_dir), - cc.find_library('swresample-4', dirs: ffmpeg_bin_dir), - ], - include_directories: include_directories(ffmpeg_include_dir) - ) - - prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') - libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/bin' - libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb + '/include' - - libusb = declare_dependency( - dependencies: [ - cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir), - ], - include_directories: include_directories(libusb_include_dir) - ) +dependencies = [ + dependency('libavformat', version: '>= 57.33'), + dependency('libavcodec', version: '>= 57.37'), + dependency('libavutil'), + dependency('libswresample'), + dependency('sdl2', version: '>= 2.0.5'), +] - dependencies = [ - ffmpeg, - sdl2, - libusb, - cc.find_library('mingw32') - ] +if v4l2_support + dependencies += dependency('libavdevice') +endif +if usb_support + dependencies += dependency('libusb-1.0') endif if host_machine.system() == 'windows' + dependencies += cc.find_library('mingw32') dependencies += cc.find_library('ws2_32') endif diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh index 96ea3ee7..19840afb 100755 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ b/app/prebuilt-deps/prepare-ffmpeg.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=6.1-scrcpy-2 +VERSION=6.1-scrcpy-3 DEP_DIR="ffmpeg-$VERSION" FILENAME="$DEP_DIR".7z -SHA256SUM=7f25f638dc24a0f5d4af07a088b6a604cf33548900bbfd2f6ce0bae050b7664d +SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a if [[ -d "$DEP_DIR" ]] then diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh index 47cf1df4..6a052f0d 100755 --- a/app/prebuilt-deps/prepare-libusb.sh +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -23,11 +23,15 @@ mkdir "$DEP_DIR" cd "$DEP_DIR" 7z x "../$FILENAME" \ - libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \ - libusb-1.0.26-binaries/libusb-MinGW-Win32/include/ \ - libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \ - libusb-1.0.26-binaries/libusb-MinGW-x64/include/ + libusb-1.0.26-binaries/libusb-MinGW-Win32/ \ + libusb-1.0.26-binaries/libusb-MinGW-Win32/ \ + libusb-1.0.26-binaries/libusb-MinGW-x64/ \ + libusb-1.0.26-binaries/libusb-MinGW-x64/ mv libusb-1.0.26-binaries/libusb-MinGW-Win32 . mv libusb-1.0.26-binaries/libusb-MinGW-x64 . rm -rf libusb-1.0.26-binaries + +# Rename the dll to get the same library name on all platforms +mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll +mv libusb-MinGW-x64/bin/msys-usb-1.0.dll libusb-MinGW-x64/bin/libusb-1.0.dll diff --git a/cross_win32.txt b/cross_win32.txt index bf3c118e..05f9a86b 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -14,8 +14,3 @@ system = 'windows' cpu_family = 'x86' cpu = 'i686' endian = 'little' - -[properties] -prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win32' -prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32' -prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32' diff --git a/cross_win64.txt b/cross_win64.txt index 81bb0309..86364ad6 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -14,8 +14,3 @@ system = 'windows' cpu_family = 'x86' cpu = 'x86_64' endian = 'little' - -[properties] -prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win64' -prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32' -prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64' diff --git a/release.mk b/release.mk index 57fa994e..7a686f49 100644 --- a/release.mk +++ b/release.mk @@ -69,58 +69,62 @@ prepare-deps: @app/prebuilt-deps/prepare-libusb.sh build-win32: prepare-deps - [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ - meson setup "$(WIN32_BUILD_DIR)" \ - --cross-file cross_win32.txt \ - --buildtype release --strip -Db_lto=true \ - -Dcompile_server=false \ - -Dportable=true ) + rm -rf "$(WIN32_BUILD_DIR)" + mkdir -p "$(WIN32_BUILD_DIR)/local" + cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/" + meson setup "$(WIN32_BUILD_DIR)" \ + --pkg-config-path="$(WIN32_BUILD_DIR)/local/lib/pkgconfig" \ + -Dc_args="-I$(PWD)/$(WIN32_BUILD_DIR)/local/include" \ + -Dc_link_args="-L$(PWD)/$(WIN32_BUILD_DIR)/local/lib" \ + --cross-file=cross_win32.txt \ + --buildtype=release --strip -Db_lto=true \ + -Dcompile_server=false \ + -Dportable=true ninja -C "$(WIN32_BUILD_DIR)" build-win64: prepare-deps - [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \ - meson setup "$(WIN64_BUILD_DIR)" \ - --cross-file cross_win64.txt \ - --buildtype release --strip -Db_lto=true \ - -Dcompile_server=false \ - -Dportable=true ) + rm -rf "$(WIN64_BUILD_DIR)" + mkdir -p "$(WIN64_BUILD_DIR)/local" + cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/" + meson setup "$(WIN64_BUILD_DIR)" \ + --pkg-config-path="$(WIN64_BUILD_DIR)/local/lib/pkgconfig" \ + -Dc_args="-I$(PWD)/$(WIN64_BUILD_DIR)/local/include" \ + -Dc_link_args="-L$(PWD)/$(WIN64_BUILD_DIR)/local/lib" \ + --cross-file=cross_win64.txt \ + --buildtype=release --strip -Db_lto=true \ + -Dcompile_server=false \ + -Dportable=true ninja -C "$(WIN64_BUILD_DIR)" dist-win32: build-server build-win32 mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp "$(WIN32_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp "$(WIN64_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)"; \ From 2370298b612bfd86da746a7480b8e159fc92c326 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Nov 2023 18:41:13 +0100 Subject: [PATCH 1037/1133] Download SDL prebuilt binaries from github The server is faster than libsdl.org. --- app/prebuilt-deps/prepare-sdl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index 645646de..f53f090c 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -17,7 +17,7 @@ then exit 0 fi -get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM" +get_file "https://github.com/libsdl-org/SDL/releases/download/release-2.28.4/$FILENAME" "$FILENAME" "$SHA256SUM" mkdir "$DEP_DIR" cd "$DEP_DIR" From 825d7f72c0a81d2aac5594aaa8fe265b468280d4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Nov 2023 21:19:18 +0100 Subject: [PATCH 1038/1133] Extract $VERSION for dependency scripts This will allow to update the version only once in these files. --- app/prebuilt-deps/prepare-libusb.sh | 24 +++++++++++++----------- app/prebuilt-deps/prepare-sdl.sh | 8 +++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh index 6a052f0d..228a5bfa 100755 --- a/app/prebuilt-deps/prepare-libusb.sh +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -6,9 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=libusb-1.0.26 +VERSION=1.0.26 +DEP_DIR="libusb-$VERSION" -FILENAME=libusb-1.0.26-binaries.7z +FILENAME="libusb-$VERSION-binaries.7z" SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5 if [[ -d "$DEP_DIR" ]] @@ -17,20 +18,21 @@ then exit 0 fi -get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM" +get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \ + "$FILENAME" "$SHA256SUM" mkdir "$DEP_DIR" cd "$DEP_DIR" 7z x "../$FILENAME" \ - libusb-1.0.26-binaries/libusb-MinGW-Win32/ \ - libusb-1.0.26-binaries/libusb-MinGW-Win32/ \ - libusb-1.0.26-binaries/libusb-MinGW-x64/ \ - libusb-1.0.26-binaries/libusb-MinGW-x64/ - -mv libusb-1.0.26-binaries/libusb-MinGW-Win32 . -mv libusb-1.0.26-binaries/libusb-MinGW-x64 . -rm -rf libusb-1.0.26-binaries + "libusb-$VERSION-binaries/libusb-MinGW-Win32/" \ + "libusb-$VERSION-binaries/libusb-MinGW-Win32/" \ + "libusb-$VERSION-binaries/libusb-MinGW-x64/" \ + "libusb-$VERSION-binaries/libusb-MinGW-x64/" + +mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" . +mv "libusb-$VERSION-binaries/libusb-MinGW-x64" . +rm -rf "libusb-$VERSION-binaries" # Rename the dll to get the same library name on all platforms mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index f53f090c..580461d2 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,9 +6,10 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -DEP_DIR=SDL2-2.28.4 +VERSION=2.28.4 +DEP_DIR="SDL2-$VERSION" -FILENAME=SDL2-devel-2.28.4-mingw.tar.gz +FILENAME="SDL2-devel-$VERSION-mingw.tar.gz" SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35 if [[ -d "$DEP_DIR" ]] @@ -17,7 +18,8 @@ then exit 0 fi -get_file "https://github.com/libsdl-org/SDL/releases/download/release-2.28.4/$FILENAME" "$FILENAME" "$SHA256SUM" +get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \ + "$FILENAME" "$SHA256SUM" mkdir "$DEP_DIR" cd "$DEP_DIR" From eed06b141aa6cba15f1c163f8ec69adf13118b0e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Nov 2023 21:46:05 +0100 Subject: [PATCH 1039/1133] Upgrade sdl (2.28.5) for Windows Include the latest version of SDL in Windows releases. --- app/prebuilt-deps/prepare-sdl.sh | 4 ++-- release.mk | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh index 580461d2..7569744f 100755 --- a/app/prebuilt-deps/prepare-sdl.sh +++ b/app/prebuilt-deps/prepare-sdl.sh @@ -6,11 +6,11 @@ cd "$DIR" mkdir -p "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR" -VERSION=2.28.4 +VERSION=2.28.5 DEP_DIR="SDL2-$VERSION" FILENAME="SDL2-devel-$VERSION-mingw.tar.gz" -SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35 +SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21 if [[ -d "$DEP_DIR" ]] then diff --git a/release.mk b/release.mk index 7a686f49..fd969e5a 100644 --- a/release.mk +++ b/release.mk @@ -72,7 +72,7 @@ build-win32: prepare-deps rm -rf "$(WIN32_BUILD_DIR)" mkdir -p "$(WIN32_BUILD_DIR)/local" cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/SDL2-2.28.5/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/" cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/" meson setup "$(WIN32_BUILD_DIR)" \ --pkg-config-path="$(WIN32_BUILD_DIR)/local/lib/pkgconfig" \ @@ -88,7 +88,7 @@ build-win64: prepare-deps rm -rf "$(WIN64_BUILD_DIR)" mkdir -p "$(WIN64_BUILD_DIR)/local" cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/" + cp -r app/prebuilt-deps/data/SDL2-2.28.5/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/" cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/" meson setup "$(WIN64_BUILD_DIR)" \ --pkg-config-path="$(WIN64_BUILD_DIR)/local/lib/pkgconfig" \ From 5d4b8a7e6d18ee0e19a764945dc67efbd46f3e97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Nov 2023 23:50:41 +0100 Subject: [PATCH 1040/1133] Fix turn screen off on Android 14 On Android 14, the methods to access the display have been moved to DisplayControl, which is not in the core framework. Use a specific ClassLoader to access this class and its native dependencies. Fixes #3927 Refs #3927 comment Refs #4446 comment PR #4456 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/Device.java | 10 ++- .../scrcpy/wrappers/DisplayControl.java | 80 +++++++++++++++++++ .../scrcpy/wrappers/SurfaceControl.java | 9 +++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 9fbe9239..b51ad8d3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ClipboardManager; +import com.genymobile.scrcpy.wrappers.DisplayControl; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -315,8 +316,12 @@ public final class Device { */ public static boolean setScreenPowerMode(int mode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // On Android 14, these internal methods have been moved to DisplayControl + boolean useDisplayControl = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod(); + // Change the power mode for all physical displays - long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds(); + long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds(); if (physicalDisplayIds == null) { Ln.e("Could not get physical display ids"); return false; @@ -324,7 +329,8 @@ public final class Device { boolean allOk = true; for (long physicalDisplayId : physicalDisplayIds) { - IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); + IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken( + physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); allOk &= SurfaceControl.setDisplayPowerMode(binder, mode); } return allOk; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java new file mode 100644 index 00000000..4e19beb9 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java @@ -0,0 +1,80 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.os.Build; +import android.os.IBinder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) +@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public final class DisplayControl { + + private static final Class CLASS; + + static { + Class displayControlClass = null; + try { + Class classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory"); + Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class, + ClassLoader.class, int.class, boolean.class, String.class); + ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null, + ClassLoader.getSystemClassLoader(), 0, true, null); + + displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl"); + + Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class); + loadMethod.setAccessible(true); + loadMethod.invoke(Runtime.getRuntime(), displayControlClass, "android_servers"); + } catch (Throwable e) { + Ln.e("Could not initialize DisplayControl", e); + // Do not throw an exception here, the methods will fail when they are called + } + CLASS = displayControlClass; + } + + private static Method getPhysicalDisplayTokenMethod; + private static Method getPhysicalDisplayIdsMethod; + + private DisplayControl() { + // only static methods + } + + private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { + if (getPhysicalDisplayTokenMethod == null) { + getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); + } + return getPhysicalDisplayTokenMethod; + } + + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + try { + Method method = getGetPhysicalDisplayTokenMethod(); + return (IBinder) method.invoke(null, physicalDisplayId); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { + if (getPhysicalDisplayIdsMethod == null) { + getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); + } + return getPhysicalDisplayIdsMethod; + } + + public static long[] getPhysicalDisplayIds() { + try { + Method method = getGetPhysicalDisplayIdsMethod(); + return (long[]) method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 595ee6d4..98259e7f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -139,6 +139,15 @@ public final class SurfaceControl { return getPhysicalDisplayIdsMethod; } + public static boolean hasPhysicalDisplayIdsMethod() { + try { + getGetPhysicalDisplayIdsMethod(); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + public static long[] getPhysicalDisplayIds() { try { Method method = getGetPhysicalDisplayIdsMethod(); From 8db4e78b34f9b2b08ce78910389a7f8f4c030f12 Mon Sep 17 00:00:00 2001 From: Kid <44045911+kidonng@users.noreply.github.com> Date: Wed, 22 Nov 2023 02:13:56 +0800 Subject: [PATCH 1041/1133] Fix Linux desktop files There were too many backslashes in the Exec line. Fixes #4367 PR #4448 Signed-off-by: Romain Vimont --- app/data/scrcpy-console.desktop | 2 +- app/data/scrcpy.desktop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index 6ca1e36a..77501456 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy --pause-on-exit=if-error" +Exec=/bin/sh -c "\"\$SHELL\" -i -c scrcpy --pause-on-exit=if-error" Icon=scrcpy Terminal=true Type=Application diff --git a/app/data/scrcpy.desktop b/app/data/scrcpy.desktop index 1be86a2b..4557e71a 100644 --- a/app/data/scrcpy.desktop +++ b/app/data/scrcpy.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy" +Exec=/bin/sh -c "\"\$SHELL\" -i -c scrcpy" Icon=scrcpy Terminal=false Type=Application From 89761213c3dd0d8ebe1ab0e8c2dd04b177b47dc2 Mon Sep 17 00:00:00 2001 From: Kid <44045911+kidonng@users.noreply.github.com> Date: Wed, 22 Nov 2023 02:32:19 +0800 Subject: [PATCH 1042/1133] Do not quote $SHELL in .desktop files This does not work properly on some desktop environments (KDE), and $SHELL is unlikely to require quoting. Fixes #4367 PR #4448 Signed-off-by: Romain Vimont --- app/data/scrcpy-console.desktop | 2 +- app/data/scrcpy.desktop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index 77501456..71c0f001 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c "\"\$SHELL\" -i -c scrcpy --pause-on-exit=if-error" +Exec=/bin/sh -c "\\$SHELL -i -c scrcpy --pause-on-exit=if-error" Icon=scrcpy Terminal=true Type=Application diff --git a/app/data/scrcpy.desktop b/app/data/scrcpy.desktop index 4557e71a..9fb81d47 100644 --- a/app/data/scrcpy.desktop +++ b/app/data/scrcpy.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c "\"\$SHELL\" -i -c scrcpy" +Exec=/bin/sh -c "\\$SHELL -i -c scrcpy" Icon=scrcpy Terminal=false Type=Application From d037b02cc2205a4b4767340347bd562b6e7c68bc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 24 Nov 2023 21:39:46 +0100 Subject: [PATCH 1043/1133] Fix scrcpy-console.desktop The argument passed to scrcpy was not applied, the full command must be passed as a single argument. PR #4448 --- app/data/scrcpy-console.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/scrcpy-console.desktop b/app/data/scrcpy-console.desktop index 71c0f001..fccd42b7 100644 --- a/app/data/scrcpy-console.desktop +++ b/app/data/scrcpy-console.desktop @@ -5,7 +5,7 @@ Comment=Display and control your Android device # For some users, the PATH or ADB environment variables are set from the shell # startup file, like .bashrc or .zshrc… Run an interactive shell to get # environment correctly initialized. -Exec=/bin/sh -c "\\$SHELL -i -c scrcpy --pause-on-exit=if-error" +Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'" Icon=scrcpy Terminal=true Type=Application From 5f3fb843f59b058b7c38b5f7b1a561befc64e706 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Nov 2023 21:38:23 +0100 Subject: [PATCH 1044/1133] Bump version to 2.3 The previous version bump to 2.2 was incorrect, it was updated by: ./bump_version v2.2 instead of: ./bump_version 2.2 Correctly bump to version 2.3. Refs #4433 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 832817d8..4540077c 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "v2.2" + VALUE "ProductVersion", "2.3" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index d1f67e38..43898157 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: 'v2.2', + version: '2.3', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 1bb31360..45bf0cc8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 34 - versionCode 200 - versionName "v2.2" + versionCode 20300 + versionName "2.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 5ab90a0a..9f153e2a 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=v2.2 +SCRCPY_VERSION_NAME=2.3 PLATFORM=${ANDROID_PLATFORM:-34} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} From 5e061636f65a5b95432d8dd5a64b4dcd2b7dd8c9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Nov 2023 22:15:07 +0100 Subject: [PATCH 1045/1133] Update links to v2.3 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b8ef9df8..9a8d688a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v2.2) +# scrcpy (v2.3) scrcpy diff --git a/doc/build.md b/doc/build.md index 15c567b5..91e2fac8 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.2`][direct-scrcpy-server] - SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874` + - [`scrcpy-server-v2.3`][direct-scrcpy-server] + SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index bd4a69f7..93231795 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit) - SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd` - - [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit) - SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74` + - [`scrcpy-win64-v2.3.zip`][direct-win64] (64-bit) + SHA-256: `a2fdd2733bd337261bb493e77d990078a23e7a40149dd0c0dc45725c929a715f` + - [`scrcpy-win32-v2.3.zip`][direct-win32] (32-bit) + SHA-256: `dfdbb69a872d717aed5bcfe352e571564c357fdb7a9c172d69f450fdf5154a0a` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win64-v2.3.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win32-v2.3.zip and extract it. diff --git a/install_release.sh b/install_release.sh index adad85f7..a81b7a45 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2 -PREBUILT_SERVER_SHA256=c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 +PREBUILT_SERVER_SHA256=8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 4135c411af419f4f86dc9ec9301c88012d616c49 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 25 Nov 2023 23:53:30 +0100 Subject: [PATCH 1046/1133] Fix compilation error Fix the following warning/error: ../app/src/cli.c:2158:17: warning: a label can only be part of a statement and a declaration is not a statement [-Wpedantic] With some compilers, this is an error rather than a pedantic warning. Refs --- app/src/cli.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index f57b75ef..fd4525f5 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2154,7 +2154,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; - case OPT_ORIENTATION: + case OPT_ORIENTATION: { enum sc_orientation orientation; if (!parse_orientation(optarg, &orientation)) { return false; @@ -2162,6 +2162,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->display_orientation = orientation; opts->record_orientation = orientation; break; + } case OPT_RENDER_DRIVER: opts->render_driver = optarg; break; From 140a49b8bee1122b28d47b4385eccece7480f18e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 26 Nov 2023 19:20:24 +0100 Subject: [PATCH 1047/1133] Add workaround for Samsung devices issues On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(), which requires a non-null ConfigurationController. Fixes --- .../com/genymobile/scrcpy/Workarounds.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index db9c9629..8781a783 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -49,6 +49,7 @@ public final class Workarounds { } public static void apply(boolean audio, boolean camera) { + boolean mustFillConfigurationController = false; boolean mustFillAppInfo = false; boolean mustFillAppContext = false; @@ -85,11 +86,23 @@ public final class Workarounds { mustFillAppContext = true; } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(), + // which requires a non-null ConfigurationController. + // ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions. + // + mustFillConfigurationController = true; + } + + if (mustFillConfigurationController) { + // Must be call before fillAppContext() because it is necessary to get a valid system context + fillConfigurationController(); + } if (mustFillAppInfo) { - Workarounds.fillAppInfo(); + fillAppInfo(); } if (mustFillAppContext) { - Workarounds.fillAppContext(); + fillAppContext(); } } @@ -149,6 +162,22 @@ public final class Workarounds { } } + private static void fillConfigurationController() { + try { + Class configurationControllerClass = Class.forName("android.app.ConfigurationController"); + Class activityThreadInternalClass = Class.forName("android.app.ActivityThreadInternal"); + Constructor configurationControllerConstructor = configurationControllerClass.getDeclaredConstructor(activityThreadInternalClass); + configurationControllerConstructor.setAccessible(true); + Object configurationController = configurationControllerConstructor.newInstance(ACTIVITY_THREAD); + + Field configurationControllerField = ACTIVITY_THREAD_CLASS.getDeclaredField("mConfigurationController"); + configurationControllerField.setAccessible(true); + configurationControllerField.set(ACTIVITY_THREAD, configurationController); + } catch (Throwable throwable) { + Ln.d("Could not fill configuration: " + throwable.getMessage()); + } + } + static Context getSystemContext() { try { Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext"); From bd9292931e868621294ec64ea85179a2803976ef Mon Sep 17 00:00:00 2001 From: Johannes Neyer Date: Fri, 17 Nov 2023 16:24:34 +0100 Subject: [PATCH 1048/1133] Mention exclusive_caps mode in v4l2 documentation PR #4435 Signed-off-by: Romain Vimont --- doc/v4l2.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/v4l2.md b/doc/v4l2.md index 23c99912..54272b2b 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -21,6 +21,13 @@ This will create a new video device in `/dev/videoN`, where `N` is an integer (more [options](https://github.com/umlaeute/v4l2loopback#options) are available to create several devices or devices with specific IDs). +If you encounter problems detecting your device with Chrome/WebRTC, you can try +`exclusive_caps` mode: + +``` +sudo modprobe v4l2loopback exclusive_caps=1 +``` + To list the enabled devices: ```bash From bf056b1fee904391bc932381c01052fb975ac62e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Nov 2023 12:14:07 +0100 Subject: [PATCH 1049/1133] Do not initialize SDL video when not necessary The SDL video subsystem is required for video playback and clipboard synchronization. If neither is used, it is not necessary to initialize it. Refs 5e59ed31352251791679e5931d7e5abf0c2d18f6 Refs 110b3a16f6d02124a4567d2ab79fcb74d78f949f Refs #4418 Refs #4477 --- app/src/scrcpy.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d62a5f52..0b0b8bad 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -419,12 +419,16 @@ scrcpy(struct scrcpy_options *options) { sdl_set_hints(options->render_driver); } - // Initialize the video subsystem even if --no-video or --no-video-playback - // is passed so that clipboard synchronization still works. - // - if (SDL_Init(SDL_INIT_VIDEO)) { - LOGE("Could not initialize SDL video: %s", SDL_GetError()); - goto end; + if (options->video_playback || + (options->control && options->clipboard_autosync)) { + // Initialize the video subsystem even if --no-video or + // --no-video-playback is passed so that clipboard synchronization + // still works. + // + if (SDL_Init(SDL_INIT_VIDEO)) { + LOGE("Could not initialize SDL video: %s", SDL_GetError()); + goto end; + } } if (options->audio_playback) { From 9497f39fb47e899df1247942f90eba5daa0cf204 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 29 Nov 2023 12:16:05 +0100 Subject: [PATCH 1050/1133] Do not fail if SDL_INIT_VIDEO fails without video The SDL video subsystem may be initialized so that clipboard synchronization works even without video playback. But if the video subsystem initialization fails (e.g. because no video device is available), consider it as an error only if video playback is enabled. Refs 5e59ed31352251791679e5931d7e5abf0c2d18f6 Fixes #4477 --- app/src/scrcpy.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0b0b8bad..cf2e7e47 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -426,8 +426,13 @@ scrcpy(struct scrcpy_options *options) { // still works. // if (SDL_Init(SDL_INIT_VIDEO)) { - LOGE("Could not initialize SDL video: %s", SDL_GetError()); - goto end; + // If it fails, it is an error only if video playback is enabled + if (options->video_playback) { + LOGE("Could not initialize SDL video: %s", SDL_GetError()); + goto end; + } else { + LOGW("Could not initialize SDL video: %s", SDL_GetError()); + } } } From ef79fcbbd27d9f6e8096c278d2975c7c71bc67b9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Dec 2023 21:13:01 +0100 Subject: [PATCH 1051/1133] Fix AV1 demuxing For AV1, the config packet must not be merged with the next non-config packet. This fixes the following error when passing --video-codec=av1: > INFO: [FFmpeg] libdav1d 1.3.0 > ERROR: [FFmpeg] Unknown OBU type 0 of size 29393 > ERROR: [FFmpeg] Error parsing OBU data > ERROR: Decoder 'video': could not send video packet: -1094995529 PR #4487 --- app/src/demuxer.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c9ee8f3c..c27ea292 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -227,8 +227,9 @@ run_demuxer(void *data) { } // Config packets must be merged with the next non-config packet only for - // video streams - bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO; + // H.26x + bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264 + || raw_codec_id == SC_CODEC_ID_H265; struct sc_packet_merger merger; From 40f2560d987fbc88711b3aac14398d38c0e2a8f6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 Dec 2023 12:30:19 +0100 Subject: [PATCH 1052/1133] Bump version to 2.3.1 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 4540077c..895b9c93 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.3" + VALUE "ProductVersion", "2.3.1" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 43898157..11b974e0 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.3', + version: '2.3.1', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 45bf0cc8..1a18d997 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 34 - versionCode 20300 - versionName "2.3" + versionCode 20301 + versionName "2.3.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 9f153e2a..69d85679 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.3 +SCRCPY_VERSION_NAME=2.3.1 PLATFORM=${ANDROID_PLATFORM:-34} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} From c6ff78f4147ad5505b45cce8f1d6f44c8585a9b5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 Dec 2023 12:39:05 +0100 Subject: [PATCH 1053/1133] Update links to v2.3.1 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9a8d688a..8fabd556 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# scrcpy (v2.3) +# scrcpy (v2.3.1) scrcpy diff --git a/doc/build.md b/doc/build.md index 91e2fac8..7e3c84e9 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.3`][direct-scrcpy-server] - SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f` + - [`scrcpy-server-v2.3.1`][direct-scrcpy-server] + SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index 93231795..60fd7986 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.3.zip`][direct-win64] (64-bit) - SHA-256: `a2fdd2733bd337261bb493e77d990078a23e7a40149dd0c0dc45725c929a715f` - - [`scrcpy-win32-v2.3.zip`][direct-win32] (32-bit) - SHA-256: `dfdbb69a872d717aed5bcfe352e571564c357fdb7a9c172d69f450fdf5154a0a` + - [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit) + SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d` + - [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit) + SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win64-v2.3.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win32-v2.3.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip and extract it. diff --git a/install_release.sh b/install_release.sh index a81b7a45..d8dbd951 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 -PREBUILT_SERVER_SHA256=8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1 +PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 3001f8a2d581a921920d18c088d42e0cd747bf41 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Dec 2023 18:00:05 +0100 Subject: [PATCH 1054/1133] Adapt AudioRecord workaround to Android 14 Android 14 added a new int parameter "halInputFlags" to an internal method: Fixes #4492 --- .../com/genymobile/scrcpy/Workarounds.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 8781a783..448e7099 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -285,16 +285,28 @@ public final class Workarounds { Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel"); Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState); - // private native int native_setup(Object audiorecordThis, - // Object /*AudioAttributes*/ attributes, - // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, - // int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, - // long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); - Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class, - int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class); - nativeSetupMethod.setAccessible(true); - initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, sampleRateArray, - channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // private native int native_setup(Object audiorecordThis, + // Object /*AudioAttributes*/ attributes, + // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, + // int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, + // long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, + int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, + sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, + attributionSourceParcel, 0L, 0); + } else { + // Android 14 added a new int parameter "halInputFlags" + // + Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, + int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class, int.class); + nativeSetupMethod.setAccessible(true); + initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference(audioRecord), attributes, + sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, + attributionSourceParcel, 0L, 0, 0); + } } } From 5ce8672ebc56b7286e1078a39abc64903e5664d0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Dec 2023 21:22:30 +0100 Subject: [PATCH 1055/1133] Add clipboard workaround for IQOO device Fixes #4492 --- .../scrcpy/wrappers/ClipboardManager.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 783a3407..0866d42d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -41,8 +41,13 @@ public final class ClipboardManager { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); getMethodVersion = 2; } catch (NoSuchMethodException e3) { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); - getMethodVersion = 3; + try { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); + getMethodVersion = 3; + } catch (NoSuchMethodException e4) { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); + getMethodVersion = 4; + } } } } @@ -87,8 +92,11 @@ public final class ClipboardManager { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); case 2: return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); - default: + case 3: return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null); + default: + // The last boolean parameter is "userOperate" + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true); } } From 1beec99f8283713b1fbf0b3704eb4dceecc9a590 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Dec 2023 10:48:53 +0100 Subject: [PATCH 1056/1133] Explicitly exit cleanup process This avoids an internal crash reported in `adb logcat`. Refs #4456 --- server/src/main/java/com/genymobile/scrcpy/CleanUp.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index b3a1aac1..c84e25bb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -187,5 +187,7 @@ public final class CleanUp { Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); } } + + System.exit(0); } } From c9a4d2b38f318a4887e3db48a7d1bcf0affc0250 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Dec 2023 19:14:22 +0100 Subject: [PATCH 1057/1133] Use up-to-date values on display fold change When a display is folded or unfolded, the maxSize may have been updated since the option was passed, and deviceSize must be updated. Refs #4469 --- server/src/main/java/com/genymobile/scrcpy/Device.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index b51ad8d3..2324ce90 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -45,11 +45,11 @@ public final class Device { void onClipboardTextChanged(String text); } - private final Size deviceSize; private final Rect crop; private int maxSize; private final int lockVideoOrientation; + private Size deviceSize; private ScreenInfo screenInfo; private RotationListener rotationListener; private FoldListener foldListener; @@ -116,8 +116,8 @@ public final class Device { return; } - screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), - options.getMaxSize(), options.getLockVideoOrientation()); + deviceSize = displayInfo.getSize(); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); // notify if (foldListener != null) { foldListener.onFoldChanged(displayId, folded); From 5a6b8310cae1e0741b4375ca760d9c8dd49822c4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 13 Dec 2023 12:50:06 +0100 Subject: [PATCH 1058/1133] Add note about official website --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8fabd556..0a3d03cb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +**This GitHub repo () is the only official +source for the project. Do not download releases from random websites, even if +their name contains `scrcpy`.** + # scrcpy (v2.3.1) scrcpy From cbce42336dce0bb4cd2c47ed7dfa56d40fc3aca1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 13 Dec 2023 13:41:34 +0100 Subject: [PATCH 1059/1133] Fix manpage syntax The '-' character must be escaped. Fixes #4528 --- app/scrcpy.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0c34b4e2..8ca4a773 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -124,7 +124,7 @@ Use USB device (if there is exactly one, like adb -d). Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). .TP -.BI "\-\-disable-screensaver" +.BI "\-\-disable\-screensaver" Disable screensaver while scrcpy is running. .TP From af69689ec1d52b442b0a4e1461ca71853c660cc6 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 14 Dec 2023 21:21:57 +0530 Subject: [PATCH 1060/1133] Fix bash completion syntax PR #4532 Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0c854310..a0490157 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -115,8 +115,7 @@ _scrcpy() { COMPREPLY=($(compgen -W 'front back external' -- "$cur")) return ;; - --orientation - --display-orientation) + --orientation|--display-orientation) COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return ;; From 604dfd7c6b07f583521c92f8391d3fdb6b1576e8 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 14 Dec 2023 21:25:32 +0530 Subject: [PATCH 1061/1133] Fix incorrect compgen usage PR #4532 Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a0490157..78aa539d 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -116,11 +116,11 @@ _scrcpy() { return ;; --orientation|--display-orientation) - COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) + COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return ;; --record-orientation) - COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur")) + COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur")) return ;; --lock-video-orientation) From d2ed4510a76e0d273f9088bce6e85334b922c9cb Mon Sep 17 00:00:00 2001 From: Till Rathmann Date: Wed, 13 Dec 2023 17:04:02 +0100 Subject: [PATCH 1062/1133] Simulate tilt multitouch event by pressing Shift PR #4529 Signed-off-by: Romain Vimont --- app/scrcpy.1 | 6 +++++- app/src/cli.c | 6 +++++- app/src/input_manager.c | 40 ++++++++++++++++++++++++++++++++-------- app/src/input_manager.h | 2 ++ doc/control.md | 8 ++++++-- doc/shortcuts.md | 3 ++- 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 8ca4a773..beaa99ab 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -642,7 +642,11 @@ Enable/disable FPS counter (print frames/second in logs) .TP .B Ctrl+click-and-move -Pinch-to-zoom from the center of the screen +Pinch-to-zoom and rotate from the center of the screen + +.TP +.B Shift+click-and-move +Tilt (slide vertically with two fingers) .TP .B Drag & drop APK file diff --git a/app/src/cli.c b/app/src/cli.c index fd4525f5..c580c959 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -947,7 +947,11 @@ static const struct sc_shortcut shortcuts[] = { }, { .shortcuts = { "Ctrl+click-and-move" }, - .text = "Pinch-to-zoom from the center of the screen", + .text = "Pinch-to-zoom and rotate from the center of the screen", + }, + { + .shortcuts = { "Shift+click-and-move" }, + .text = "Tilt (slide vertically with two fingers)", }, { .shortcuts = { "Drag & drop APK file" }, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 9a487836..76cfbd92 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -76,6 +76,8 @@ sc_input_manager_init(struct sc_input_manager *im, im->sdl_shortcut_mods.count = shortcut_mods->count; im->vfinger_down = false; + im->vfinger_invert_x = false; + im->vfinger_invert_y = false; im->last_keycode = SDLK_UNKNOWN; im->last_mod = 0; @@ -347,9 +349,14 @@ simulate_virtual_finger(struct sc_input_manager *im, } static struct sc_point -inverse_point(struct sc_point point, struct sc_size size) { - point.x = size.width - point.x; - point.y = size.height - point.y; +inverse_point(struct sc_point point, struct sc_size size, + bool invert_x, bool invert_y) { + if (invert_x) { + point.x = size.width - point.x; + } + if (invert_y) { + point.y = size.height - point.y; + } return point; } @@ -605,7 +612,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im, struct sc_point mouse = sc_screen_convert_window_to_frame_coords(im->screen, event->x, event->y); - struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); + struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size, + im->vfinger_invert_x, + im->vfinger_invert_y); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } } @@ -726,7 +735,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, return; } - // Pinch-to-zoom simulation. + // Pinch-to-zoom, rotate and tilt simulation. // // If Ctrl is hold when the left-click button is pressed, then // pinch-to-zoom mode is enabled: on every mouse event until the left-click @@ -735,14 +744,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, // // In other words, the center of the rotation/scaling is the center of the // screen. -#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) + // + // To simulate a tilt gesture (a vertical slide with two fingers), Shift + // can be used instead of Ctrl. The "virtual finger" has a position + // inverted with respect to the vertical axis of symmetry in the middle of + // the screen. + const SDL_Keymod keymod = SDL_GetModState(); + const bool ctrl_pressed = keymod & KMOD_CTRL; + const bool shift_pressed = keymod & KMOD_SHIFT; if (event->button == SDL_BUTTON_LEFT && - ((down && !im->vfinger_down && CTRL_PRESSED) || + ((down && !im->vfinger_down && + ((ctrl_pressed && !shift_pressed) || + (!ctrl_pressed && shift_pressed))) || (!down && im->vfinger_down))) { struct sc_point mouse = sc_screen_convert_window_to_frame_coords(im->screen, event->x, event->y); - struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); + if (down) { + im->vfinger_invert_x = ctrl_pressed || shift_pressed; + im->vfinger_invert_y = ctrl_pressed; + } + struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size, + im->vfinger_invert_x, + im->vfinger_invert_y); enum android_motionevent_action action = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index b5a762eb..2ce11b03 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -32,6 +32,8 @@ struct sc_input_manager { } sdl_shortcut_mods; bool vfinger_down; + bool vfinger_invert_x; + bool vfinger_invert_y; // Tracks the number of identical consecutive shortcut key down events. // Not to be confused with event->repeat, which counts the number of diff --git a/doc/control.md b/doc/control.md index 0b060775..595e910e 100644 --- a/doc/control.md +++ b/doc/control.md @@ -85,7 +85,7 @@ way as MOD+Shift+v). To disable automatic clipboard synchronization, use `--no-clipboard-autosync`. -## Pinch-to-zoom +## Pinch-to-zoom, rotate and tilt simulation To simulate "pinch-to-zoom": Ctrl+_click-and-move_. @@ -93,8 +93,12 @@ More precisely, hold down Ctrl while pressing the left-click button. Until the left-click button is released, all mouse movements scale and rotate the content (if supported by the app) relative to the center of the screen. +To simulate a tilt gesture: Shift+_click-and-move-up-or-down_. + Technically, _scrcpy_ generates additional touch events from a "virtual finger" -at a location inverted through the center of the screen. +at a location inverted through the center of the screen. When pressing +Ctrl the x and y coordinates are inverted. Using Shift +only inverts x. ## Key repeat diff --git a/doc/shortcuts.md b/doc/shortcuts.md index c0fc2842..21bccbd9 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -49,7 +49,8 @@ _[Super] is typically the Windows or Cmd key._ | Synchronize clipboards and paste⁵ | MOD+v | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i - | Pinch-to-zoom | Ctrl+_click-and-move_ + | Pinch-to-zoom/rotate | Ctrl+_click-and-move_ + | Tilt (slide vertically with 2 fingers) | Shift+_click-and-move_ | Drag & drop APK file | Install APK from computer | Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device) From 4cd61b5a9001043f1054502f0c29465707260cb1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 16 Dec 2023 20:12:58 +0100 Subject: [PATCH 1063/1133] Fix checkstyle violation Reported by checkstyle: > [ant:checkstyle] [INFO] > scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java:48: > Line is longer than 150 characters (found 167). [LineLength] --- .../java/com/genymobile/scrcpy/wrappers/ClipboardManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 0866d42d..15f0ee74 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -45,7 +45,8 @@ public final class ClipboardManager { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); getMethodVersion = 3; } catch (NoSuchMethodException e4) { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); + getPrimaryClipMethod = manager.getClass() + .getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); getMethodVersion = 4; } } From ec41896c853325d670e361f641f4490ed71b641f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 16 Dec 2023 20:06:45 +0100 Subject: [PATCH 1064/1133] Fix integer overflow for audio packet duration The result is assigned to a long (64-bit signed integer), but the intermediate multiplication was stored in an int (32-bit signed integer). This value is only used as a fallback when no timestamp could be retrieved, that's why it did not cause too much harm so far. Fixes #4536 --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index e3de50e6..76e2f63b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -159,7 +159,7 @@ public final class AudioCapture { pts = nextPts; } - long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); + long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); nextPts = pts + durationUs; if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { From 6a58891e13bc3d5b531aa93718591ec495c4fb6f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 16 Dec 2023 20:09:08 +0100 Subject: [PATCH 1065/1133] Use current time as initial timestamp on error If the initial timestamp could not be retrieved, use the current time as returned by System.nanoTime(). In practice, it is the same time base as AudioRecord timestamps. Fixes #4536 --- server/src/main/java/com/genymobile/scrcpy/AudioCapture.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 76e2f63b..45634c70 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -153,7 +153,8 @@ public final class AudioCapture { previousRecorderTimestamp = timestamp.nanoTime; } else { if (nextPts == 0) { - Ln.w("Could not get any audio timestamp"); + Ln.w("Could not get initial audio timestamp"); + nextPts = System.nanoTime() / 1000; } // compute from previous timestamp and packet size pts = nextPts; From cd4056d0f333aee26efa8ba083e08c59b6d63a70 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 2 Jan 2024 10:22:06 +0100 Subject: [PATCH 1066/1133] Fix include formatting --- app/src/audio_player.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/audio_player.h b/app/src/audio_player.h index a03e9e35..30378246 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -4,16 +4,16 @@ #include "common.h" #include -#include "trait/frame_sink.h" -#include -#include -#include -#include - #include #include #include +#include "trait/frame_sink.h" +#include "util/audiobuf.h" +#include "util/average.h" +#include "util/thread.h" +#include "util/tick.h" + struct sc_audio_player { struct sc_frame_sink frame_sink; From d067a11478e5a6312325bdd051c4ffd1362945c5 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Jan 2024 21:06:09 +0100 Subject: [PATCH 1067/1133] Do not power on if no video Power on the device on start only if video capture is enabled. Note that it only impacts display mirroring, since control is completely disabled if video source is camera. Refs 110b3a16f6d02124a4567d2ab79fcb74d78f949f --- app/src/cli.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index c580c959..f7d7e390 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2398,6 +2398,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], if (!opts->video) { opts->video_playback = false; + // Do not power on the device on start if video capture is disabled + opts->power_on = false; } if (!opts->audio) { From 2ad93d1fc0094ba8263a05f0b162f2607aa68ea9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 15 Jan 2024 22:01:19 +0100 Subject: [PATCH 1068/1133] Fix scrcpy_otg() return value on error The function now returns an enum scrcpy_exit_code, not a bool. --- app/src/usb/scrcpy_otg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 6a7fd79b..dfb0b9e9 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -62,7 +62,7 @@ scrcpy_otg(struct scrcpy_options *options) { // Minimal SDL initialization if (SDL_Init(SDL_INIT_EVENTS)) { LOGE("Could not initialize SDL: %s", SDL_GetError()); - return false; + return SCRCPY_EXIT_FAILURE; } atexit(SDL_Quit); From 5187f7254e4b556e3731fdb77fda68ae2e9fddce Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 16 Jan 2024 18:59:36 +0100 Subject: [PATCH 1069/1133] Add another clipboard workaround for IQOO device Fixes #4589 Refs 5ce8672ebc56b7286e1078a39abc64903e5664d0 Refs #4492 --- .../scrcpy/wrappers/ClipboardManager.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 15f0ee74..daea6db3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -45,9 +45,16 @@ public final class ClipboardManager { getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); getMethodVersion = 3; } catch (NoSuchMethodException e4) { - getPrimaryClipMethod = manager.getClass() - .getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); - getMethodVersion = 4; + try { + getPrimaryClipMethod = manager.getClass() + .getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); + getMethodVersion = 4; + } catch (NoSuchMethodException e5) { + getPrimaryClipMethod = manager.getClass() + .getMethod("getPrimaryClip", String.class, String.class, String.class, String.class, int.class, int.class, + boolean.class); + getMethodVersion = 5; + } } } } @@ -95,9 +102,11 @@ public final class ClipboardManager { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); case 3: return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null); - default: + case 4: // The last boolean parameter is "userOperate" return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true); + default: + return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, null, null, FakeContext.ROOT_UID, 0, true); } } From 7c53a29d72cb0725e960c1b92732193251984558 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 26 Jan 2024 13:13:14 +0100 Subject: [PATCH 1070/1133] Remove useless run script This script was outdated and redundant with ./run. --- meson.build | 2 -- scripts/run-scrcpy.sh | 2 -- 2 files changed, 4 deletions(-) delete mode 100755 scripts/run-scrcpy.sh diff --git a/meson.build b/meson.build index 11b974e0..4ae91f69 100644 --- a/meson.build +++ b/meson.build @@ -16,5 +16,3 @@ endif if get_option('compile_server') subdir('server') endif - -run_target('run', command: ['scripts/run-scrcpy.sh']) diff --git a/scripts/run-scrcpy.sh b/scripts/run-scrcpy.sh deleted file mode 100755 index e93b639f..00000000 --- a/scripts/run-scrcpy.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy" From 3333e67452e52a1e0cd1c68181067f9eccdeb582 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Feb 2024 09:18:14 +0100 Subject: [PATCH 1071/1133] Fix memory leak on error Fixes #4636 --- app/src/adb/adb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 54375451..15c9c85a 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -458,6 +458,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, // in the buffer in a single pass LOGW("Result of \"adb devices -l\" does not fit in 64Kb. " "Please report an issue."); + free(buf); return false; } From d25cbc55f2238e2f9e621c83f27d675a7de913aa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Feb 2024 18:32:48 +0100 Subject: [PATCH 1072/1133] Remove unused field --- .../java/com/genymobile/scrcpy/wrappers/ContentProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 8171988e..89c1d0e2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -42,8 +42,6 @@ public final class ContentProvider implements Closeable { private Method callMethod; private int callMethodVersion; - private Object attributionSource; - ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { this.manager = manager; this.provider = provider; From 05b5deacadf25f706a62b981a1558c75b37dea93 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Feb 2024 18:36:27 +0100 Subject: [PATCH 1073/1133] Move service managers creation Create the service managers from each manager wrapper class rather than from their getter in ServiceManager. The way a wrapper retrieve the underlying service is an implementation detail, and it must be consistent with the way it accesses it, so it is better to write the creation in the wrapper. --- .../scrcpy/wrappers/ActivityManager.java | 15 ++++- .../scrcpy/wrappers/ClipboardManager.java | 13 ++++- .../scrcpy/wrappers/DisplayManager.java | 17 +++++- .../scrcpy/wrappers/InputManager.java | 24 +++++++- .../scrcpy/wrappers/PowerManager.java | 7 ++- .../scrcpy/wrappers/ServiceManager.java | 58 +++---------------- .../scrcpy/wrappers/StatusBarManager.java | 7 ++- .../scrcpy/wrappers/WindowManager.java | 7 ++- 8 files changed, 92 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 75115618..fd0a7798 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -26,7 +26,20 @@ public final class ActivityManager { private Method startActivityAsUserWithFeatureMethod; private Method forceStopPackageMethod; - public ActivityManager(IInterface manager) { + static ActivityManager create() { + try { + // On old Android versions, the ActivityManager is not exposed via AIDL, + // so use ActivityManagerNative.getDefault() + Class cls = Class.forName("android.app.ActivityManagerNative"); + Method getDefaultMethod = cls.getDeclaredMethod("getDefault"); + IInterface am = (IInterface) getDefaultMethod.invoke(null); + return new ActivityManager(am); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private ActivityManager(IInterface manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index daea6db3..2a09b200 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -20,7 +20,18 @@ public final class ClipboardManager { private int setMethodVersion; private int addListenerMethodVersion; - public ClipboardManager(IInterface manager) { + static ClipboardManager create() { + IInterface clipboard = ServiceManager.getService("clipboard", "android.content.IClipboard"); + if (clipboard == null) { + // Some devices have no clipboard manager + // + // + return null; + } + return new ClipboardManager(clipboard); + } + + private ClipboardManager(IInterface manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 17b9ae4d..80785a9f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -5,16 +5,31 @@ import com.genymobile.scrcpy.DisplayInfo; import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; +import android.annotation.SuppressLint; import android.view.Display; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.regex.Matcher; import java.util.regex.Pattern; +@SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class DisplayManager { private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal - public DisplayManager(Object manager) { + static DisplayManager create() { + try { + Class clazz = Class.forName("android.hardware.display.DisplayManagerGlobal"); + Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); + Object dmg = getInstanceMethod.invoke(null); + return new DisplayManager(dmg); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + } + + private DisplayManager(Object manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index ef0a4f50..c7c72dc9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -2,12 +2,14 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; +import android.annotation.SuppressLint; import android.view.InputEvent; import android.view.MotionEvent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +@SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class InputManager { public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; @@ -20,7 +22,27 @@ public final class InputManager { private static Method setDisplayIdMethod; private static Method setActionButtonMethod; - public InputManager(Object manager) { + static InputManager create() { + try { + Class inputManagerClass = getInputManagerClass(); + Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); + Object im = getInstanceMethod.invoke(null); + return new InputManager(im); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + } + + private static Class getInputManagerClass() { + try { + // Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview + return Class.forName("android.hardware.input.InputManagerGlobal"); + } catch (ClassNotFoundException e) { + return android.hardware.input.InputManager.class; + } + } + + private InputManager(Object manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java index 93722687..942a5880 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java @@ -13,7 +13,12 @@ public final class PowerManager { private final IInterface manager; private Method isScreenOnMethod; - public PowerManager(IInterface manager) { + static PowerManager create() { + IInterface manager = ServiceManager.getService("power", "android.os.IPowerManager"); + return new PowerManager(manager); + } + + private PowerManager(IInterface manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 85602c19..a8a56dab 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -9,7 +9,6 @@ import android.os.IBinder; import android.os.IInterface; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -38,7 +37,7 @@ public final class ServiceManager { /* not instantiable */ } - private static IInterface getService(String service, String type) { + static IInterface getService(String service, String type) { try { IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service); Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class); @@ -50,90 +49,51 @@ public final class ServiceManager { public static WindowManager getWindowManager() { if (windowManager == null) { - windowManager = new WindowManager(getService("window", "android.view.IWindowManager")); + windowManager = WindowManager.create(); } return windowManager; } public static DisplayManager getDisplayManager() { if (displayManager == null) { - try { - Class clazz = Class.forName("android.hardware.display.DisplayManagerGlobal"); - Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); - Object dmg = getInstanceMethod.invoke(null); - displayManager = new DisplayManager(dmg); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new AssertionError(e); - } + displayManager = DisplayManager.create(); } return displayManager; } - public static Class getInputManagerClass() { - try { - // Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview - return Class.forName("android.hardware.input.InputManagerGlobal"); - } catch (ClassNotFoundException e) { - return android.hardware.input.InputManager.class; - } - } - public static InputManager getInputManager() { if (inputManager == null) { - try { - Class inputManagerClass = getInputManagerClass(); - Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); - Object im = getInstanceMethod.invoke(null); - inputManager = new InputManager(im); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new AssertionError(e); - } + inputManager = InputManager.create(); } return inputManager; } public static PowerManager getPowerManager() { if (powerManager == null) { - powerManager = new PowerManager(getService("power", "android.os.IPowerManager")); + powerManager = PowerManager.create(); } return powerManager; } public static StatusBarManager getStatusBarManager() { if (statusBarManager == null) { - statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService")); + statusBarManager = StatusBarManager.create(); } return statusBarManager; } public static ClipboardManager getClipboardManager() { if (clipboardManager == null) { - IInterface clipboard = getService("clipboard", "android.content.IClipboard"); - if (clipboard == null) { - // Some devices have no clipboard manager - // - // - return null; - } - clipboardManager = new ClipboardManager(clipboard); + // May be null, some devices have no clipboard manager + clipboardManager = ClipboardManager.create(); } return clipboardManager; } public static ActivityManager getActivityManager() { if (activityManager == null) { - try { - // On old Android versions, the ActivityManager is not exposed via AIDL, - // so use ActivityManagerNative.getDefault() - Class cls = Class.forName("android.app.ActivityManagerNative"); - Method getDefaultMethod = cls.getDeclaredMethod("getDefault"); - IInterface am = (IInterface) getDefaultMethod.invoke(null); - activityManager = new ActivityManager(am); - } catch (Exception e) { - throw new AssertionError(e); - } + activityManager = ActivityManager.create(); } - return activityManager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java index 9126d5ed..e65cef5c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -16,7 +16,12 @@ public final class StatusBarManager { private boolean expandSettingsPanelMethodNewVersion = true; private Method collapsePanelsMethod; - public StatusBarManager(IInterface manager) { + static StatusBarManager create() { + IInterface manager = ServiceManager.getService("statusbar", "com.android.internal.statusbar.IStatusBarService"); + return new StatusBarManager(manager); + } + + private StatusBarManager(IInterface manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index a746be5c..99b9148f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -17,7 +17,12 @@ public final class WindowManager { private Method isRotationFrozenMethod; private Method thawRotationMethod; - public WindowManager(IInterface manager) { + static WindowManager create() { + IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); + return new WindowManager(manager); + } + + private WindowManager(IInterface manager) { this.manager = manager; } From f7b4a18b4398c013b2bed9630b7bb35eb50ad97e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 Feb 2024 18:39:57 +0100 Subject: [PATCH 1074/1133] Catch generic ReflectiveOperationException This exception is a super-type of: - ClassNotFoundException - IllegalAccessException - InstantiationException - InvocationTargetException - NoSuchFieldException - NoSuchMethodException Use it to simplify. --- .../scrcpy/wrappers/ActivityManager.java | 7 +++---- .../scrcpy/wrappers/ClipboardManager.java | 15 ++++++--------- .../scrcpy/wrappers/ContentProvider.java | 6 ++---- .../scrcpy/wrappers/DisplayControl.java | 5 ++--- .../scrcpy/wrappers/DisplayManager.java | 9 ++++----- .../genymobile/scrcpy/wrappers/InputManager.java | 9 ++++----- .../genymobile/scrcpy/wrappers/PowerManager.java | 3 +-- .../scrcpy/wrappers/StatusBarManager.java | 7 +++---- .../scrcpy/wrappers/SurfaceControl.java | 9 ++++----- .../genymobile/scrcpy/wrappers/WindowManager.java | 9 ++++----- 10 files changed, 33 insertions(+), 46 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index fd0a7798..367ea2e7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -13,7 +13,6 @@ import android.os.IBinder; import android.os.IInterface; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -34,7 +33,7 @@ public final class ActivityManager { Method getDefaultMethod = cls.getDeclaredMethod("getDefault"); IInterface am = (IInterface) getDefaultMethod.invoke(null); return new ActivityManager(am); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } @@ -89,7 +88,7 @@ public final class ActivityManager { return null; } return new ContentProvider(this, provider, name, token); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | NoSuchFieldException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -99,7 +98,7 @@ public final class ActivityManager { try { Method method = getRemoveContentProviderExternalMethod(); method.invoke(manager, name, token); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 2a09b200..2c8d9907 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -8,7 +8,6 @@ import android.content.IOnPrimaryClipChangedListener; import android.os.Build; import android.os.IInterface; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class ClipboardManager { @@ -98,8 +97,7 @@ public final class ClipboardManager { return setPrimaryClipMethod; } - private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) - throws InvocationTargetException, IllegalAccessException { + private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) throws ReflectiveOperationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME); } @@ -121,8 +119,7 @@ public final class ClipboardManager { } } - private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) - throws InvocationTargetException, IllegalAccessException { + private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) throws ReflectiveOperationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, clipData, FakeContext.PACKAGE_NAME); return; @@ -149,7 +146,7 @@ public final class ClipboardManager { return null; } return clipData.getItemAt(0).getText(); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -161,14 +158,14 @@ public final class ClipboardManager { ClipData clipData = ClipData.newPlainText(null, text); setPrimaryClip(method, setMethodVersion, manager, clipData); return true; - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } } private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener) - throws InvocationTargetException, IllegalAccessException { + throws ReflectiveOperationException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { method.invoke(manager, listener, FakeContext.PACKAGE_NAME); return; @@ -220,7 +217,7 @@ public final class ClipboardManager { Method method = getAddPrimaryClipChangedListener(); addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener); return true; - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java index 89c1d0e2..a03f824e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java @@ -11,7 +11,6 @@ import android.os.Bundle; import android.os.IBinder; import java.io.Closeable; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class ContentProvider implements Closeable { @@ -75,8 +74,7 @@ public final class ContentProvider implements Closeable { return callMethod; } - private Bundle call(String callMethod, String arg, Bundle extras) - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + private Bundle call(String callMethod, String arg, Bundle extras) throws ReflectiveOperationException { try { Method method = getCallMethod(); Object[] args; @@ -97,7 +95,7 @@ public final class ContentProvider implements Closeable { } } return (Bundle) method.invoke(provider, args); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); throw e; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java index 4e19beb9..ba3e9ee0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java @@ -7,7 +7,6 @@ import android.annotation.TargetApi; import android.os.Build; import android.os.IBinder; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) @@ -55,7 +54,7 @@ public final class DisplayControl { try { Method method = getGetPhysicalDisplayTokenMethod(); return (IBinder) method.invoke(null, physicalDisplayId); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -72,7 +71,7 @@ public final class DisplayControl { try { Method method = getGetPhysicalDisplayIdsMethod(); return (long[]) method.invoke(null); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 80785a9f..33a061ba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -9,7 +9,6 @@ import android.annotation.SuppressLint; import android.view.Display; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -24,7 +23,7 @@ public final class DisplayManager { Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); Object dmg = getInstanceMethod.invoke(null); return new DisplayManager(dmg); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } @@ -75,7 +74,7 @@ public final class DisplayManager { try { Field filed = Display.class.getDeclaredField(flagString); flags |= filed.getInt(null); - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (ReflectiveOperationException e) { // Silently ignore, some flags reported by "dumpsys display" are @TestApi } } @@ -97,7 +96,7 @@ public final class DisplayManager { int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); int flags = cls.getDeclaredField("flags").getInt(displayInfo); return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } @@ -105,7 +104,7 @@ public final class DisplayManager { public int[] getDisplayIds() { try { return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index c7c72dc9..16ecb09f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -6,7 +6,6 @@ import android.annotation.SuppressLint; import android.view.InputEvent; import android.view.MotionEvent; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -28,7 +27,7 @@ public final class InputManager { Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); Object im = getInstanceMethod.invoke(null); return new InputManager(im); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } @@ -57,7 +56,7 @@ public final class InputManager { try { Method method = getInjectInputEventMethod(); return (boolean) method.invoke(manager, inputEvent, mode); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } @@ -75,7 +74,7 @@ public final class InputManager { Method method = getSetDisplayIdMethod(); method.invoke(inputEvent, displayId); return true; - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Cannot associate a display id to the input event", e); return false; } @@ -93,7 +92,7 @@ public final class InputManager { Method method = getSetActionButtonMethod(); method.invoke(motionEvent, actionButton); return true; - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Cannot set action button on MotionEvent", e); return false; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java index 942a5880..36d5f1ac 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/PowerManager.java @@ -6,7 +6,6 @@ import android.annotation.SuppressLint; import android.os.Build; import android.os.IInterface; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class PowerManager { @@ -35,7 +34,7 @@ public final class PowerManager { try { Method method = getIsScreenOnMethod(); return (boolean) method.invoke(manager); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java index e65cef5c..af217da2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -4,7 +4,6 @@ import com.genymobile.scrcpy.Ln; import android.os.IInterface; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class StatusBarManager { @@ -67,7 +66,7 @@ public final class StatusBarManager { } else { method.invoke(manager); } - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } @@ -82,7 +81,7 @@ public final class StatusBarManager { // old version method.invoke(manager); } - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } @@ -91,7 +90,7 @@ public final class StatusBarManager { try { Method method = getCollapsePanelsMethod(); method.invoke(manager); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 98259e7f..4a3d0bfe 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -8,7 +8,6 @@ import android.os.Build; import android.os.IBinder; import android.view.Surface; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi") @@ -109,7 +108,7 @@ public final class SurfaceControl { // call getInternalDisplayToken() return (IBinder) method.invoke(null); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -126,7 +125,7 @@ public final class SurfaceControl { try { Method method = getGetPhysicalDisplayTokenMethod(); return (IBinder) method.invoke(null, physicalDisplayId); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -152,7 +151,7 @@ public final class SurfaceControl { try { Method method = getGetPhysicalDisplayIdsMethod(); return (long[]) method.invoke(null); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return null; } @@ -170,7 +169,7 @@ public final class SurfaceControl { Method method = getSetDisplayPowerModeMethod(); method.invoke(null, displayToken, mode); return true; - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index 99b9148f..b19dace9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -7,7 +7,6 @@ import android.os.IInterface; import android.view.IDisplayFoldListener; import android.view.IRotationWatcher; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class WindowManager { @@ -66,7 +65,7 @@ public final class WindowManager { try { Method method = getGetRotationMethod(); return (int) method.invoke(manager); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return 0; } @@ -76,7 +75,7 @@ public final class WindowManager { try { Method method = getFreezeRotationMethod(); method.invoke(manager, rotation); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } @@ -85,7 +84,7 @@ public final class WindowManager { try { Method method = getIsRotationFrozenMethod(); return (boolean) method.invoke(manager); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } @@ -95,7 +94,7 @@ public final class WindowManager { try { Method method = getThawRotationMethod(); method.invoke(manager); - } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } From be3f949aa5c4dcad276ac0f2b36671a3309ce12f Mon Sep 17 00:00:00 2001 From: wuderek Date: Fri, 9 Feb 2024 16:02:48 +0800 Subject: [PATCH 1075/1133] Adapt to display API changes The method SurfaceControl.createDisplay() has been removed in AOSP. Use DisplayManager to create a VirtualDisplay object instead. Fixes #4646 Fixes #4656 PR #4657 Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/Device.java | 4 +++ .../com/genymobile/scrcpy/ScreenCapture.java | 29 +++++++++++++++++-- .../scrcpy/wrappers/DisplayManager.java | 16 ++++++++++ .../scrcpy/wrappers/SurfaceControl.java | 8 ++--- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 2324ce90..33b09a57 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -164,6 +164,10 @@ public final class Device { } } + public int getDisplayId() { + return displayId; + } + public synchronized void setMaxSize(int newMaxSize) { maxSize = newMaxSize; screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index e048354a..95214188 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -1,8 +1,10 @@ package com.genymobile.scrcpy; +import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Rect; +import android.hardware.display.VirtualDisplay; import android.os.Build; import android.os.IBinder; import android.view.Surface; @@ -11,6 +13,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList private final Device device; private IBinder display; + private VirtualDisplay virtualDisplay; public ScreenCapture(Device device) { this.device = device; @@ -34,9 +37,29 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList if (display != null) { SurfaceControl.destroyDisplay(display); + display = null; + } + if (virtualDisplay != null) { + virtualDisplay.release(); + virtualDisplay = null; + } + + try { + display = createDisplay(); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + Ln.d("Display: using SurfaceControl API"); + } catch (Exception surfaceControlException) { + Rect videoRect = screenInfo.getVideoSize().toRect(); + try { + virtualDisplay = ServiceManager.getDisplayManager() + .createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface); + Ln.d("Display: using DisplayManager API"); + } catch (Exception displayManagerException) { + Ln.e("Could not create display using SurfaceControl", surfaceControlException); + Ln.e("Could not create display using DisplayManager", displayManagerException); + throw new AssertionError("Could not create display"); + } } - display = createDisplay(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); } @Override @@ -69,7 +92,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList requestReset(); } - private static IBinder createDisplay() { + private static IBinder createDisplay() throws Exception { // Since Android 12 (preview), secure displays could not be created with shell permissions anymore. // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals( diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 33a061ba..2ff82d04 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -6,7 +6,9 @@ import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; import android.annotation.SuppressLint; +import android.hardware.display.VirtualDisplay; import android.view.Display; +import android.view.Surface; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -16,6 +18,7 @@ import java.util.regex.Pattern; @SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class DisplayManager { private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal + private Method createVirtualDisplayMethod; static DisplayManager create() { try { @@ -108,4 +111,17 @@ public final class DisplayManager { throw new AssertionError(e); } } + + private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException { + if (createVirtualDisplayMethod == null) { + createVirtualDisplayMethod = android.hardware.display.DisplayManager.class + .getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class); + } + return createVirtualDisplayMethod; + } + + public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception { + Method method = getCreateVirtualDisplayMethod(); + return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 4a3d0bfe..f0e351a2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -77,12 +77,8 @@ public final class SurfaceControl { } } - public static IBinder createDisplay(String name, boolean secure) { - try { - return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure); - } catch (Exception e) { - throw new AssertionError(e); - } + public static IBinder createDisplay(String name, boolean secure) throws Exception { + return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure); } private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException { From 9efa162949c2a3e3e42564862ff390700270394d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 Feb 2024 18:52:10 +0100 Subject: [PATCH 1076/1133] Configure clean up actions dynamically Some actions may be performed when scrcpy exits, currently: - disable "show touches" - restore "stay on while plugged in" - power off screen - restore "power mode" (to disable "turn screen off") They are performed from a separate process so that they can be executed even when scrcpy-server is killed (e.g. if the device is unplugged). The clean up actions to perform were configured when scrcpy started. Given that there is no method to read the current "power mode" in Android, and that "turn screen off" can be applied at any time using an scrcpy shortcut, there was no way to determine if "power mode" had to be restored on exit. Therefore, it was always restored to "normal", even when not necessary. However, setting the "power mode" is quite fragile on some devices, and may cause some issues, so it is preferable to call it only when necessary (when "turn screen off" has actually been called). For that purpose, make the scrcpy-server main process and the clean up process communicate the actions to perform over a pipe (stdin/stdout), so that they can be changed dynamically. In particular, when the power mode is changed at runtime, notify the clean up process. Refs 1beec99f8283713b1fbf0b3704eb4dceecc9a590 Refs #4456 Refs #4624 PR #4649 --- .../java/com/genymobile/scrcpy/CleanUp.java | 218 +++++++----------- .../com/genymobile/scrcpy/Controller.java | 8 +- .../java/com/genymobile/scrcpy/Server.java | 83 ++++--- 3 files changed, 140 insertions(+), 169 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index c84e25bb..f9b1efd6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -1,11 +1,8 @@ package com.genymobile.scrcpy; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Base64; - import java.io.File; import java.io.IOException; +import java.io.OutputStream; /** * Handle the cleanup of scrcpy, even if the main process is killed. @@ -14,127 +11,59 @@ import java.io.IOException; */ public final class CleanUp { - // A simple struct to be passed from the main process to the cleanup process - public static class Config implements Parcelable { - - public static final Creator CREATOR = new Creator() { - @Override - public Config createFromParcel(Parcel in) { - return new Config(in); - } - - @Override - public Config[] newArray(int size) { - return new Config[size]; - } - }; - - private static final int FLAG_DISABLE_SHOW_TOUCHES = 1; - private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2; - private static final int FLAG_POWER_OFF_SCREEN = 4; - - private int displayId; - - // Restore the value (between 0 and 7), -1 to not restore - // - private int restoreStayOn = -1; - - private boolean disableShowTouches; - private boolean restoreNormalPowerMode; - private boolean powerOffScreen; - - public Config() { - // Default constructor, the fields are initialized by CleanUp.configure() - } - - protected Config(Parcel in) { - displayId = in.readInt(); - restoreStayOn = in.readInt(); - byte options = in.readByte(); - disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0; - restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0; - powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(displayId); - dest.writeInt(restoreStayOn); - byte options = 0; - if (disableShowTouches) { - options |= FLAG_DISABLE_SHOW_TOUCHES; - } - if (restoreNormalPowerMode) { - options |= FLAG_RESTORE_NORMAL_POWER_MODE; - } - if (powerOffScreen) { - options |= FLAG_POWER_OFF_SCREEN; - } - dest.writeByte(options); - } + private static final int MSG_TYPE_MASK = 0b11; + private static final int MSG_TYPE_RESTORE_STAY_ON = 0; + private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1; + private static final int MSG_TYPE_RESTORE_NORMAL_POWER_MODE = 2; + private static final int MSG_TYPE_POWER_OFF_SCREEN = 3; - private boolean hasWork() { - return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen; - } + private static final int MSG_PARAM_SHIFT = 2; - @Override - public int describeContents() { - return 0; - } + private final OutputStream out; - byte[] serialize() { - Parcel parcel = Parcel.obtain(); - writeToParcel(parcel, 0); - byte[] bytes = parcel.marshall(); - parcel.recycle(); - return bytes; - } + public CleanUp(OutputStream out) { + this.out = out; + } - static Config deserialize(byte[] bytes) { - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(bytes, 0, bytes.length); - parcel.setDataPosition(0); - return CREATOR.createFromParcel(parcel); - } + public static CleanUp configure(int displayId) throws IOException { + String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)}; - static Config fromBase64(String base64) { - byte[] bytes = Base64.decode(base64, Base64.NO_WRAP); - return deserialize(bytes); - } + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.environment().put("CLASSPATH", Server.SERVER_PATH); + Process process = builder.start(); + return new CleanUp(process.getOutputStream()); + } - String toBase64() { - byte[] bytes = serialize(); - return Base64.encodeToString(bytes, Base64.NO_WRAP); + private boolean sendMessage(int type, int param) { + assert (type & ~MSG_TYPE_MASK) == 0; + int msg = type | param << MSG_PARAM_SHIFT; + try { + out.write(msg); + out.flush(); + return true; + } catch (IOException e) { + Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e); + return false; } } - private CleanUp() { - // not instantiable + public boolean setRestoreStayOn(int restoreValue) { + // Restore the value (between 0 and 7), -1 to not restore + // + assert restoreValue >= -1 && restoreValue <= 7; + return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111); } - public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen) - throws IOException { - Config config = new Config(); - config.displayId = displayId; - config.disableShowTouches = disableShowTouches; - config.restoreStayOn = restoreStayOn; - config.restoreNormalPowerMode = restoreNormalPowerMode; - config.powerOffScreen = powerOffScreen; - - if (config.hasWork()) { - startProcess(config); - } else { - // There is no additional clean up to do when scrcpy dies - unlinkSelf(); - } + public boolean setDisableShowTouches(boolean disableOnExit) { + return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0); } - private static void startProcess(Config config) throws IOException { - String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()}; + public boolean setRestoreNormalPowerMode(boolean restoreOnExit) { + return sendMessage(MSG_TYPE_RESTORE_NORMAL_POWER_MODE, restoreOnExit ? 1 : 0); + } - ProcessBuilder builder = new ProcessBuilder(cmd); - builder.environment().put("CLASSPATH", Server.SERVER_PATH); - builder.start(); + public boolean setPowerOffScreen(boolean powerOffScreenOnExit) { + return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0); } public static void unlinkSelf() { @@ -148,41 +77,66 @@ public final class CleanUp { public static void main(String... args) { unlinkSelf(); + int displayId = Integer.parseInt(args[0]); + + int restoreStayOn = -1; + boolean disableShowTouches = false; + boolean restoreNormalPowerMode = false; + boolean powerOffScreen = false; + try { // Wait for the server to die - System.in.read(); + int msg; + while ((msg = System.in.read()) != -1) { + int type = msg & MSG_TYPE_MASK; + int param = msg >> MSG_PARAM_SHIFT; + switch (type) { + case MSG_TYPE_RESTORE_STAY_ON: + restoreStayOn = param > 7 ? -1 : param; + break; + case MSG_TYPE_DISABLE_SHOW_TOUCHES: + disableShowTouches = param != 0; + break; + case MSG_TYPE_RESTORE_NORMAL_POWER_MODE: + restoreNormalPowerMode = param != 0; + break; + case MSG_TYPE_POWER_OFF_SCREEN: + powerOffScreen = param != 0; + break; + default: + Ln.w("Unexpected msg type: " + type); + break; + } + } } catch (IOException e) { // Expected when the server is dead } Ln.i("Cleaning up"); - Config config = Config.fromBase64(args[0]); - - if (config.disableShowTouches || config.restoreStayOn != -1) { - if (config.disableShowTouches) { - Ln.i("Disabling \"show touches\""); - try { - Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); - } catch (SettingsException e) { - Ln.e("Could not restore \"show_touches\"", e); - } + if (disableShowTouches) { + Ln.i("Disabling \"show touches\""); + try { + Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0"); + } catch (SettingsException e) { + Ln.e("Could not restore \"show_touches\"", e); } - if (config.restoreStayOn != -1) { - Ln.i("Restoring \"stay awake\""); - try { - Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn)); - } catch (SettingsException e) { - Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); - } + } + + if (restoreStayOn != -1) { + Ln.i("Restoring \"stay awake\""); + try { + Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn)); + } catch (SettingsException e) { + Ln.e("Could not restore \"stay_on_while_plugged_in\"", e); } } if (Device.isScreenOn()) { - if (config.powerOffScreen) { + if (powerOffScreen) { Ln.i("Power off screen"); - Device.powerOffScreen(config.displayId); - } else if (config.restoreNormalPowerMode) { + Device.powerOffScreen(displayId); + } else if (restoreNormalPowerMode) { Ln.i("Restoring normal power mode"); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 3b0e9031..c0763012 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -28,6 +28,7 @@ public class Controller implements AsyncProcessor { private final Device device; private final DesktopConnection connection; + private final CleanUp cleanUp; private final DeviceMessageSender sender; private final boolean clipboardAutosync; private final boolean powerOn; @@ -41,9 +42,10 @@ public class Controller implements AsyncProcessor { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) { + public Controller(Device device, DesktopConnection connection, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { this.device = device; this.connection = connection; + this.cleanUp = cleanUp; this.clipboardAutosync = clipboardAutosync; this.powerOn = powerOn; initPointers(); @@ -170,6 +172,10 @@ public class Controller implements AsyncProcessor { if (setPowerModeOk) { keepPowerModeOff = mode == Device.POWER_MODE_OFF; Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); + if (cleanUp != null) { + boolean mustRestoreOnExit = mode != Device.POWER_MODE_NORMAL; + cleanUp.setRestoreNormalPowerMode(mustRestoreOnExit); + } } } break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index e4a95140..bcafa133 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -51,46 +51,47 @@ public final class Server { // not instantiable } - private static void initAndCleanUp(Options options) { - boolean mustDisableShowTouchesOnCleanUp = false; - int restoreStayOn = -1; - boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled - if (options.getShowTouches() || options.getStayAwake()) { - if (options.getShowTouches()) { - try { - String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); - // If "show touches" was disabled, it must be disabled back on clean up - mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); - } catch (SettingsException e) { - Ln.e("Could not change \"show_touches\"", e); + private static void initAndCleanUp(Options options, CleanUp cleanUp) { + // This method is called from its own thread, so it may only configure cleanup actions which are NOT dynamic (i.e. they are configured once + // and for all, they cannot be changed from another thread) + + if (options.getShowTouches()) { + try { + String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1"); + // If "show touches" was disabled, it must be disabled back on clean up + if (!"1".equals(oldValue)) { + if (!cleanUp.setDisableShowTouches(true)) { + Ln.e("Could not disable show touch on exit"); + } } + } catch (SettingsException e) { + Ln.e("Could not change \"show_touches\"", e); } + } - if (options.getStayAwake()) { - int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; + if (options.getStayAwake()) { + int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; + try { + String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); try { - String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn)); - try { - restoreStayOn = Integer.parseInt(oldValue); - if (restoreStayOn == stayOn) { - // No need to restore - restoreStayOn = -1; + int restoreStayOn = Integer.parseInt(oldValue); + if (restoreStayOn != stayOn) { + // Restore only if the current value is different + if (!cleanUp.setRestoreStayOn(restoreStayOn)) { + Ln.e("Could not restore stay on on exit"); } - } catch (NumberFormatException e) { - restoreStayOn = 0; } - } catch (SettingsException e) { - Ln.e("Could not change \"stay_on_while_plugged_in\"", e); + } catch (NumberFormatException e) { + // ignore } + } catch (SettingsException e) { + Ln.e("Could not change \"stay_on_while_plugged_in\"", e); } } - if (options.getCleanup()) { - try { - CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, - options.getPowerOffScreenOnClose()); - } catch (IOException e) { - Ln.e("Could not configure cleanup", e); + if (options.getPowerOffScreenOnClose()) { + if (!cleanUp.setPowerOffScreen(true)) { + Ln.e("Could not power off screen on exit"); } } } @@ -101,7 +102,13 @@ public final class Server { throw new ConfigurationException("Camera mirroring is not supported"); } - Thread initThread = startInitThread(options); + CleanUp cleanUp = null; + Thread initThread = null; + + if (options.getCleanup()) { + cleanUp = CleanUp.configure(options.getDisplayId()); + initThread = startInitThread(options, cleanUp); + } int scid = options.getScid(); boolean tunnelForward = options.isTunnelForward(); @@ -124,7 +131,7 @@ public final class Server { } if (control) { - Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); + Controller controller = new Controller(device, connection, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); asyncProcessors.add(controller); } @@ -167,7 +174,9 @@ public final class Server { completion.await(); } finally { - initThread.interrupt(); + if (initThread != null) { + initThread.interrupt(); + } for (AsyncProcessor asyncProcessor : asyncProcessors) { asyncProcessor.stop(); } @@ -175,7 +184,9 @@ public final class Server { connection.shutdown(); try { - initThread.join(); + if (initThread != null) { + initThread.join(); + } for (AsyncProcessor asyncProcessor : asyncProcessors) { asyncProcessor.join(); } @@ -187,8 +198,8 @@ public final class Server { } } - private static Thread startInitThread(final Options options) { - Thread thread = new Thread(() -> initAndCleanUp(options), "init-cleanup"); + private static Thread startInitThread(final Options options, final CleanUp cleanUp) { + Thread thread = new Thread(() -> initAndCleanUp(options, cleanUp), "init-cleanup"); thread.start(); return thread; } From d47ecef1b561322872437368edbfff69dc5ad0bd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Feb 2024 17:27:14 +0100 Subject: [PATCH 1077/1133] Limit buffering time value This avoids unreasonable values which could lead to integer overflow. PR #4572 --- app/src/cli.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index f7d7e390..b2b02ecd 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1385,7 +1385,11 @@ parse_max_fps(const char *s, uint16_t *max_fps) { static bool parse_buffering_time(const char *s, sc_tick *tick) { long value; - bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, + // In practice, buffering time should not exceed a few seconds. + // Limit it to some arbitrary value (1 hour) to prevent 32-bit overflow + // when multiplied by the audio sample size and the number of samples per + // millisecond. + bool ok = parse_integer_arg(s, &value, false, 0, 60 * 60 * 1000, "buffering time"); if (!ok) { return false; From cfa4f7e2f2ac867dd6d6278a48cc470e82d42f37 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Jan 2024 19:22:55 +0100 Subject: [PATCH 1078/1133] Replace locks by atomics in audio player The audio output thread only reads samples from the buffer, and most of the time, the audio receiver thread only writes samples to the buffer. In these cases, using atomics avoids lock contention. There are still corner cases where the audio receiver thread needs to "read" samples (and drop them), so lock only in these cases. PR #4572 --- app/meson.build | 9 +- app/src/audio_player.c | 202 ++++++++++++++++++-------------------- app/src/audio_player.h | 22 ++--- app/src/util/audiobuf.c | 112 +++++++++++++++++++++ app/src/util/audiobuf.h | 79 +++++---------- app/src/util/bytebuf.c | 104 -------------------- app/src/util/bytebuf.h | 114 --------------------- app/tests/test_audiobuf.c | 128 ++++++++++++++++++++++++ app/tests/test_bytebuf.c | 126 ------------------------ 9 files changed, 376 insertions(+), 520 deletions(-) create mode 100644 app/src/util/audiobuf.c delete mode 100644 app/src/util/bytebuf.c delete mode 100644 app/src/util/bytebuf.h create mode 100644 app/tests/test_audiobuf.c delete mode 100644 app/tests/test_bytebuf.c diff --git a/app/meson.build b/app/meson.build index 88e2df9a..caf5ee5c 100644 --- a/app/meson.build +++ b/app/meson.build @@ -34,8 +34,8 @@ src = [ 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', + 'src/util/audiobuf.c', 'src/util/average.c', - 'src/util/bytebuf.c', 'src/util/file.c', 'src/util/intmap.c', 'src/util/intr.c', @@ -212,9 +212,10 @@ if get_option('buildtype') == 'debug' ['test_binary', [ 'tests/test_binary.c', ]], - ['test_bytebuf', [ - 'tests/test_bytebuf.c', - 'src/util/bytebuf.c', + ['test_audiobuf', [ + 'tests/test_audiobuf.c', + 'src/util/audiobuf.c', + 'src/util/memory.c', ]], ['test_cli', [ 'tests/test_cli.c', diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 8f0ad7fb..728d3f2a 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -66,8 +66,7 @@ static void SDLCALL sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { struct sc_audio_player *ap = userdata; - // This callback is called with the lock used by SDL_AudioDeviceLock(), so - // the audiobuf is protected + // This callback is called with the lock used by SDL_LockAudioDevice() assert(len_int > 0); size_t len = len_int; @@ -77,8 +76,9 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count); #endif - uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); - if (!ap->played) { + bool played = atomic_load_explicit(&ap->played, memory_order_relaxed); + if (!played) { + uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); // Part of the buffering is handled by inserting initial silence. The // remaining (margin) last samples will be handled by compensation. uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms @@ -93,10 +93,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { } } - uint32_t read = MIN(buffered_samples, count); - if (read) { - sc_audiobuf_read(&ap->buf, stream, read); - } + uint32_t read = sc_audiobuf_read(&ap->buf, stream, count); if (read < count) { uint32_t silence = count - read; @@ -109,13 +106,16 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { silence); memset(stream + TO_BYTES(read), 0, TO_BYTES(silence)); - if (ap->received) { + bool received = atomic_load_explicit(&ap->received, + memory_order_relaxed); + if (received) { // Inserting additional samples immediately increases buffering - ap->underflow += silence; + atomic_fetch_add_explicit(&ap->underflow, silence, + memory_order_relaxed); } } - ap->played = true; + atomic_store_explicit(&ap->played, true, memory_order_relaxed); } static uint8_t * @@ -162,123 +162,119 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // swr_convert() returns the number of samples which would have been // written if the buffer was big enough. - uint32_t samples_written = MIN(ret, dst_nb_samples); + uint32_t samples = MIN(ret, dst_nb_samples); #ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written); + LOGD("[Audio] %" PRIu32 " samples written to buffer", samples); #endif - // Since this function is the only writer, the current available space is - // at least the previous available space. In practice, it should almost - // always be possible to write without lock. - bool lockless_write = samples_written <= ap->previous_can_write; - if (lockless_write) { - sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written); + uint32_t cap = sc_audiobuf_capacity(&ap->buf); + if (samples > cap) { + // Very very unlikely: a single resampled frame should never + // exceed the audio buffer size (or something is very wrong). + // Ignore the first bytes in swr_buf to avoid memory corruption anyway. + swr_buf += TO_BYTES(samples - cap); + samples = cap; } - SDL_LockAudioDevice(ap->device); - - uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); - - if (lockless_write) { - sc_audiobuf_commit_write(&ap->buf, samples_written); - } else { - uint32_t can_write = sc_audiobuf_can_write(&ap->buf); - if (samples_written > can_write) { - // Entering this branch is very unlikely, the audio buffer is - // allocated with a size sufficient to store 1 second more than the - // target buffering. If this happens, though, we have to skip old - // samples. - uint32_t cap = sc_audiobuf_capacity(&ap->buf); - if (samples_written > cap) { - // Very very unlikely: a single resampled frame should never - // exceed the audio buffer size (or something is very wrong). - // Ignore the first bytes in swr_buf - swr_buf += TO_BYTES(samples_written - cap); - // This change in samples_written will impact the - // instant_compensation below - samples_written = cap; - } - - assert(samples_written >= can_write); - if (samples_written > can_write) { - uint32_t skip_samples = samples_written - can_write; - assert(buffered_samples >= skip_samples); - sc_audiobuf_skip(&ap->buf, skip_samples); - buffered_samples -= skip_samples; - if (ap->played) { - // Dropping input samples instantly decreases buffering - ap->avg_buffering.avg -= skip_samples; - } - } - - // It should remain exactly the expected size to write the new - // samples. - assert(sc_audiobuf_can_write(&ap->buf) == samples_written); + uint32_t skipped_samples = 0; + + uint32_t written = sc_audiobuf_write(&ap->buf, swr_buf, samples); + if (written < samples) { + uint32_t remaining = samples - written; + + // All samples that could be written without locking have been written, + // now we need to lock to drop/consume old samples + SDL_LockAudioDevice(ap->device); + + // Retry with the lock + written += sc_audiobuf_write(&ap->buf, + swr_buf + TO_BYTES(written), + remaining); + if (written < samples) { + remaining = samples - written; + // Still insufficient, drop old samples to make space + skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining); + assert(skipped_samples == remaining); + + // Now there is enough space + uint32_t w = sc_audiobuf_write(&ap->buf, + swr_buf + TO_BYTES(written), + remaining); + assert(w == remaining); + (void) w; } - sc_audiobuf_write(&ap->buf, swr_buf, samples_written); + SDL_UnlockAudioDevice(ap->device); } - buffered_samples += samples_written; - assert(buffered_samples == sc_audiobuf_can_read(&ap->buf)); - - // Read with lock held, to be used after unlocking - bool played = ap->played; - uint32_t underflow = ap->underflow; - + uint32_t underflow = 0; + uint32_t max_buffered_samples; + bool played = atomic_load_explicit(&ap->played, memory_order_relaxed); if (played) { - uint32_t max_buffered_samples = ap->target_buffering - + 12 * ap->output_buffer - + ap->target_buffering / 10; - if (buffered_samples > max_buffered_samples) { - uint32_t skip_samples = buffered_samples - max_buffered_samples; - sc_audiobuf_skip(&ap->buf, skip_samples); - LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32 - " samples", skip_samples); - } + underflow = atomic_exchange_explicit(&ap->underflow, 0, + memory_order_relaxed); - // reset (the current value was copied to a local variable) - ap->underflow = 0; + max_buffered_samples = ap->target_buffering + + 12 * ap->output_buffer + + ap->target_buffering / 10; } else { // SDL playback not started yet, do not accumulate more than // max_initial_buffering samples, this would cause unnecessary delay // (and glitches to compensate) on start. - uint32_t max_initial_buffering = ap->target_buffering - + 2 * ap->output_buffer; - if (buffered_samples > max_initial_buffering) { - uint32_t skip_samples = buffered_samples - max_initial_buffering; - sc_audiobuf_skip(&ap->buf, skip_samples); + max_buffered_samples = ap->target_buffering + 2 * ap->output_buffer; + } + + uint32_t can_read = sc_audiobuf_can_read(&ap->buf); + if (can_read > max_buffered_samples) { + uint32_t skip_samples = 0; + + SDL_LockAudioDevice(ap->device); + can_read = sc_audiobuf_can_read(&ap->buf); + if (can_read > max_buffered_samples) { + skip_samples = can_read - max_buffered_samples; + uint32_t r = sc_audiobuf_read(&ap->buf, NULL, skip_samples); + assert(r == skip_samples); + (void) r; + skipped_samples += skip_samples; + } + SDL_UnlockAudioDevice(ap->device); + + if (skip_samples) { + if (played) { + LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32 + " samples", skip_samples); #ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples", - skip_samples); + } else { + LOGD("[Audio] Playback not started, skipping %" PRIu32 + " samples", skip_samples); #endif + } } } - ap->previous_can_write = sc_audiobuf_can_write(&ap->buf); - ap->received = true; - - SDL_UnlockAudioDevice(ap->device); + atomic_store_explicit(&ap->received, true, memory_order_relaxed); if (played) { // Number of samples added (or removed, if negative) for compensation - int32_t instant_compensation = - (int32_t) samples_written - frame->nb_samples; + int32_t instant_compensation = (int32_t) written - frame->nb_samples; + // Inserting silence instantly increases buffering int32_t inserted_silence = (int32_t) underflow; + // Dropping input samples instantly decreases buffering + int32_t dropped = (int32_t) skipped_samples; // The compensation must apply instantly, it must not be smoothed - ap->avg_buffering.avg += instant_compensation + inserted_silence; - + ap->avg_buffering.avg += + instant_compensation + inserted_silence - dropped; // However, the buffering level must be smoothed - sc_average_push(&ap->avg_buffering, buffered_samples); + sc_average_push(&ap->avg_buffering, can_read); #ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f", - buffered_samples, sc_average_get(&ap->avg_buffering)); + LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f", + can_read, sc_average_get(&ap->avg_buffering)); #endif - ap->samples_since_resync += samples_written; + ap->samples_since_resync += written; if (ap->samples_since_resync >= ap->sample_rate) { // Recompute compensation every second ap->samples_since_resync = 0; @@ -288,7 +284,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, if (abs(diff) < (int) ap->sample_rate / 1000) { // Do not compensate for less than 1ms, the error is just noise diff = 0; - } else if (diff < 0 && buffered_samples < ap->target_buffering) { + } else if (diff < 0 && can_read < ap->target_buffering) { // Do not accelerate if the instant buffering level is below // the average, this would increase underflow diff = 0; @@ -300,8 +296,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, int abs_max_diff = distance / 50; diff = CLAMP(diff, -abs_max_diff, abs_max_diff); LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32 - " compensation=%d", ap->target_buffering, avg, - buffered_samples, diff); + " compensation=%d", ap->target_buffering, avg, can_read, diff); if (diff != ap->compensation) { int ret = swr_set_compensation(swr_ctx, diff, distance); @@ -397,7 +392,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, // producer and the consumer. It's too big on purpose, to guarantee that // the producer and the consumer will be able to access it in parallel // without locking. - size_t audiobuf_samples = ap->target_buffering + ap->sample_rate; + uint32_t audiobuf_samples = ap->target_buffering + ap->sample_rate; size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample; bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples); @@ -413,16 +408,15 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, } ap->swr_buf_alloc_size = initial_swr_buf_size; - ap->previous_can_write = sc_audiobuf_can_write(&ap->buf); - // Samples are produced and consumed by blocks, so the buffering must be // smoothed to get a relatively stable value. sc_average_init(&ap->avg_buffering, 32); ap->samples_since_resync = 0; ap->received = false; - ap->played = false; - ap->underflow = 0; + atomic_init(&ap->played, false); + atomic_init(&ap->received, false); + atomic_init(&ap->underflow, 0); ap->compensation = 0; // The thread calling open() is the thread calling push(), which fills the diff --git a/app/src/audio_player.h b/app/src/audio_player.h index 30378246..0c677363 100644 --- a/app/src/audio_player.h +++ b/app/src/audio_player.h @@ -3,6 +3,7 @@ #include "common.h" +#include #include #include #include @@ -32,13 +33,9 @@ struct sc_audio_player { uint16_t output_buffer; // Audio buffer to communicate between the receiver and the SDL audio - // callback (protected by SDL_AudioDeviceLock()) + // callback struct sc_audiobuf buf; - // The previous empty space in the buffer (only used by the receiver - // thread) - uint32_t previous_can_write; - // Resampler (only used from the receiver thread) struct SwrContext *swr_ctx; @@ -47,7 +44,7 @@ struct sc_audio_player { // The number of channels is the same for input and output unsigned nb_channels; // The number of bytes per sample for a single channel - unsigned out_bytes_per_sample; + size_t out_bytes_per_sample; // Target buffer for resampling (only used by the receiver thread) uint8_t *swr_buf; @@ -61,19 +58,16 @@ struct sc_audio_player { uint32_t samples_since_resync; // Number of silence samples inserted since the last received packet - // (protected by SDL_AudioDeviceLock()) - uint32_t underflow; + atomic_uint_least32_t underflow; // Current applied compensation value (only used by the receiver thread) int compensation; - // Set to true the first time a sample is received (protected by - // SDL_AudioDeviceLock()) - bool received; + // Set to true the first time a sample is received + atomic_bool received; - // Set to true the first time the SDL callback is called (protected by - // SDL_AudioDeviceLock()) - bool played; + // Set to true the first time the SDL callback is called + atomic_bool played; const struct sc_audio_player_callbacks *cbs; void *cbs_userdata; diff --git a/app/src/util/audiobuf.c b/app/src/util/audiobuf.c new file mode 100644 index 00000000..3597f7ee --- /dev/null +++ b/app/src/util/audiobuf.c @@ -0,0 +1,112 @@ +#include "audiobuf.h" + +#include +#include +#include +#include + +bool +sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size, + uint32_t capacity) { + assert(sample_size); + assert(capacity); + + // The actual capacity is (alloc_size - 1) so that head == tail is + // non-ambiguous + buf->alloc_size = capacity + 1; + buf->data = sc_allocarray(buf->alloc_size, sample_size); + if (!buf->data) { + LOG_OOM(); + return false; + } + + buf->sample_size = sample_size; + atomic_init(&buf->head, 0); + atomic_init(&buf->tail, 0); + + return true; +} + +void +sc_audiobuf_destroy(struct sc_audiobuf *buf) { + free(buf->data); +} + +uint32_t +sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) { + assert(samples_count); + + uint8_t *to = to_; + + // Only the reader thread can write tail without synchronization, so + // memory_order_relaxed is sufficient + uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_relaxed); + + // The head cursor is updated after the data is written to the array + uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire); + + uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size; + if (samples_count > can_read) { + samples_count = can_read; + } + + if (to) { + uint32_t right_count = buf->alloc_size - tail; + if (right_count > samples_count) { + right_count = samples_count; + } + memcpy(to, + buf->data + (tail * buf->sample_size), + right_count * buf->sample_size); + + if (samples_count > right_count) { + uint32_t left_count = samples_count - right_count; + memcpy(to + (right_count * buf->sample_size), + buf->data, + left_count * buf->sample_size); + } + } + + uint32_t new_tail = (tail + samples_count) % buf->alloc_size; + atomic_store_explicit(&buf->tail, new_tail, memory_order_release); + + return samples_count; +} + +uint32_t +sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_, + uint32_t samples_count) { + const uint8_t *from = from_; + + // Only the writer thread can write head, so memory_order_relaxed is + // sufficient + uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed); + + // The tail cursor is updated after the data is consumed by the reader + uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire); + + uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size; + if (samples_count > can_write) { + samples_count = can_write; + } + + uint32_t right_count = buf->alloc_size - head; + if (right_count > samples_count) { + right_count = samples_count; + } + memcpy(buf->data + (head * buf->sample_size), + from, + right_count * buf->sample_size); + + if (samples_count > right_count) { + uint32_t left_count = samples_count - right_count; + memcpy(buf->data, + from + (right_count * buf->sample_size), + left_count * buf->sample_size); + } + + uint32_t new_head = (head + samples_count) % buf->alloc_size; + atomic_store_explicit(&buf->head, new_head, memory_order_release); + + return samples_count; +} diff --git a/app/src/util/audiobuf.h b/app/src/util/audiobuf.h index 8616d539..5e7dd4a0 100644 --- a/app/src/util/audiobuf.h +++ b/app/src/util/audiobuf.h @@ -3,19 +3,25 @@ #include "common.h" +#include +#include #include #include -#include "util/bytebuf.h" - /** * Wrapper around bytebuf to read and write samples * * Each sample takes sample_size bytes. */ struct sc_audiobuf { - struct sc_bytebuf buf; + uint8_t *data; + uint32_t alloc_size; // in samples size_t sample_size; + + atomic_uint_least32_t head; // writer cursor, in samples + atomic_uint_least32_t tail; // reader cursor, in samples + // empty: tail == head + // full: ((tail + 1) % alloc_size) == head }; static inline uint32_t @@ -29,66 +35,31 @@ sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) { return samples * buf->sample_size; } -static inline bool +bool sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size, - uint32_t capacity) { - buf->sample_size = sample_size; - return sc_bytebuf_init(&buf->buf, capacity * sample_size + 1); -} - -static inline void -sc_audiobuf_read(struct sc_audiobuf *buf, uint8_t *to, uint32_t samples) { - size_t bytes = sc_audiobuf_to_bytes(buf, samples); - sc_bytebuf_read(&buf->buf, to, bytes); -} + uint32_t capacity); -static inline void -sc_audiobuf_skip(struct sc_audiobuf *buf, uint32_t samples) { - size_t bytes = sc_audiobuf_to_bytes(buf, samples); - sc_bytebuf_skip(&buf->buf, bytes); -} +void +sc_audiobuf_destroy(struct sc_audiobuf *buf); -static inline void -sc_audiobuf_write(struct sc_audiobuf *buf, const uint8_t *from, - uint32_t samples) { - size_t bytes = sc_audiobuf_to_bytes(buf, samples); - sc_bytebuf_write(&buf->buf, from, bytes); -} +uint32_t +sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count); -static inline void -sc_audiobuf_prepare_write(struct sc_audiobuf *buf, const uint8_t *from, - uint32_t samples) { - size_t bytes = sc_audiobuf_to_bytes(buf, samples); - sc_bytebuf_prepare_write(&buf->buf, from, bytes); -} - -static inline void -sc_audiobuf_commit_write(struct sc_audiobuf *buf, uint32_t samples) { - size_t bytes = sc_audiobuf_to_bytes(buf, samples); - sc_bytebuf_commit_write(&buf->buf, bytes); -} - -static inline uint32_t -sc_audiobuf_can_read(struct sc_audiobuf *buf) { - size_t bytes = sc_bytebuf_can_read(&buf->buf); - return sc_audiobuf_to_samples(buf, bytes); -} - -static inline uint32_t -sc_audiobuf_can_write(struct sc_audiobuf *buf) { - size_t bytes = sc_bytebuf_can_write(&buf->buf); - return sc_audiobuf_to_samples(buf, bytes); -} +uint32_t +sc_audiobuf_write(struct sc_audiobuf *buf, const void *from, + uint32_t samples_count); static inline uint32_t sc_audiobuf_capacity(struct sc_audiobuf *buf) { - size_t bytes = sc_bytebuf_capacity(&buf->buf); - return sc_audiobuf_to_samples(buf, bytes); + assert(buf->alloc_size); + return buf->alloc_size - 1; } -static inline void -sc_audiobuf_destroy(struct sc_audiobuf *buf) { - sc_bytebuf_destroy(&buf->buf); +static inline uint32_t +sc_audiobuf_can_read(struct sc_audiobuf *buf) { + uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire); + uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire); + return (buf->alloc_size + head - tail) % buf->alloc_size; } #endif diff --git a/app/src/util/bytebuf.c b/app/src/util/bytebuf.c deleted file mode 100644 index 93544d72..00000000 --- a/app/src/util/bytebuf.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "bytebuf.h" - -#include -#include -#include - -#include "util/log.h" - -bool -sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) { - assert(alloc_size); - buf->data = malloc(alloc_size); - if (!buf->data) { - LOG_OOM(); - return false; - } - - buf->alloc_size = alloc_size; - buf->head = 0; - buf->tail = 0; - - return true; -} - -void -sc_bytebuf_destroy(struct sc_bytebuf *buf) { - free(buf->data); -} - -void -sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) { - assert(len); - assert(len <= sc_bytebuf_can_read(buf)); - assert(buf->tail != buf->head); // the buffer could not be empty - - size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size; - size_t right_len = right_limit - buf->tail; - if (len < right_len) { - right_len = len; - } - memcpy(to, buf->data + buf->tail, right_len); - - if (len > right_len) { - memcpy(to + right_len, buf->data, len - right_len); - } - - buf->tail = (buf->tail + len) % buf->alloc_size; -} - -void -sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) { - assert(len); - assert(len <= sc_bytebuf_can_read(buf)); - assert(buf->tail != buf->head); // the buffer could not be empty - - buf->tail = (buf->tail + len) % buf->alloc_size; -} - -static inline void -sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from, - size_t len) { - size_t right_len = buf->alloc_size - buf->head; - if (len < right_len) { - right_len = len; - } - memcpy(buf->data + buf->head, from, right_len); - - if (len > right_len) { - memcpy(buf->data, from + right_len, len - right_len); - } -} - -static inline void -sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) { - buf->head = (buf->head + len) % buf->alloc_size; -} - -void -sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) { - assert(len); - assert(len <= sc_bytebuf_can_write(buf)); - - sc_bytebuf_write_step0(buf, from, len); - sc_bytebuf_write_step1(buf, len); -} - -void -sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from, - size_t len) { - // *This function MUST NOT access buf->tail (even in assert()).* - // The purpose of this function is to allow a reader and a writer to access - // different parts of the buffer in parallel simultaneously. It is intended - // to be called without lock (only sc_bytebuf_commit_write() is intended to - // be called with lock held). - - assert(len < buf->alloc_size - 1); - sc_bytebuf_write_step0(buf, from, len); -} - -void -sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) { - assert(len <= sc_bytebuf_can_write(buf)); - sc_bytebuf_write_step1(buf, len); -} diff --git a/app/src/util/bytebuf.h b/app/src/util/bytebuf.h deleted file mode 100644 index 1448f752..00000000 --- a/app/src/util/bytebuf.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef SC_BYTEBUF_H -#define SC_BYTEBUF_H - -#include "common.h" - -#include -#include - -struct sc_bytebuf { - uint8_t *data; - // The actual capacity is (allocated - 1) so that head == tail is - // non-ambiguous - size_t alloc_size; - size_t head; // writter cursor - size_t tail; // reader cursor - // empty: tail == head - // full: ((tail + 1) % alloc_size) == head -}; - -bool -sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size); - -/** - * Copy from the bytebuf to a user-provided array - * - * The caller must check that len <= sc_bytebuf_read_available() (it is an - * error to attempt to read more bytes than available). - * - * This function is guaranteed not to write to buf->head. - */ -void -sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len); - -/** - * Drop len bytes from the buffer - * - * The caller must check that len <= sc_bytebuf_read_available() (it is an - * error to attempt to skip more bytes than available). - * - * This function is guaranteed not to write to buf->head. - * - * It is equivalent to call sc_bytebuf_read() to some array and discard the - * array (but this function is more efficient since there is no copy). - */ -void -sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len); - -/** - * Copy the user-provided array to the bytebuf - * - * The caller must check that len <= sc_bytebuf_write_available() (it is an - * error to write more bytes than the remaining available space). - * - * This function is guaranteed not to write to buf->tail. - */ -void -sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len); - -/** - * Copy the user-provided array to the bytebuf, but do not advance the cursor - * - * The caller must check that len <= sc_bytebuf_write_available() (it is an - * error to write more bytes than the remaining available space). - * - * After this function is called, the write must be committed with - * sc_bytebuf_commit_write(). - * - * The purpose of this mechanism is to acquire a lock only to commit the write, - * but not to perform the actual copy. - * - * This function is guaranteed not to access buf->tail. - */ -void -sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from, - size_t len); - -/** - * Commit a prepared write - */ -void -sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len); - -/** - * Return the number of bytes which can be read - * - * It is an error to read more bytes than available. - */ -static inline size_t -sc_bytebuf_can_read(struct sc_bytebuf *buf) { - return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size; -} - -/** - * Return the number of bytes which can be written - * - * It is an error to write more bytes than available. - */ -static inline size_t -sc_bytebuf_can_write(struct sc_bytebuf *buf) { - return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size; -} - -/** - * Return the actual capacity of the buffer (can_read() + can_write()) - */ -static inline size_t -sc_bytebuf_capacity(struct sc_bytebuf *buf) { - return buf->alloc_size - 1; -} - -void -sc_bytebuf_destroy(struct sc_bytebuf *buf); - -#endif diff --git a/app/tests/test_audiobuf.c b/app/tests/test_audiobuf.c new file mode 100644 index 00000000..94d0f07a --- /dev/null +++ b/app/tests/test_audiobuf.c @@ -0,0 +1,128 @@ +#include "common.h" + +#include +#include + +#include "util/audiobuf.h" + +static void test_audiobuf_simple(void) { + struct sc_audiobuf buf; + uint32_t data[20]; + + bool ok = sc_audiobuf_init(&buf, 4, 20); + assert(ok); + + uint32_t samples[] = {1, 2, 3, 4, 5}; + uint32_t w = sc_audiobuf_write(&buf, samples, 5); + assert(w == 5); + + uint32_t r = sc_audiobuf_read(&buf, data, 4); + assert(r == 4); + assert(!memcmp(data, samples, 16)); + + uint32_t samples2[] = {6, 7, 8}; + w = sc_audiobuf_write(&buf, samples2, 3); + assert(w == 3); + + uint32_t single = 9; + w = sc_audiobuf_write(&buf, &single, 1); + assert(w == 1); + + r = sc_audiobuf_read(&buf, &data[4], 8); + assert(r == 5); + + uint32_t expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + assert(!memcmp(data, expected, 36)); + + sc_audiobuf_destroy(&buf); +} + +static void test_audiobuf_boundaries(void) { + struct sc_audiobuf buf; + uint32_t data[20]; + + bool ok = sc_audiobuf_init(&buf, 4, 20); + assert(ok); + + uint32_t samples[] = {1, 2, 3, 4, 5, 6}; + uint32_t w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 6); + + w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 6); + + w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 6); + + uint32_t r = sc_audiobuf_read(&buf, data, 9); + assert(r == 9); + + uint32_t expected[] = {1, 2, 3, 4, 5, 6, 1, 2, 3}; + assert(!memcmp(data, expected, 36)); + + uint32_t samples2[] = {7, 8, 9, 10, 11}; + w = sc_audiobuf_write(&buf, samples2, 5); + assert(w == 5); + + uint32_t single = 12; + w = sc_audiobuf_write(&buf, &single, 1); + assert(w == 1); + + w = sc_audiobuf_read(&buf, NULL, 3); + assert(w == 3); + + assert(sc_audiobuf_can_read(&buf) == 12); + + r = sc_audiobuf_read(&buf, data, 12); + assert(r == 12); + + uint32_t expected2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + assert(!memcmp(data, expected2, 48)); + + sc_audiobuf_destroy(&buf); +} + +static void test_audiobuf_partial_read_write(void) { + struct sc_audiobuf buf; + uint32_t data[15]; + + bool ok = sc_audiobuf_init(&buf, 4, 10); + assert(ok); + + uint32_t samples[] = {1, 2, 3, 4, 5, 6}; + uint32_t w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 6); + + w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 4); + + w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 0); + + uint32_t r = sc_audiobuf_read(&buf, data, 3); + assert(r == 3); + + uint32_t expected[] = {1, 2, 3}; + assert(!memcmp(data, expected, 12)); + + w = sc_audiobuf_write(&buf, samples, 6); + assert(w == 3); + + r = sc_audiobuf_read(&buf, data, 15); + assert(r == 10); + uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3}; + assert(!memcmp(data, expected2, 12)); + + sc_audiobuf_destroy(&buf); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_audiobuf_simple(); + test_audiobuf_boundaries(); + test_audiobuf_partial_read_write(); + + return 0; +} diff --git a/app/tests/test_bytebuf.c b/app/tests/test_bytebuf.c deleted file mode 100644 index 8e9d7c57..00000000 --- a/app/tests/test_bytebuf.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "common.h" - -#include -#include - -#include "util/bytebuf.h" - -static void test_bytebuf_simple(void) { - struct sc_bytebuf buf; - uint8_t data[20]; - - bool ok = sc_bytebuf_init(&buf, 20); - assert(ok); - - sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1); - assert(sc_bytebuf_can_read(&buf) == 5); - - sc_bytebuf_read(&buf, data, 4); - assert(!strncmp((char *) data, "hell", 4)); - - sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1); - assert(sc_bytebuf_can_read(&buf) == 7); - - sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_can_read(&buf) == 8); - - sc_bytebuf_read(&buf, &data[4], 8); - assert(sc_bytebuf_can_read(&buf) == 0); - - data[12] = '\0'; - assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_can_read(&buf) == 0); - - sc_bytebuf_destroy(&buf); -} - -static void test_bytebuf_boundaries(void) { - struct sc_bytebuf buf; - uint8_t data[20]; - - bool ok = sc_bytebuf_init(&buf, 20); - assert(ok); - - sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 6); - - sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 12); - - sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 18); - - sc_bytebuf_read(&buf, data, 9); - assert(!strncmp((char *) data, "hello hel", 9)); - assert(sc_bytebuf_can_read(&buf) == 9); - - sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1); - assert(sc_bytebuf_can_read(&buf) == 14); - - sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_can_read(&buf) == 15); - - sc_bytebuf_skip(&buf, 3); - assert(sc_bytebuf_can_read(&buf) == 12); - - sc_bytebuf_read(&buf, data, 12); - data[12] = '\0'; - assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_can_read(&buf) == 0); - - sc_bytebuf_destroy(&buf); -} - -static void test_bytebuf_two_steps_write(void) { - struct sc_bytebuf buf; - uint8_t data[20]; - - bool ok = sc_bytebuf_init(&buf, 20); - assert(ok); - - sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 6); - - sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 12); - - sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet - - sc_bytebuf_read(&buf, data, 9); - assert(!strncmp((char *) data, "hello hel", 3)); - assert(sc_bytebuf_can_read(&buf) == 3); - - sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1); - assert(sc_bytebuf_can_read(&buf) == 9); - - sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1); - assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet - - sc_bytebuf_commit_write(&buf, sizeof("world") - 1); - assert(sc_bytebuf_can_read(&buf) == 14); - - sc_bytebuf_write(&buf, (uint8_t *) "!", 1); - assert(sc_bytebuf_can_read(&buf) == 15); - - sc_bytebuf_skip(&buf, 3); - assert(sc_bytebuf_can_read(&buf) == 12); - - sc_bytebuf_read(&buf, data, 12); - data[12] = '\0'; - assert(!strcmp((char *) data, "hello world!")); - assert(sc_bytebuf_can_read(&buf) == 0); - - sc_bytebuf_destroy(&buf); -} - -int main(int argc, char *argv[]) { - (void) argc; - (void) argv; - - test_bytebuf_simple(); - test_bytebuf_boundaries(); - test_bytebuf_two_steps_write(); - - return 0; -} From 44abed5c68d657c664457eb562318168876d2208 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Jan 2024 18:23:24 +0100 Subject: [PATCH 1079/1133] Improve audio compensation thresholds Use different thresholds for enabling and disabling compensation. Concretely, enable compensation if the difference between the average and the target buffering levels exceeds 4 ms (instead of 1 ms). This avoids unnecessary compensation due to small noise in buffering level estimation. But keep a smaller threshold (1 ms) for disabling compensation, so that the buffering level is restored closer to the target value. This avoids to keep the actual level close to the compensation threshold. PR #4572 --- app/src/audio_player.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 728d3f2a..c70964b9 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -281,8 +281,15 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, float avg = sc_average_get(&ap->avg_buffering); int diff = ap->target_buffering - avg; - if (abs(diff) < (int) ap->sample_rate / 1000) { - // Do not compensate for less than 1ms, the error is just noise + + // Enable compensation when the difference exceeds +/- 4ms. + // Disable compensation when the difference is lower than +/- 1ms. + int threshold = ap->compensation != 0 + ? ap->sample_rate / 1000 /* 1ms */ + : ap->sample_rate * 4 / 1000; /* 4ms */ + + if (abs(diff) < threshold) { + // Do not compensate for small values, the error is just noise diff = 0; } else if (diff < 0 && can_read < ap->target_buffering) { // Do not accelerate if the instant buffering level is below From edac4b8a9a4a261a82400828b17d7b7ae0b33cd0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Jan 2024 18:27:56 +0100 Subject: [PATCH 1080/1133] Increase buffering level smoothness The buffering level does not change continuously: it increases abruptly when a packet is received, and decreases abruptly when an audio block is consumed. To estimate the buffering level, a rolling average is used. To make the buffering more stable, increase the smoothness of this rolling average. This decreases the risk of enabling audio compensation due to an estimation error. PR #4572 --- app/src/audio_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index c70964b9..4552b0f7 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -417,7 +417,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink, // Samples are produced and consumed by blocks, so the buffering must be // smoothed to get a relatively stable value. - sc_average_init(&ap->avg_buffering, 32); + sc_average_init(&ap->avg_buffering, 128); ap->samples_since_resync = 0; ap->received = false; From dfa3f97a87c0b42289920777731b4da95369d7ed Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 24 Jan 2024 14:46:16 +0100 Subject: [PATCH 1081/1133] Fix audio player comment PR #4572 --- app/src/audio_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 4552b0f7..4d101b01 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -293,7 +293,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, diff = 0; } else if (diff < 0 && can_read < ap->target_buffering) { // Do not accelerate if the instant buffering level is below - // the average, this would increase underflow + // the target, this would increase underflow diff = 0; } // Compensate the diff over 4 seconds (but will be recomputed after From 4502126e3b2ab2d5a82f636e32524929b3b0d07e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 Feb 2024 15:02:09 +0100 Subject: [PATCH 1082/1133] Use early return to avoid additional indentation PR #4572 --- app/src/audio_player.c | 107 +++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index 4d101b01..e978cd9f 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -253,66 +253,67 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, } atomic_store_explicit(&ap->received, true, memory_order_relaxed); + if (!played) { + // Nothing more to do + return true; + } - if (played) { - // Number of samples added (or removed, if negative) for compensation - int32_t instant_compensation = (int32_t) written - frame->nb_samples; - // Inserting silence instantly increases buffering - int32_t inserted_silence = (int32_t) underflow; - // Dropping input samples instantly decreases buffering - int32_t dropped = (int32_t) skipped_samples; + // Number of samples added (or removed, if negative) for compensation + int32_t instant_compensation = (int32_t) written - frame->nb_samples; + // Inserting silence instantly increases buffering + int32_t inserted_silence = (int32_t) underflow; + // Dropping input samples instantly decreases buffering + int32_t dropped = (int32_t) skipped_samples; - // The compensation must apply instantly, it must not be smoothed - ap->avg_buffering.avg += - instant_compensation + inserted_silence - dropped; + // The compensation must apply instantly, it must not be smoothed + ap->avg_buffering.avg += instant_compensation + inserted_silence - dropped; - // However, the buffering level must be smoothed - sc_average_push(&ap->avg_buffering, can_read); + // However, the buffering level must be smoothed + sc_average_push(&ap->avg_buffering, can_read); #ifndef SC_AUDIO_PLAYER_NDEBUG - LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f", - can_read, sc_average_get(&ap->avg_buffering)); + LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f", + can_read, sc_average_get(&ap->avg_buffering)); #endif - ap->samples_since_resync += written; - if (ap->samples_since_resync >= ap->sample_rate) { - // Recompute compensation every second - ap->samples_since_resync = 0; - - float avg = sc_average_get(&ap->avg_buffering); - int diff = ap->target_buffering - avg; - - // Enable compensation when the difference exceeds +/- 4ms. - // Disable compensation when the difference is lower than +/- 1ms. - int threshold = ap->compensation != 0 - ? ap->sample_rate / 1000 /* 1ms */ - : ap->sample_rate * 4 / 1000; /* 4ms */ - - if (abs(diff) < threshold) { - // Do not compensate for small values, the error is just noise - diff = 0; - } else if (diff < 0 && can_read < ap->target_buffering) { - // Do not accelerate if the instant buffering level is below - // the target, this would increase underflow - diff = 0; - } - // Compensate the diff over 4 seconds (but will be recomputed after - // 1 second) - int distance = 4 * ap->sample_rate; - // Limit compensation rate to 2% - int abs_max_diff = distance / 50; - diff = CLAMP(diff, -abs_max_diff, abs_max_diff); - LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32 - " compensation=%d", ap->target_buffering, avg, can_read, diff); - - if (diff != ap->compensation) { - int ret = swr_set_compensation(swr_ctx, diff, distance); - if (ret < 0) { - LOGW("Resampling compensation failed: %d", ret); - // not fatal - } else { - ap->compensation = diff; - } + ap->samples_since_resync += written; + if (ap->samples_since_resync >= ap->sample_rate) { + // Recompute compensation every second + ap->samples_since_resync = 0; + + float avg = sc_average_get(&ap->avg_buffering); + int diff = ap->target_buffering - avg; + + // Enable compensation when the difference exceeds +/- 4ms. + // Disable compensation when the difference is lower than +/- 1ms. + int threshold = ap->compensation != 0 + ? ap->sample_rate / 1000 /* 1ms */ + : ap->sample_rate * 4 / 1000; /* 4ms */ + + if (abs(diff) < threshold) { + // Do not compensate for small values, the error is just noise + diff = 0; + } else if (diff < 0 && can_read < ap->target_buffering) { + // Do not accelerate if the instant buffering level is below the + // target, this would increase underflow + diff = 0; + } + // Compensate the diff over 4 seconds (but will be recomputed after 1 + // second) + int distance = 4 * ap->sample_rate; + // Limit compensation rate to 2% + int abs_max_diff = distance / 50; + diff = CLAMP(diff, -abs_max_diff, abs_max_diff); + LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32 + " compensation=%d", ap->target_buffering, avg, can_read, diff); + + if (diff != ap->compensation) { + int ret = swr_set_compensation(swr_ctx, diff, distance); + if (ret < 0) { + LOGW("Resampling compensation failed: %d", ret); + // not fatal + } else { + ap->compensation = diff; } } } From c12fdf900fb82e8ce958881e24a9f4f6185c0db7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 7 Feb 2024 17:50:14 +0100 Subject: [PATCH 1083/1133] Minimize buffer underflow on starting If playback starts too early, insert silence until the buffer is filled up to at least target_buffering before playing. PR #4572 --- app/src/audio_player.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index e978cd9f..ea44e8d9 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -79,10 +79,9 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) { bool played = atomic_load_explicit(&ap->played, memory_order_relaxed); if (!played) { uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf); - // Part of the buffering is handled by inserting initial silence. The - // remaining (margin) last samples will be handled by compensation. - uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms - if (buffered_samples + margin < ap->target_buffering) { + // Wait until the buffer is filled up to at least target_buffering + // before playing + if (buffered_samples < ap->target_buffering) { LOGV("[Audio] Inserting initial buffering silence: %" PRIu32 " samples", count); // Delay playback starting to reach the target buffering. Fill the From a7cf4daf3bcb573add846c2f4c98b94944c05cb9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 8 Feb 2024 12:31:03 +0100 Subject: [PATCH 1084/1133] Avoid negative average buffering The assumption that underflow and overbuffering are caused by jitter (and that the delay between the producer and consumer will be caught up) does not always hold. For example, if the consumer does not consume at the expected rate (the SDL callback is not called often enough, which is an audio output issue), many samples will be dropped due to overbuffering, decreasing the average buffering indefinitely. Prevent the average buffering to become negative to limit the consequences of an unexpected behavior. PR #4572 --- app/src/audio_player.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index ea44e8d9..bd799c51 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -266,6 +266,16 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // The compensation must apply instantly, it must not be smoothed ap->avg_buffering.avg += instant_compensation + inserted_silence - dropped; + if (ap->avg_buffering.avg < 0) { + // Since dropping samples instantly reduces buffering, the difference + // is applied immediately to the average value, assuming that the delay + // between the producer and the consumer will be caught up. + // + // However, when this assumption is not valid, the average buffering + // may decrease indefinitely. Prevent it to become negative to limit + // the consequences. + ap->avg_buffering.avg = 0; + } // However, the buffering level must be smoothed sc_average_push(&ap->avg_buffering, can_read); From 25f1e703b7637c3eb1382e435113688520a38d36 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 22 Feb 2024 18:52:57 +0100 Subject: [PATCH 1085/1133] Extract ControlChannel class This prevents many components from depending on the whole DesktopConnection. --- .../com/genymobile/scrcpy/ControlChannel.java | 33 +++++++++++++++++++ .../com/genymobile/scrcpy/Controller.java | 10 +++--- .../genymobile/scrcpy/DesktopConnection.java | 32 ++++-------------- .../scrcpy/DeviceMessageSender.java | 10 +++--- .../java/com/genymobile/scrcpy/Server.java | 3 +- 5 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/ControlChannel.java diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlChannel.java b/server/src/main/java/com/genymobile/scrcpy/ControlChannel.java new file mode 100644 index 00000000..4677cfda --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/ControlChannel.java @@ -0,0 +1,33 @@ +package com.genymobile.scrcpy; + +import android.net.LocalSocket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class ControlChannel { + private final InputStream inputStream; + private final OutputStream outputStream; + + private final ControlMessageReader reader = new ControlMessageReader(); + private final DeviceMessageWriter writer = new DeviceMessageWriter(); + + public ControlChannel(LocalSocket controlSocket) throws IOException { + this.inputStream = controlSocket.getInputStream(); + this.outputStream = controlSocket.getOutputStream(); + } + + public ControlMessage recv() throws IOException { + ControlMessage msg = reader.next(); + while (msg == null) { + reader.readFrom(inputStream); + msg = reader.next(); + } + return msg; + } + + public void send(DeviceMessage msg) throws IOException { + writer.writeTo(msg, outputStream); + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index c0763012..257f732b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -27,7 +27,7 @@ public class Controller implements AsyncProcessor { private Thread thread; private final Device device; - private final DesktopConnection connection; + private final ControlChannel controlChannel; private final CleanUp cleanUp; private final DeviceMessageSender sender; private final boolean clipboardAutosync; @@ -42,14 +42,14 @@ public class Controller implements AsyncProcessor { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { + public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { this.device = device; - this.connection = connection; + this.controlChannel = controlChannel; this.cleanUp = cleanUp; this.clipboardAutosync = clipboardAutosync; this.powerOn = powerOn; initPointers(); - sender = new DeviceMessageSender(connection); + sender = new DeviceMessageSender(controlChannel); } private void initPointers() { @@ -123,7 +123,7 @@ public class Controller implements AsyncProcessor { } private void handleEvent() throws IOException { - ControlMessage msg = connection.receiveControlMessage(); + ControlMessage msg = controlChannel.recv(); switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: if (device.supportsInputEvents()) { diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 8bc743f8..d693ad61 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -7,8 +7,6 @@ import android.net.LocalSocketAddress; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; public final class DesktopConnection implements Closeable { @@ -24,25 +22,16 @@ public final class DesktopConnection implements Closeable { private final FileDescriptor audioFd; private final LocalSocket controlSocket; - private final InputStream controlInputStream; - private final OutputStream controlOutputStream; - - private final ControlMessageReader reader = new ControlMessageReader(); - private final DeviceMessageWriter writer = new DeviceMessageWriter(); + private final ControlChannel controlChannel; private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; - this.controlSocket = controlSocket; this.audioSocket = audioSocket; - if (controlSocket != null) { - controlInputStream = controlSocket.getInputStream(); - controlOutputStream = controlSocket.getOutputStream(); - } else { - controlInputStream = null; - controlOutputStream = null; - } + this.controlSocket = controlSocket; + videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null; audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; + controlChannel = controlSocket != null ? new ControlChannel(controlSocket) : null; } private static LocalSocket connect(String abstractName) throws IOException { @@ -179,16 +168,7 @@ public final class DesktopConnection implements Closeable { return audioFd; } - public ControlMessage receiveControlMessage() throws IOException { - ControlMessage msg = reader.next(); - while (msg == null) { - reader.readFrom(controlInputStream); - msg = reader.next(); - } - return msg; - } - - public void sendDeviceMessage(DeviceMessage msg) throws IOException { - writer.writeTo(msg, controlOutputStream); + public ControlChannel getControlChannel() { + return controlChannel; } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 94e842ee..efb7b975 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -4,7 +4,7 @@ import java.io.IOException; public final class DeviceMessageSender { - private final DesktopConnection connection; + private final ControlChannel controlChannel; private Thread thread; @@ -12,8 +12,8 @@ public final class DeviceMessageSender { private long ack; - public DeviceMessageSender(DesktopConnection connection) { - this.connection = connection; + public DeviceMessageSender(ControlChannel controlChannel) { + this.controlChannel = controlChannel; } public synchronized void pushClipboardText(String text) { @@ -43,11 +43,11 @@ public final class DeviceMessageSender { if (sequence != DeviceMessage.SEQUENCE_INVALID) { DeviceMessage event = DeviceMessage.createAckClipboard(sequence); - connection.sendDeviceMessage(event); + controlChannel.send(event); } if (text != null) { DeviceMessage event = DeviceMessage.createClipboard(text); - connection.sendDeviceMessage(event); + controlChannel.send(event); } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index bcafa133..3936648d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -131,7 +131,8 @@ public final class Server { } if (control) { - Controller controller = new Controller(device, connection, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); + ControlChannel controlChannel = connection.getControlChannel(); + Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); asyncProcessors.add(controller); } From 9e22f3bf1cca9a957173193250eaeb084ab0c245 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 23 Feb 2024 19:59:54 +0100 Subject: [PATCH 1086/1133] Replace unsigned char by uint8_t for buffers For consistency. --- app/src/control_msg.c | 4 +- app/src/control_msg.h | 2 +- app/src/controller.c | 2 +- app/src/device_msg.c | 3 +- app/src/device_msg.h | 3 +- app/src/receiver.c | 5 ++- app/src/server.c | 2 +- app/src/usb/aoa_hid.c | 4 +- app/src/usb/aoa_hid.h | 2 +- app/tests/test_control_msg_serialize.c | 57 +++++++++++++------------ app/tests/test_device_msg_deserialize.c | 10 ++--- 11 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index d4d6c62a..e173dac7 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -87,7 +87,7 @@ write_position(uint8_t *buf, const struct sc_position *position) { // write length (4 bytes) + string (non null-terminated) static size_t -write_string(const char *utf8, size_t max_len, unsigned char *buf) { +write_string(const char *utf8, size_t max_len, uint8_t *buf) { size_t len = sc_str_utf8_truncation_index(utf8, max_len); sc_write32be(buf, len); memcpy(&buf[4], utf8, len); @@ -95,7 +95,7 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) { } size_t -sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { +sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { buf[0] = msg->type; switch (msg->type) { case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: diff --git a/app/src/control_msg.h b/app/src/control_msg.h index b90a00b3..04eeb83b 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -98,7 +98,7 @@ struct sc_control_msg { // buf size must be at least CONTROL_MSG_MAX_SIZE // return the number of bytes written size_t -sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf); +sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf); void sc_control_msg_log(const struct sc_control_msg *msg); diff --git a/app/src/controller.c b/app/src/controller.c index 0139e42c..250321fe 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -84,7 +84,7 @@ sc_controller_push_msg(struct sc_controller *controller, static bool process_msg(struct sc_controller *controller, const struct sc_control_msg *msg) { - static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE]; + static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE]; size_t length = sc_control_msg_serialize(msg, serialized_msg); if (!length) { return false; diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 265c7505..9925cf97 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -8,8 +8,7 @@ #include "util/log.h" ssize_t -device_msg_deserialize(const unsigned char *buf, size_t len, - struct device_msg *msg) { +device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) { if (len < 5) { // at least type + empty string length return 0; // not available diff --git a/app/src/device_msg.h b/app/src/device_msg.h index e8d9fed4..3b68a61a 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -30,8 +30,7 @@ struct device_msg { // return the number of bytes consumed (0 for no msg available, -1 on error) ssize_t -device_msg_deserialize(const unsigned char *buf, size_t len, - struct device_msg *msg); +device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg); void device_msg_destroy(struct device_msg *msg); diff --git a/app/src/receiver.c b/app/src/receiver.c index e715a8e6..c08cd6cf 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -1,6 +1,7 @@ #include "receiver.h" #include +#include #include #include "device_msg.h" @@ -51,7 +52,7 @@ process_msg(struct sc_receiver *receiver, struct device_msg *msg) { } static ssize_t -process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) { +process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) { size_t head = 0; for (;;) { struct device_msg msg; @@ -78,7 +79,7 @@ static int run_receiver(void *data) { struct sc_receiver *receiver = data; - static unsigned char buf[DEVICE_MSG_MAX_SIZE]; + static uint8_t buf[DEVICE_MSG_MAX_SIZE]; size_t head = 0; for (;;) { diff --git a/app/src/server.c b/app/src/server.c index d4726c2a..4d55e994 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -498,7 +498,7 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params, static bool device_read_info(struct sc_intr *intr, sc_socket device_socket, struct sc_server_info *info) { - unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH]; + uint8_t buf[SC_DEVICE_NAME_FIELD_LENGTH]; ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf)); if (r < SC_DEVICE_NAME_FIELD_LENGTH) { LOGE("Could not retrieve device information"); diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index fb64e57c..9bad5296 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -113,7 +113,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, - const unsigned char *report_desc, + const uint8_t *report_desc, uint16_t report_desc_size) { uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request = ACCESSORY_SET_HID_REPORT_DESC; @@ -150,7 +150,7 @@ 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, - const unsigned char *report_desc, uint16_t report_desc_size) { + const uint8_t *report_desc, uint16_t report_desc_size) { bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size); if (!ok) { return false; diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 8803c1d9..fb5e1d28 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -57,7 +57,7 @@ sc_aoa_join(struct sc_aoa *aoa); bool sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, - const unsigned char *report_desc, uint16_t report_desc_size); + const uint8_t *report_desc, uint16_t report_desc_size); bool sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index b2eef49c..80d33fc3 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -1,6 +1,7 @@ #include "common.h" #include +#include #include #include "control_msg.h" @@ -16,11 +17,11 @@ static void test_serialize_inject_keycode(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 14); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, 0x01, // AKEY_EVENT_ACTION_UP 0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER @@ -38,11 +39,11 @@ static void test_serialize_inject_text(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 18); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_INJECT_TEXT, 0x00, 0x00, 0x00, 0x0d, // text length 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text @@ -58,11 +59,11 @@ static void test_serialize_inject_text_long(void) { text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; msg.inject_text.text = text; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); - unsigned char expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; + uint8_t expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; expected[0] = SC_CONTROL_MSG_TYPE_INJECT_TEXT; expected[1] = 0x00; expected[2] = 0x00; @@ -95,11 +96,11 @@ static void test_serialize_inject_touch_event(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 32); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, 0x00, // AKEY_EVENT_ACTION_DOWN 0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id @@ -132,11 +133,11 @@ static void test_serialize_inject_scroll_event(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 21); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x04, 0x38, 0x07, 0x80, // 1080 1920 @@ -155,11 +156,11 @@ static void test_serialize_back_or_screen_on(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, 0x01, // AKEY_EVENT_ACTION_UP }; @@ -171,11 +172,11 @@ static void test_serialize_expand_notification_panel(void) { .type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, }; assert(!memcmp(buf, expected, sizeof(expected))); @@ -186,11 +187,11 @@ static void test_serialize_expand_settings_panel(void) { .type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, }; assert(!memcmp(buf, expected, sizeof(expected))); @@ -201,11 +202,11 @@ static void test_serialize_collapse_panels(void) { .type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, }; assert(!memcmp(buf, expected, sizeof(expected))); @@ -219,11 +220,11 @@ static void test_serialize_get_clipboard(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, SC_COPY_KEY_COPY, }; @@ -240,11 +241,11 @@ static void test_serialize_set_clipboard(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 27); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence 1, // paste @@ -269,11 +270,11 @@ static void test_serialize_set_clipboard_long(void) { text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0'; msg.set_clipboard.text = text; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == SC_CONTROL_MSG_MAX_SIZE); - unsigned char expected[SC_CONTROL_MSG_MAX_SIZE] = { + uint8_t expected[SC_CONTROL_MSG_MAX_SIZE] = { SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence 1, // paste @@ -296,11 +297,11 @@ static void test_serialize_set_screen_power_mode(void) { }, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 2); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, 0x02, // SC_SCREEN_POWER_MODE_NORMAL }; @@ -312,11 +313,11 @@ static void test_serialize_rotate_device(void) { .type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, }; - unsigned char buf[SC_CONTROL_MSG_MAX_SIZE]; + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; size_t size = sc_control_msg_serialize(&msg, buf); assert(size == 1); - const unsigned char expected[] = { + const uint8_t expected[] = { SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index 835096c0..a1a3f695 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -1,14 +1,14 @@ #include "common.h" #include +#include +#include #include #include "device_msg.h" -#include - static void test_deserialize_clipboard(void) { - const unsigned char input[] = { + const uint8_t input[] = { DEVICE_MSG_TYPE_CLIPBOARD, 0x00, 0x00, 0x00, 0x03, // text length 0x41, 0x42, 0x43, // "ABC" @@ -26,7 +26,7 @@ static void test_deserialize_clipboard(void) { } static void test_deserialize_clipboard_big(void) { - unsigned char input[DEVICE_MSG_MAX_SIZE]; + uint8_t input[DEVICE_MSG_MAX_SIZE]; input[0] = DEVICE_MSG_TYPE_CLIPBOARD; input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24; input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16; @@ -48,7 +48,7 @@ static void test_deserialize_clipboard_big(void) { } static void test_deserialize_ack_set_clipboard(void) { - const unsigned char input[] = { + const uint8_t input[] = { DEVICE_MSG_TYPE_ACK_CLIPBOARD, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence }; From 9858eff85625abcbf392ae57220df1b1b03f793b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 23 Feb 2024 20:01:30 +0100 Subject: [PATCH 1087/1133] Fix device message deserialization checks If any message is incomplete, the deserialization method must return immediately. --- app/src/device_msg.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 9925cf97..f9f22a85 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -9,17 +9,20 @@ ssize_t device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) { - if (len < 5) { - // at least type + empty string length - return 0; // not available + if (!len) { + return 0; // no message } msg->type = buf[0]; switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { + if (len < 5) { + // at least type + empty string length + return 0; // no complete message + } size_t clipboard_len = sc_read32be(&buf[1]); if (clipboard_len > len - 5) { - return 0; // not available + return 0; // no complete message } char *text = malloc(clipboard_len + 1); if (!text) { @@ -35,6 +38,9 @@ device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) { return 5 + clipboard_len; } case DEVICE_MSG_TYPE_ACK_CLIPBOARD: { + if (len < 9) { + return 0; // no complete message + } uint64_t sequence = sc_read64be(&buf[1]); msg->ack_clipboard.sequence = sequence; return 9; From 78a7e4f293f59499fbb4be850a29e891171fcf4f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 23 Feb 2024 20:05:12 +0100 Subject: [PATCH 1088/1133] Use sc_ prefix for device sender --- app/src/device_msg.c | 5 +++-- app/src/device_msg.h | 11 ++++++----- app/src/receiver.c | 8 ++++---- app/tests/test_device_msg_deserialize.c | 16 ++++++++-------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/src/device_msg.c b/app/src/device_msg.c index f9f22a85..0cadc49c 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -8,7 +8,8 @@ #include "util/log.h" ssize_t -device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) { +sc_device_msg_deserialize(const uint8_t *buf, size_t len, + struct sc_device_msg *msg) { if (!len) { return 0; // no message } @@ -52,7 +53,7 @@ device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) { } void -device_msg_destroy(struct device_msg *msg) { +sc_device_msg_destroy(struct sc_device_msg *msg) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { free(msg->clipboard.text); } diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 3b68a61a..3f541cf5 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -11,13 +11,13 @@ // type: 1 byte; length: 4 bytes #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) -enum device_msg_type { +enum sc_device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_ACK_CLIPBOARD, }; -struct device_msg { - enum device_msg_type type; +struct sc_device_msg { + enum sc_device_msg_type type; union { struct { char *text; // owned, to be freed by free() @@ -30,9 +30,10 @@ struct device_msg { // return the number of bytes consumed (0 for no msg available, -1 on error) ssize_t -device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg); +sc_device_msg_deserialize(const uint8_t *buf, size_t len, + struct sc_device_msg *msg); void -device_msg_destroy(struct device_msg *msg); +sc_device_msg_destroy(struct sc_device_msg *msg); #endif diff --git a/app/src/receiver.c b/app/src/receiver.c index c08cd6cf..408e1db7 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -27,7 +27,7 @@ sc_receiver_destroy(struct sc_receiver *receiver) { } static void -process_msg(struct sc_receiver *receiver, struct device_msg *msg) { +process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) { switch (msg->type) { case DEVICE_MSG_TYPE_CLIPBOARD: { char *current = SDL_GetClipboardText(); @@ -55,8 +55,8 @@ static ssize_t process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) { size_t head = 0; for (;;) { - struct device_msg msg; - ssize_t r = device_msg_deserialize(&buf[head], len - head, &msg); + struct sc_device_msg msg; + ssize_t r = sc_device_msg_deserialize(&buf[head], len - head, &msg); if (r == -1) { return -1; } @@ -65,7 +65,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) { } process_msg(receiver, &msg); - device_msg_destroy(&msg); + sc_device_msg_destroy(&msg); head += r; assert(head <= len); diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index a1a3f695..bfbcefd6 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -14,15 +14,15 @@ static void test_deserialize_clipboard(void) { 0x41, 0x42, 0x43, // "ABC" }; - struct device_msg msg; - ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); + struct sc_device_msg msg; + ssize_t r = sc_device_msg_deserialize(input, sizeof(input), &msg); assert(r == 8); assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); assert(msg.clipboard.text); assert(!strcmp("ABC", msg.clipboard.text)); - device_msg_destroy(&msg); + sc_device_msg_destroy(&msg); } static void test_deserialize_clipboard_big(void) { @@ -35,8 +35,8 @@ static void test_deserialize_clipboard_big(void) { memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH); - struct device_msg msg; - ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); + struct sc_device_msg msg; + ssize_t r = sc_device_msg_deserialize(input, sizeof(input), &msg); assert(r == DEVICE_MSG_MAX_SIZE); assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD); @@ -44,7 +44,7 @@ static void test_deserialize_clipboard_big(void) { assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH); assert(msg.clipboard.text[0] == 'a'); - device_msg_destroy(&msg); + sc_device_msg_destroy(&msg); } static void test_deserialize_ack_set_clipboard(void) { @@ -53,8 +53,8 @@ static void test_deserialize_ack_set_clipboard(void) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence }; - struct device_msg msg; - ssize_t r = device_msg_deserialize(input, sizeof(input), &msg); + struct sc_device_msg msg; + ssize_t r = sc_device_msg_deserialize(input, sizeof(input), &msg); assert(r == 9); assert(msg.type == DEVICE_MSG_TYPE_ACK_CLIPBOARD); From 746eaea55683e8e97ba7763bc0fa567227004c5d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Feb 2024 08:33:11 +0100 Subject: [PATCH 1089/1133] Add missing clipboard workaround for IQOO device The first part of the workaround fixed getPrimaryClip(). This part fixes setPrimaryClip(). Fixes #4703 Refs 5ce8672ebc56b7286e1078a39abc64903e5664d0 Refs #4492 --- .../scrcpy/wrappers/ClipboardManager.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 2c8d9907..ed5c8d75 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -87,9 +87,15 @@ public final class ClipboardManager { setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class); setMethodVersion = 1; } catch (NoSuchMethodException e2) { - setPrimaryClipMethod = manager.getClass() - .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class); - setMethodVersion = 2; + try { + setPrimaryClipMethod = manager.getClass() + .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class); + setMethodVersion = 2; + } catch (NoSuchMethodException e3) { + setPrimaryClipMethod = manager.getClass() + .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class, boolean.class); + setMethodVersion = 3; + } } } } @@ -132,9 +138,12 @@ public final class ClipboardManager { case 1: method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); break; - default: + case 2: method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); break; + default: + // The last boolean parameter is "userOperate" + method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true); } } From d894e270a7719b92e38b4f5e0294b9d55e90a6df Mon Sep 17 00:00:00 2001 From: eiyooooo Date: Sat, 24 Feb 2024 01:10:35 +0800 Subject: [PATCH 1090/1133] Add rotation support for non-default display Use new methods introduced by this commit: PR #4698 Signed-off-by: Romain Vimont --- .../com/genymobile/scrcpy/Controller.java | 2 +- .../java/com/genymobile/scrcpy/Device.java | 19 +++-- .../scrcpy/wrappers/WindowManager.java | 76 ++++++++++++++++--- 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 257f732b..73d6ad57 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -180,7 +180,7 @@ public class Controller implements AsyncProcessor { } break; case ControlMessage.TYPE_ROTATE_DEVICE: - Device.rotateDevice(); + device.rotateDevice(); break; default: // do nothing diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 33b09a57..8d0ee231 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -359,21 +359,30 @@ public final class Device { /** * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). */ - public static void rotateDevice() { + public void rotateDevice() { WindowManager wm = ServiceManager.getWindowManager(); - boolean accelerometerRotation = !wm.isRotationFrozen(); + boolean accelerometerRotation = !wm.isRotationFrozen(displayId); - int currentRotation = wm.getRotation(); + int currentRotation = getCurrentRotation(displayId); int newRotation = (currentRotation & 1) ^ 1; // 0->1, 1->0, 2->1, 3->0 String newRotationString = newRotation == 0 ? "portrait" : "landscape"; Ln.i("Device rotation requested: " + newRotationString); - wm.freezeRotation(newRotation); + wm.freezeRotation(displayId, newRotation); // restore auto-rotate if necessary if (accelerometerRotation) { - wm.thawRotation(); + wm.thawRotation(displayId); } } + + private static int getCurrentRotation(int displayId) { + if (displayId == 0) { + return ServiceManager.getWindowManager().getRotation(); + } + + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + return displayInfo.getRotation(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index b19dace9..d9654b1b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -13,8 +13,11 @@ public final class WindowManager { private final IInterface manager; private Method getRotationMethod; private Method freezeRotationMethod; + private Method freezeDisplayRotationMethod; private Method isRotationFrozenMethod; + private Method isDisplayRotationFrozenMethod; private Method thawRotationMethod; + private Method thawDisplayRotationMethod; static WindowManager create() { IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); @@ -47,6 +50,15 @@ public final class WindowManager { return freezeRotationMethod; } + // New method added by this commit: + // + private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException { + if (freezeDisplayRotationMethod == null) { + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); + } + return freezeDisplayRotationMethod; + } + private Method getIsRotationFrozenMethod() throws NoSuchMethodException { if (isRotationFrozenMethod == null) { isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); @@ -54,6 +66,15 @@ public final class WindowManager { return isRotationFrozenMethod; } + // New method added by this commit: + // + private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException { + if (isDisplayRotationFrozenMethod == null) { + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); + } + return isDisplayRotationFrozenMethod; + } + private Method getThawRotationMethod() throws NoSuchMethodException { if (thawRotationMethod == null) { thawRotationMethod = manager.getClass().getMethod("thawRotation"); @@ -61,6 +82,15 @@ public final class WindowManager { return thawRotationMethod; } + // New method added by this commit: + // + private Method getThawDisplayRotationMethod() throws NoSuchMethodException { + if (thawDisplayRotationMethod == null) { + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); + } + return thawDisplayRotationMethod; + } + public int getRotation() { try { Method method = getGetRotationMethod(); @@ -71,29 +101,57 @@ public final class WindowManager { } } - public void freezeRotation(int rotation) { + public void freezeRotation(int displayId, int rotation) { try { - Method method = getFreezeRotationMethod(); - method.invoke(manager, rotation); + try { + Method method = getFreezeDisplayRotationMethod(); + method.invoke(manager, displayId, rotation); + } catch (ReflectiveOperationException e) { + if (displayId == 0) { + Method method = getFreezeRotationMethod(); + method.invoke(manager, rotation); + } else { + Ln.e("Could not invoke method", e); + } + } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } } - public boolean isRotationFrozen() { + public boolean isRotationFrozen(int displayId) { try { - Method method = getIsRotationFrozenMethod(); - return (boolean) method.invoke(manager); + try { + Method method = getIsDisplayRotationFrozenMethod(); + return (boolean) method.invoke(manager, displayId); + } catch (ReflectiveOperationException e) { + if (displayId == 0) { + Method method = getIsRotationFrozenMethod(); + return (boolean) method.invoke(manager); + } else { + Ln.e("Could not invoke method", e); + return false; + } + } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); return false; } } - public void thawRotation() { + public void thawRotation(int displayId) { try { - Method method = getThawRotationMethod(); - method.invoke(manager); + try { + Method method = getThawDisplayRotationMethod(); + method.invoke(manager, displayId); + } catch (ReflectiveOperationException e) { + if (displayId == 0) { + Method method = getThawRotationMethod(); + method.invoke(manager); + } else { + Ln.e("Could not invoke method", e); + } + } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); } From 295102a6d91e5a71fbcc7fa3f17a2cea9c9eb9e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 27 Feb 2024 09:59:23 +0100 Subject: [PATCH 1091/1133] Check device messages assumptions at runtime Do not assume the server behaves correctly (scrcpy should not require the device to be trusted). --- app/src/receiver.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/receiver.c b/app/src/receiver.c index 408e1db7..6be705e3 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -43,9 +43,19 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) { break; } case DEVICE_MSG_TYPE_ACK_CLIPBOARD: - assert(receiver->acksync); LOGD("Ack device clipboard sequence=%" PRIu64_, msg->ack_clipboard.sequence); + + // This is a programming error to receive this message if there is + // no ACK synchronization mechanism + assert(receiver->acksync); + + // Also check at runtime (do not trust the server) + if (!receiver->acksync) { + LOGE("Received unexpected ack"); + return; + } + sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence); break; } From f6459dd742f356fade275e2178aa9ecee05c23cc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 10:26:49 +0100 Subject: [PATCH 1092/1133] Fix FAQ link Refs ad05a018003a66b0a5f8afefb0d2f16a392d3077 --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index a6eaeefa..6d02361b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -222,7 +222,7 @@ java.lang.IllegalStateException at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method) ``` -then try with another [encoder](doc/video.md#codec). +then try with another [encoder](doc/video.md#encoder). ## Translations From ffa238b9d35bb9c882537d32724bbadbe4da7ef6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 23:55:44 +0100 Subject: [PATCH 1093/1133] Remove duplicate lines in libusb script --- app/prebuilt-deps/prepare-libusb.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh index 228a5bfa..b31c45eb 100755 --- a/app/prebuilt-deps/prepare-libusb.sh +++ b/app/prebuilt-deps/prepare-libusb.sh @@ -26,8 +26,6 @@ cd "$DEP_DIR" 7z x "../$FILENAME" \ "libusb-$VERSION-binaries/libusb-MinGW-Win32/" \ - "libusb-$VERSION-binaries/libusb-MinGW-Win32/" \ - "libusb-$VERSION-binaries/libusb-MinGW-x64/" \ "libusb-$VERSION-binaries/libusb-MinGW-x64/" mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" . From a97641757237fc9342f2d1b4b9573a99761ffc21 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Mar 2024 00:11:48 +0100 Subject: [PATCH 1094/1133] Fix typo in error message --- app/src/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/display.c b/app/src/display.c index 906b5d65..ba15cd14 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -59,7 +59,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { LOGI("Trilinear filtering disabled"); } } else if (mipmaps) { - LOGD("Trilinear filtering disabled (not an OpenGL renderer"); + LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } display->pending.flags = 0; From c0a1aee8cea2ce6a5dbe39117d0505786ea0db7b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Jan 2024 18:19:39 +0100 Subject: [PATCH 1095/1133] Always pass input manager instance Some functions in input_manager.c only have access to a sub-object (for example the controller). For consistency, always pass the whole input manager instance. This will allow to add assertions when keyboard and mouse could be disabled separately. PR #4473 --- app/src/input_manager.c | 193 ++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 86 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 76cfbd92..8e7a6402 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -87,8 +87,10 @@ sc_input_manager_init(struct sc_input_manager *im, } static void -send_keycode(struct sc_controller *controller, enum android_keycode keycode, +send_keycode(struct sc_input_manager *im, enum android_keycode keycode, enum sc_action action, const char *name) { + assert(im->controller); + // send DOWN event struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE; @@ -99,100 +101,109 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode, msg.inject_keycode.metastate = 0; msg.inject_keycode.repeat = 0; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'inject %s'", name); } } static inline void -action_home(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_HOME, action, "HOME"); +action_home(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_HOME, action, "HOME"); } static inline void -action_back(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_BACK, action, "BACK"); +action_back(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_BACK, action, "BACK"); } static inline void -action_app_switch(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH"); +action_app_switch(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_APP_SWITCH, action, "APP_SWITCH"); } static inline void -action_power(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_POWER, action, "POWER"); +action_power(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_POWER, action, "POWER"); } static inline void -action_volume_up(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP"); +action_volume_up(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_VOLUME_UP, action, "VOLUME_UP"); } static inline void -action_volume_down(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN"); +action_volume_down(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN"); } static inline void -action_menu(struct sc_controller *controller, enum sc_action action) { - send_keycode(controller, AKEYCODE_MENU, action, "MENU"); +action_menu(struct sc_input_manager *im, enum sc_action action) { + send_keycode(im, AKEYCODE_MENU, action, "MENU"); } // turn the screen on if it was off, press BACK otherwise // If the screen is off, it is turned on only on ACTION_DOWN static void -press_back_or_turn_screen_on(struct sc_controller *controller, +press_back_or_turn_screen_on(struct sc_input_manager *im, enum sc_action action) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.back_or_screen_on.action = action == SC_ACTION_DOWN ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'press back or turn screen on'"); } } static void -expand_notification_panel(struct sc_controller *controller) { +expand_notification_panel(struct sc_input_manager *im) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'expand notification panel'"); } } static void -expand_settings_panel(struct sc_controller *controller) { +expand_settings_panel(struct sc_input_manager *im) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'expand settings panel'"); } } static void -collapse_panels(struct sc_controller *controller) { +collapse_panels(struct sc_input_manager *im) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'collapse notification panel'"); } } static bool -get_device_clipboard(struct sc_controller *controller, - enum sc_copy_key copy_key) { +get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD; msg.get_clipboard.copy_key = copy_key; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'get device clipboard'"); return false; } @@ -201,8 +212,10 @@ get_device_clipboard(struct sc_controller *controller, } static bool -set_device_clipboard(struct sc_controller *controller, bool paste, +set_device_clipboard(struct sc_input_manager *im, bool paste, uint64_t sequence) { + assert(im->controller); + char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); @@ -222,7 +235,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste, msg.set_clipboard.text = text_dup; msg.set_clipboard.paste = paste; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { free(text_dup); LOGW("Could not request 'set device clipboard'"); return false; @@ -232,19 +245,23 @@ set_device_clipboard(struct sc_controller *controller, bool paste, } static void -set_screen_power_mode(struct sc_controller *controller, +set_screen_power_mode(struct sc_input_manager *im, enum sc_screen_power_mode mode) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.set_screen_power_mode.mode = mode; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request 'set screen power mode'"); } } static void -switch_fps_counter_state(struct sc_fps_counter *fps_counter) { +switch_fps_counter_state(struct sc_input_manager *im) { + struct sc_fps_counter *fps_counter = &im->screen->fps_counter; + // the started state can only be written from the current thread, so there // is no ToCToU issue if (sc_fps_counter_is_started(fps_counter)) { @@ -256,7 +273,9 @@ switch_fps_counter_state(struct sc_fps_counter *fps_counter) { } static void -clipboard_paste(struct sc_controller *controller) { +clipboard_paste(struct sc_input_manager *im) { + assert(im->controller); + char *text = SDL_GetClipboardText(); if (!text) { LOGW("Could not get clipboard text: %s", SDL_GetError()); @@ -278,25 +297,28 @@ clipboard_paste(struct sc_controller *controller) { struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT; msg.inject_text.text = text_dup; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { free(text_dup); LOGW("Could not request 'paste clipboard'"); } } static void -rotate_device(struct sc_controller *controller) { +rotate_device(struct sc_input_manager *im) { + assert(im->controller); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE; - if (!sc_controller_push_msg(controller, &msg)) { + if (!sc_controller_push_msg(im->controller, &msg)) { LOGW("Could not request device rotation"); } } static void -apply_orientation_transform(struct sc_screen *screen, +apply_orientation_transform(struct sc_input_manager *im, enum sc_orientation transform) { + struct sc_screen *screen = im->screen; enum sc_orientation new_orientation = sc_orientation_apply(screen->orientation, transform); sc_screen_set_orientation(screen, new_orientation); @@ -364,7 +386,7 @@ static void sc_input_manager_process_key(struct sc_input_manager *im, const SDL_KeyboardEvent *event) { // controller is NULL if --no-control is requested - struct sc_controller *controller = im->controller; + bool control = im->controller; SDL_Keycode keycode = event->keysym.sym; uint16_t mod = event->keysym.mod; @@ -390,68 +412,68 @@ sc_input_manager_process_key(struct sc_input_manager *im, enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: - if (controller && !shift && !repeat) { - action_home(controller, action); + if (control && !shift && !repeat) { + action_home(im, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (controller && !shift && !repeat) { - action_back(controller, action); + if (control && !shift && !repeat) { + action_back(im, action); } return; case SDLK_s: - if (controller && !shift && !repeat) { - action_app_switch(controller, action); + if (control && !shift && !repeat) { + action_app_switch(im, action); } return; case SDLK_m: - if (controller && !shift && !repeat) { - action_menu(controller, action); + if (control && !shift && !repeat) { + action_menu(im, action); } return; case SDLK_p: - if (controller && !shift && !repeat) { - action_power(controller, action); + if (control && !shift && !repeat) { + action_power(im, action); } return; case SDLK_o: - if (controller && !repeat && down) { + if (control && !repeat && down) { enum sc_screen_power_mode mode = shift ? SC_SCREEN_POWER_MODE_NORMAL : SC_SCREEN_POWER_MODE_OFF; - set_screen_power_mode(controller, mode); + set_screen_power_mode(im, mode); } return; case SDLK_DOWN: if (shift) { if (!repeat & down) { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (controller) { + } else if (control) { // forward repeated events - action_volume_down(controller, action); + action_volume_down(im, action); } return; case SDLK_UP: if (shift) { if (!repeat & down) { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (controller) { + } else if (control) { // forward repeated events - action_volume_up(controller, action); + action_volume_up(im, action); } return; case SDLK_LEFT: if (!repeat && down) { if (shift) { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_FLIP_0); } else { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_270); } } @@ -459,34 +481,33 @@ sc_input_manager_process_key(struct sc_input_manager *im, case SDLK_RIGHT: if (!repeat && down) { if (shift) { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_FLIP_0); } else { - apply_orientation_transform(im->screen, + apply_orientation_transform(im, SC_ORIENTATION_90); } } return; case SDLK_c: - if (controller && !shift && !repeat && down) { - get_device_clipboard(controller, SC_COPY_KEY_COPY); + if (control && !shift && !repeat && down) { + get_device_clipboard(im, SC_COPY_KEY_COPY); } return; case SDLK_x: - if (controller && !shift && !repeat && down) { - get_device_clipboard(controller, SC_COPY_KEY_CUT); + if (control && !shift && !repeat && down) { + get_device_clipboard(im, SC_COPY_KEY_CUT); } return; case SDLK_v: - if (controller && !repeat && down) { + if (control && !repeat && down) { if (shift || im->legacy_paste) { // inject the text as input events - clipboard_paste(controller); + clipboard_paste(im); } else { // store the text in the device clipboard and paste, // without requesting an acknowledgment - set_device_clipboard(controller, true, - SC_SEQUENCE_INVALID); + set_device_clipboard(im, true, SC_SEQUENCE_INVALID); } } return; @@ -507,23 +528,23 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_i: if (!shift && !repeat && down) { - switch_fps_counter_state(&im->screen->fps_counter); + switch_fps_counter_state(im); } return; case SDLK_n: - if (controller && !repeat && down) { + if (control && !repeat && down) { if (shift) { - collapse_panels(controller); + collapse_panels(im); } else if (im->key_repeat == 0) { - expand_notification_panel(controller); + expand_notification_panel(im); } else { - expand_settings_panel(controller); + expand_settings_panel(im); } } return; case SDLK_r: - if (controller && !shift && !repeat && down) { - rotate_device(controller); + if (control && !shift && !repeat && down) { + rotate_device(im); } return; } @@ -531,7 +552,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; } - if (!controller) { + if (!control) { return; } @@ -540,7 +561,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, if (im->clipboard_autosync && is_ctrl_v) { if (im->legacy_paste) { // inject the text as input events - clipboard_paste(controller); + clipboard_paste(im); return; } @@ -550,7 +571,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, // Synchronize the computer clipboard to the device clipboard before // sending Ctrl+v, to allow seamless copy-paste. - bool ok = set_device_clipboard(controller, false, sequence); + bool ok = set_device_clipboard(im, false, sequence); if (!ok) { LOGW("Clipboard could not be synchronized, Ctrl+v not injected"); return; @@ -652,7 +673,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im, static void sc_input_manager_process_mouse_button(struct sc_input_manager *im, const SDL_MouseButtonEvent *event) { - struct sc_controller *controller = im->controller; + bool control = im->controller; if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate @@ -661,27 +682,27 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, bool down = event->type == SDL_MOUSEBUTTONDOWN; if (!im->forward_all_clicks) { - if (controller) { + if (control) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; if (event->button == SDL_BUTTON_X1) { - action_app_switch(controller, action); + action_app_switch(im, action); return; } if (event->button == SDL_BUTTON_X2 && down) { if (event->clicks < 2) { - expand_notification_panel(controller); + expand_notification_panel(im); } else { - expand_settings_panel(controller); + expand_settings_panel(im); } return; } if (event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(controller, action); + press_back_or_turn_screen_on(im, action); return; } if (event->button == SDL_BUTTON_MIDDLE) { - action_home(controller, action); + action_home(im, action); return; } } @@ -704,7 +725,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, // otherwise, send the click event to the device } - if (!controller) { + if (!control) { return; } From 35add3daee0907f4ca4e706c60d1c00a27702a79 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Jan 2024 18:34:33 +0100 Subject: [PATCH 1096/1133] Accept disabled keyboard or mouse The input manager assumed that if a controller was present, then both a key processor and a mouse processor were present. Remove this assumption, to support disabling keyboard and mouse separately. This prepares the introduction of new command line options --keyboard and --mouse. PR #4473 --- app/src/input_manager.c | 55 ++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 8e7a6402..7186186f 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -52,8 +52,11 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) { void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { - assert(!params->controller || (params->kp && params->kp->ops)); - assert(!params->controller || (params->mp && params->mp->ops)); + // A key/mouse processor may not be present if there is no controller + assert((!params->kp && !params->mp) || params->controller); + // A processor must have ops initialized + assert(!params->kp || params->kp->ops); + assert(!params->mp || params->mp->ops); im->controller = params->controller; im->fp = params->fp; @@ -89,7 +92,7 @@ sc_input_manager_init(struct sc_input_manager *im, static void send_keycode(struct sc_input_manager *im, enum android_keycode keycode, enum sc_action action, const char *name) { - assert(im->controller); + assert(im->controller && im->kp); // send DOWN event struct sc_control_msg msg; @@ -146,7 +149,7 @@ action_menu(struct sc_input_manager *im, enum sc_action action) { static void press_back_or_turn_screen_on(struct sc_input_manager *im, enum sc_action action) { - assert(im->controller); + assert(im->controller && im->kp); struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; @@ -197,7 +200,7 @@ collapse_panels(struct sc_input_manager *im) { static bool get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) { - assert(im->controller); + assert(im->controller && im->kp); struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD; @@ -214,7 +217,7 @@ get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) { static bool set_device_clipboard(struct sc_input_manager *im, bool paste, uint64_t sequence) { - assert(im->controller); + assert(im->controller && im->kp); char *text = SDL_GetClipboardText(); if (!text) { @@ -274,7 +277,7 @@ switch_fps_counter_state(struct sc_input_manager *im) { static void clipboard_paste(struct sc_input_manager *im) { - assert(im->controller); + assert(im->controller && im->kp); char *text = SDL_GetClipboardText(); if (!text) { @@ -412,28 +415,28 @@ sc_input_manager_process_key(struct sc_input_manager *im, enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: - if (control && !shift && !repeat) { + if (im->kp && !shift && !repeat) { action_home(im, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && !shift && !repeat) { + if (im->kp && !shift && !repeat) { action_back(im, action); } return; case SDLK_s: - if (control && !shift && !repeat) { + if (im->kp && !shift && !repeat) { action_app_switch(im, action); } return; case SDLK_m: - if (control && !shift && !repeat) { + if (im->kp && !shift && !repeat) { action_menu(im, action); } return; case SDLK_p: - if (control && !shift && !repeat) { + if (im->kp && !shift && !repeat) { action_power(im, action); } return; @@ -451,7 +454,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (control) { + } else if (im->kp) { // forward repeated events action_volume_down(im, action); } @@ -462,7 +465,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (control) { + } else if (im->kp) { // forward repeated events action_volume_up(im, action); } @@ -490,17 +493,17 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_c: - if (control && !shift && !repeat && down) { + if (im->kp && !shift && !repeat && down) { get_device_clipboard(im, SC_COPY_KEY_COPY); } return; case SDLK_x: - if (control && !shift && !repeat && down) { + if (im->kp && !shift && !repeat && down) { get_device_clipboard(im, SC_COPY_KEY_CUT); } return; case SDLK_v: - if (control && !repeat && down) { + if (im->kp && !repeat && down) { if (shift || im->legacy_paste) { // inject the text as input events clipboard_paste(im); @@ -552,7 +555,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; } - if (!control) { + if (!im->kp) { return; } @@ -685,7 +688,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, if (control) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; - if (event->button == SDL_BUTTON_X1) { + if (im->kp && event->button == SDL_BUTTON_X1) { action_app_switch(im, action); return; } @@ -697,11 +700,11 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, } return; } - if (event->button == SDL_BUTTON_RIGHT) { + if (im->kp && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(im, action); return; } - if (event->button == SDL_BUTTON_MIDDLE) { + if (im->kp && event->button == SDL_BUTTON_MIDDLE) { action_home(im, action); return; } @@ -725,7 +728,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, // otherwise, send the click event to the device } - if (!control) { + if (!im->mp) { return; } @@ -865,7 +868,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im, bool control = im->controller; switch (event->type) { case SDL_TEXTINPUT: - if (!control) { + if (!im->kp) { break; } sc_input_manager_process_text_input(im, &event->text); @@ -877,13 +880,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im, sc_input_manager_process_key(im, &event->key); break; case SDL_MOUSEMOTION: - if (!control) { + if (!im->mp) { break; } sc_input_manager_process_mouse_motion(im, &event->motion); break; case SDL_MOUSEWHEEL: - if (!control) { + if (!im->mp) { break; } sc_input_manager_process_mouse_wheel(im, &event->wheel); @@ -897,7 +900,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im, case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: - if (!control) { + if (!im->mp) { break; } sc_input_manager_process_touch(im, &event->tfinger); From ea98d49baed749eea0f3c9a02c9f019e37c85af6 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:17:35 +0800 Subject: [PATCH 1097/1133] Introduce --keyboard and --mouse Until now, there was two modes for keyboard and mouse: - event injection using the Android system API (default) - HID/AOA over USB For this reason, the options were exposed as simple flags: - -K or --hid-keyboard to enable physical keyboard simulation (AOA) - -M or --hid-mouse to enable physical mouse simulation (AOA) Replace them by explicit --keyboard and --mouse options, with 3 possible values: - disabled - sdk (default) - aoa This will allow to add a new mode (uhid). PR #4473 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 12 ++- app/data/zsh-completion/_scrcpy | 4 +- app/scrcpy.1 | 47 +++++---- app/src/cli.c | 175 ++++++++++++++++++++++++++------ app/src/options.c | 4 +- app/src/options.h | 12 ++- app/src/scrcpy.c | 36 ++++--- app/src/usb/scrcpy_otg.c | 15 ++- 8 files changed, 219 insertions(+), 86 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 78aa539d..b2009c56 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -27,8 +27,8 @@ _scrcpy() { --force-adb-forward --forward-all-clicks -h --help + --keyboard= --kill-adb-on-close - -K --hid-keyboard --legacy-paste --list-camera-sizes --list-cameras @@ -37,8 +37,8 @@ _scrcpy() { --lock-video-orientation --lock-video-orientation= -m --max-size= - -M --hid-mouse --max-fps= + --mouse= -n --no-control -N --no-playback --no-audio @@ -115,6 +115,14 @@ _scrcpy() { COMPREPLY=($(compgen -W 'front back external' -- "$cur")) return ;; + --keyboard) + COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + return + ;; + --mouse) + COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + return + ;; --orientation|--display-orientation) COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 3c7ca217..a4611632 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -34,8 +34,8 @@ arguments=( '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' + '--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa)' '--kill-adb-on-close[Kill adb when scrcpy terminates]' - {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-camera-sizes[List the valid camera capture sizes]' '--list-cameras[List cameras available on the device]' @@ -43,8 +43,8 @@ arguments=( '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' {-m,--max-size=}'[Limit both the width and height of the video to value]' - {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' '--max-fps=[Limit the frame rate of screen capture]' + '--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index beaa99ab..ed2e620e 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -172,24 +172,26 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO Print this help. .TP -.B \-\-kill\-adb\-on\-close -Kill adb when scrcpy terminates. +.BI "\-\-keyboard " mode +Select how to send keyboard inputs to the device. -.TP -.B \-K, \-\-hid\-keyboard -Simulate a physical keyboard by using HID over AOAv2. +Possible values are "disabled", "sdk" and "aoa": -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. + - "disabled" does not send keyboard inputs to the device. + - "sdk" uses the Android system API to deliver keyboard events to applications. + - "aoa" simulates a physical keyboard using the AOAv2 protocol. It may only work over USB. -The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: +For "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected). +This option is only available when the HID keyboard is enabled (or a physical keyboard is connected). -Also see \fB\-\-hid\-mouse\fR. +Also see \fB\-\-mouse\fR. + +.TP +.B \-\-kill\-adb\-on\-close +Kill adb when scrcpy terminates. .TP .B \-\-legacy\-paste @@ -230,20 +232,25 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension Default is 0 (unlimited). .TP -.B \-M, \-\-hid\-mouse -Simulate a physical mouse by using HID over AOAv2. +.BI "\-\-max\-fps " value +Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). + +.TP +.BI "\-\-mouse " mode +Select how to send mouse inputs to the device. -In this mode, the computer mouse is captured to control the device directly (relative mouse mode). +Possible values are "disabled", "sdk" and "aoa": -LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. + - "disabled" does not send mouse inputs to the device. + - "sdk" uses the Android system API to deliver mouse events to applications. + - "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB. -It may only work over USB. +In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode). -Also see \fB\-\-hid\-keyboard\fR. +LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. + +Also see \fB\-\-keyboard\fR. -.TP -.BI "\-\-max\-fps " value -Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). .TP .B \-n, \-\-no\-control diff --git a/app/src/cli.c b/app/src/cli.c index b2b02ecd..364590a4 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -93,6 +93,8 @@ enum { OPT_DISPLAY_ORIENTATION, OPT_RECORD_ORIENTATION, OPT_ORIENTATION, + OPT_KEYBOARD, + OPT_MOUSE, }; struct sc_option { @@ -358,27 +360,35 @@ static const struct sc_option options[] = { .longopt = "help", .text = "Print this help.", }, + { + .longopt_id = OPT_KEYBOARD, + .longopt = "keyboard", + .argdesc = "mode", + .text = "Select how to send keyboard inputs to the device.\n" + "Possible values are \"disabled\", \"sdk\" and \"aoa\".\n" + "\"disabled\" does not send keyboard inputs to the device.\n" + "\"sdk\" uses the Android system API to deliver keyboard " + "events to applications.\n" + "\"aoa\" simulates a physical keyboard using the AOAv2 " + "protocol. It may only work over USB.\n" + "For \"aoa\", the keyboard layout must be configured (once and " + "for all) on the device, via Settings -> System -> Languages " + "and input -> Physical keyboard. This settings page can be " + "started directly: `adb shell am start -a " + "android.settings.HARD_KEYBOARD_SETTINGS`.\n" + "This option is only available when the HID keyboard is " + "enabled (or a physical keyboard is connected).\n" + "Also see --mouse.", + }, { .longopt_id = OPT_KILL_ADB_ON_CLOSE, .longopt = "kill-adb-on-close", .text = "Kill adb when scrcpy terminates.", }, { + // deprecated .shortopt = 'K', .longopt = "hid-keyboard", - .text = "Simulate a physical keyboard by using HID over AOAv2.\n" - "It provides a better experience for IME users, and allows to " - "generate non-ASCII characters, contrary to the default " - "injection method.\n" - "It may only work over USB.\n" - "The keyboard layout must be configured (once and for all) on " - "the device, via Settings -> System -> Languages and input -> " - "Physical keyboard. This settings page can be started " - "directly: `adb shell am start -a " - "android.settings.HARD_KEYBOARD_SETTINGS`.\n" - "However, the option is only available when the HID keyboard " - "is enabled (or a physical keyboard is connected).\n" - "Also see --hid-mouse.", }, { .longopt_id = OPT_LEGACY_PASTE, @@ -432,15 +442,9 @@ static const struct sc_option options[] = { "Default is 0 (unlimited).", }, { + // deprecated .shortopt = 'M', .longopt = "hid-mouse", - .text = "Simulate a physical mouse by using HID over AOAv2.\n" - "In this mode, the computer mouse is captured to control the " - "device directly (relative mouse mode).\n" - "LAlt, LSuper or RSuper toggle the capture mode, to give " - "control of the mouse back to the computer.\n" - "It may only work over USB.\n" - "Also see --hid-keyboard.", }, { .longopt_id = OPT_MAX_FPS, @@ -449,6 +453,23 @@ static const struct sc_option options[] = { .text = "Limit the frame rate of screen capture (officially supported " "since Android 10, but may work on earlier versions).", }, + { + .longopt_id = OPT_MOUSE, + .longopt = "mouse", + .argdesc = "mode", + .text = "Select how to send mouse inputs to the device.\n" + "Possible values are \"disabled\", \"sdk\" and \"aoa\".\n" + "\"disabled\" does not send mouse inputs to the device.\n" + "\"sdk\" uses the Android system API to deliver mouse events" + "to applications.\n" + "\"aoa\" simulates a physical mouse using the AOAv2 protocol. " + "It may only work over USB.\n" + "In \"aoa\" mode, the computer mouse is captured to control " + "the device directly (relative mouse mode).\n" + "LAlt, LSuper or RSuper toggle the capture mode, to give " + "control of the mouse back to the computer.\n" + "Also see --keyboard.", + }, { .shortopt = 'n', .longopt = "no-control", @@ -543,10 +564,10 @@ static const struct sc_option options[] = { "mirroring is disabled.\n" "LAlt, LSuper or RSuper toggle the mouse capture mode, to give " "control of the mouse back to the computer.\n" - "If any of --hid-keyboard or --hid-mouse is set, only enable " - "keyboard or mouse respectively, otherwise enable both.\n" + "Keyboard and mouse may be disabled separately using" + "--keyboard=disabled and --mouse=disabled.\n" "It may only work over USB.\n" - "See --hid-keyboard and --hid-mouse.", + "See --keyboard and --mouse.", }, { .shortopt = 'p', @@ -1906,6 +1927,58 @@ parse_camera_fps(const char *s, uint16_t *camera_fps) { return true; } +static bool +parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) { + if (!strcmp(optarg, "disabled")) { + *mode = SC_KEYBOARD_INPUT_MODE_DISABLED; + return true; + } + + if (!strcmp(optarg, "sdk")) { + *mode = SC_KEYBOARD_INPUT_MODE_SDK; + return true; + } + + if (!strcmp(optarg, "aoa")) { +#ifdef HAVE_USB + *mode = SC_KEYBOARD_INPUT_MODE_AOA; + return true; +#else + LOGE("--keyboard=aoa is disabled."); + return false; +#endif + } + + LOGE("Unsupported keyboard: %s (expected disabled, sdk or aoa)", optarg); + return false; +} + +static bool +parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { + if (!strcmp(optarg, "disabled")) { + *mode = SC_MOUSE_INPUT_MODE_DISABLED; + return true; + } + + if (!strcmp(optarg, "sdk")) { + *mode = SC_MOUSE_INPUT_MODE_SDK; + return true; + } + + if (!strcmp(optarg, "aoa")) { +#ifdef HAVE_USB + *mode = SC_MOUSE_INPUT_MODE_AOA; + return true; +#else + LOGE("--mouse=aoa is disabled."); + return false; +#endif + } + + LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg); + return false; +} + static bool parse_time_limit(const char *s, sc_tick *tick) { long value; @@ -1995,12 +2068,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], break; case 'K': #ifdef HAVE_USB - opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; + LOGW("-K/--hid-keyboard is deprecated, use --keyboard=aoa " + "instead."); + opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AOA; break; #else LOGE("HID over AOA (-K/--hid-keyboard) is disabled."); return false; #endif + case OPT_KEYBOARD: + if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) { + return false; + } + break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; @@ -2013,12 +2093,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], break; case 'M': #ifdef HAVE_USB - opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; + LOGW("-M/--hid-mouse is deprecated, use --mouse=aoa instead."); + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA; break; #else LOGE("HID over AOA (-M/--hid-mouse) is disabled."); return false; #endif + case OPT_MOUSE: + if (!parse_mouse(optarg, &opts->mouse_input_mode)) { + return false; + } + break; case OPT_LOCK_VIDEO_ORIENTATION: if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { @@ -2465,6 +2551,37 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif + if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) { + opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA + : SC_KEYBOARD_INPUT_MODE_SDK; + } + if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) { + opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA + : SC_MOUSE_INPUT_MODE_SDK; + } + + if (otg) { + enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode; + if (kmode != SC_KEYBOARD_INPUT_MODE_AOA + && kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) { + LOGE("In OTG mode, --keyboard only supports aoa or disabled."); + return false; + } + + enum sc_mouse_input_mode mmode = opts->mouse_input_mode; + if (mmode != SC_MOUSE_INPUT_MODE_AOA + && mmode != SC_MOUSE_INPUT_MODE_DISABLED) { + LOGE("In OTG mode, --mouse only supports aoa or disabled."); + return false; + } + + if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED + && mmode == SC_MOUSE_INPUT_MODE_DISABLED) { + LOGE("Could not disable both keyboard and mouse in OTG mode."); + return false; + } + } + if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { LOGI("Tunnel host/port is set, " "--force-adb-forward automatically enabled."); @@ -2625,12 +2742,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } # ifdef _WIN32 - if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID - || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { + if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA + || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA)) { LOGE("On Windows, it is not possible to open a USB device already open " "by another process (like adb)."); - LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in " - "OTG mode (--otg)."); + LOGE("Therefore, --keyboard=aoa and --mouse=aoa may only work in OTG" + "mode (--otg)."); return false; } # endif diff --git a/app/src/options.c b/app/src/options.c index a13df585..7a885aa5 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -21,8 +21,8 @@ const struct scrcpy_options scrcpy_options_default = { .video_source = SC_VIDEO_SOURCE_DISPLAY, .audio_source = SC_AUDIO_SOURCE_AUTO, .record_format = SC_RECORD_FORMAT_AUTO, - .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, - .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, + .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, + .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO, .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, diff --git a/app/src/options.h b/app/src/options.h index 11e64fa1..1fb31c1a 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -140,13 +140,17 @@ enum sc_lock_video_orientation { }; enum sc_keyboard_input_mode { - SC_KEYBOARD_INPUT_MODE_INJECT, - SC_KEYBOARD_INPUT_MODE_HID, + SC_KEYBOARD_INPUT_MODE_AUTO, + SC_KEYBOARD_INPUT_MODE_DISABLED, + SC_KEYBOARD_INPUT_MODE_SDK, + SC_KEYBOARD_INPUT_MODE_AOA, }; enum sc_mouse_input_mode { - SC_MOUSE_INPUT_MODE_INJECT, - SC_MOUSE_INPUT_MODE_HID, + SC_MOUSE_INPUT_MODE_AUTO, + SC_MOUSE_INPUT_MODE_DISABLED, + SC_MOUSE_INPUT_MODE_SDK, + SC_MOUSE_INPUT_MODE_AOA, }; enum sc_key_inject_mode { diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index cf2e7e47..24177f15 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -543,11 +543,11 @@ scrcpy(struct scrcpy_options *options) { if (options->control) { #ifdef HAVE_USB - bool use_hid_keyboard = - options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; - bool use_hid_mouse = - options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; - if (use_hid_keyboard || use_hid_mouse) { + bool use_aoa_keyboard = + options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; + bool use_aoa_mouse = + options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; + if (use_aoa_keyboard || use_aoa_mouse) { bool ok = sc_acksync_init(&s->acksync); if (!ok) { goto end; @@ -590,7 +590,7 @@ scrcpy(struct scrcpy_options *options) { goto aoa_hid_end; } - if (use_hid_keyboard) { + if (use_aoa_keyboard) { if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { hid_keyboard_initialized = true; kp = &s->keyboard_hid.key_processor; @@ -599,7 +599,7 @@ scrcpy(struct scrcpy_options *options) { } } - if (use_hid_mouse) { + if (use_aoa_mouse) { if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) { hid_mouse_initialized = true; mp = &s->mouse_hid.mouse_processor; @@ -634,25 +634,23 @@ aoa_hid_end: } } - if (use_hid_keyboard && !hid_keyboard_initialized) { - LOGE("Fallback to default keyboard injection method " - "(-K/--hid-keyboard ignored)"); - options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; + if (use_aoa_keyboard && !hid_keyboard_initialized) { + LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); + options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK; } - if (use_hid_mouse && !hid_mouse_initialized) { - LOGE("Fallback to default mouse injection method " - "(-M/--hid-mouse ignored)"); - options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT; + if (use_aoa_mouse && !hid_mouse_initialized) { + LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); + options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; } } #else - assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID); - assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID); + assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA); + assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA); #endif // keyboard_input_mode may have been reset if HID mode failed - if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) { + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) { sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, options->key_inject_mode, options->forward_key_repeat); @@ -660,7 +658,7 @@ aoa_hid_end: } // mouse_input_mode may have been reset if HID mode failed - if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) { + if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { sc_mouse_inject_init(&s->mouse_inject, &s->controller); mp = &s->mouse_inject.mouse_processor; } diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index dfb0b9e9..5955e909 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -117,16 +117,15 @@ scrcpy_otg(struct scrcpy_options *options) { } aoa_initialized = true; + assert(options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA + || options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED); + assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA + || options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED); + bool enable_keyboard = - options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; + options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; bool enable_mouse = - options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; - - // If neither --hid-keyboard or --hid-mouse is passed, enable both - if (!enable_keyboard && !enable_mouse) { - enable_keyboard = true; - enable_mouse = true; - } + options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; if (enable_keyboard) { ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); From 48adae1728c6870a88a596ea092f98c76c7586b7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 19:39:35 +0100 Subject: [PATCH 1098/1133] Fix HID mouse documentation The size of a mouse HID event is 4 bytes. PR #4473 --- app/src/usb/hid_mouse.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index bab89940..06e2a224 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -10,7 +10,8 @@ #define HID_MOUSE_ACCESSORY_ID 2 -// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position +// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position, +// 1 byte for wheel motion #define HID_MOUSE_EVENT_SIZE 4 /** @@ -90,11 +91,12 @@ static const unsigned char mouse_report_desc[] = { }; /** - * A mouse HID event is 3 bytes long: + * A mouse HID event is 4 bytes long: * * - byte 0: buttons state * - byte 1: relative x motion (signed byte from -127 to 127) * - byte 2: relative y motion (signed byte from -127 to 127) + * - byte 3: wheel motion (-1, 0 or 1) * * 7 6 5 4 3 2 1 0 * +---------------+ @@ -112,7 +114,7 @@ static const unsigned char mouse_report_desc[] = { * +---------------+ * byte 2: |. . . . . . . .| relative y motion * +---------------+ - * byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1) + * byte 3: |. . . . . . . .| wheel motion * +---------------+ * * As an example, here is the report for a motion of (x=5, y=-4) with left From 29ce03e3370d6d79c042e02abaa6666c4930d0ee Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 19:45:21 +0100 Subject: [PATCH 1099/1133] Rename "buffer" to "data" The variable name is intended to match the parameter name of libusb_control_transfer(). PR #4473 --- app/src/usb/aoa_hid.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 9bad5296..eb47f415 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -97,10 +97,10 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, // index (arg1): total length of the HID report descriptor uint16_t value = accessory_id; uint16_t index = report_desc_size; - unsigned char *buffer = NULL; + unsigned char *data = NULL; uint16_t length = 0; int result = libusb_control_transfer(aoa->usb->handle, request_type, - request, value, index, buffer, length, + request, value, index, data, length, DEFAULT_TIMEOUT); if (result < 0) { LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result)); @@ -130,14 +130,14 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, * See */ // value (arg0): accessory assigned ID for the HID device - // index (arg1): offset of data (buffer) in descriptor + // index (arg1): offset of data in descriptor uint16_t value = accessory_id; uint16_t index = 0; // libusb_control_transfer expects a pointer to non-const - unsigned char *buffer = (unsigned char *) report_desc; + unsigned char *data = (unsigned char *) report_desc; uint16_t length = report_desc_size; int result = libusb_control_transfer(aoa->usb->handle, request_type, - request, value, index, buffer, length, + request, value, index, data, length, DEFAULT_TIMEOUT); if (result < 0) { LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result)); @@ -177,10 +177,10 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { // index (arg1): 0 (unused) uint16_t value = event->accessory_id; uint16_t index = 0; - unsigned char *buffer = event->buffer; + unsigned char *data = event->buffer; uint16_t length = event->size; int result = libusb_control_transfer(aoa->usb->handle, request_type, - request, value, index, buffer, length, + request, value, index, data, length, DEFAULT_TIMEOUT); if (result < 0) { LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result)); @@ -200,10 +200,10 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { // index (arg1): 0 uint16_t value = accessory_id; uint16_t index = 0; - unsigned char *buffer = NULL; + unsigned char *data = NULL; uint16_t length = 0; int result = libusb_control_transfer(aoa->usb->handle, request_type, - request, value, index, buffer, length, + request, value, index, data, length, DEFAULT_TIMEOUT); if (result < 0) { LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); From ae303b8d07bf84a96d97c7cead9e0a405a5e1482 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 20:01:34 +0100 Subject: [PATCH 1100/1133] Rename hid event "buffer" to "data" This fields contains the HID event data (there is no "bufferization"). PR #4473 --- app/src/usb/aoa_hid.c | 12 ++++++------ app/src/usb/aoa_hid.h | 4 ++-- app/src/usb/hid_keyboard.c | 24 ++++++++++++------------ app/src/usb/hid_mouse.c | 36 ++++++++++++++++++------------------ 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index eb47f415..1f2f7c79 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -27,7 +27,7 @@ sc_hid_event_log(const struct sc_hid_event *event) { return; } for (unsigned i = 0; i < event->size; ++i) { - snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]); + snprintf(buffer + i * 3, 4, " %02x", event->data[i]); } LOGV("HID Event: [%d]%s", event->accessory_id, buffer); free(buffer); @@ -35,16 +35,16 @@ sc_hid_event_log(const struct sc_hid_event *event) { void sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, - unsigned char *buffer, uint16_t buffer_size) { + unsigned char *data, uint16_t size) { hid_event->accessory_id = accessory_id; - hid_event->buffer = buffer; - hid_event->size = buffer_size; + hid_event->data = data; + hid_event->size = size; hid_event->ack_to_wait = SC_SEQUENCE_INVALID; } void sc_hid_event_destroy(struct sc_hid_event *hid_event) { - free(hid_event->buffer); + free(hid_event->data); } bool @@ -177,7 +177,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { // index (arg1): 0 (unused) uint16_t value = event->accessory_id; uint16_t index = 0; - unsigned char *data = event->buffer; + unsigned char *data = event->data; uint16_t length = event->size; int result = libusb_control_transfer(aoa->usb->handle, request_type, request, value, index, data, length, diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index fb5e1d28..a726938a 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -14,7 +14,7 @@ struct sc_hid_event { uint16_t accessory_id; - unsigned char *buffer; + unsigned char *data; uint16_t size; uint64_t ack_to_wait; }; @@ -22,7 +22,7 @@ struct sc_hid_event { // 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); + unsigned char *data, uint16_t size); void sc_hid_event_destroy(struct sc_hid_event *hid_event); diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index e717006a..8bd0866a 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -233,17 +233,17 @@ sdl_keymod_to_hid_modifiers(uint16_t mod) { static bool sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { - unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE); - if (!buffer) { + unsigned char *data = malloc(HID_KEYBOARD_EVENT_SIZE); + if (!data) { LOG_OOM(); return false; } - buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; - buffer[1] = HID_RESERVED; - memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); + data[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; + data[1] = HID_RESERVED; + memset(&data[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); - sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer, + sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, data, HID_KEYBOARD_EVENT_SIZE); return true; } @@ -282,9 +282,9 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, kb->keys[scancode] ? "true" : "false"); } - hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers; + hid_event->data[HID_KEYBOARD_INDEX_MODIFIER] = modifiers; - unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS]; + unsigned char *keys_data = &hid_event->data[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) { @@ -296,11 +296,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, // - Modifiers // - Reserved // - ErrorRollOver * HID_MAX_KEYS - memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS); + memset(keys_data, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS); goto end; } - keys_buffer[keys_pressed_count] = i; + keys_data[keys_pressed_count] = i; ++keys_pressed_count; } } @@ -331,11 +331,11 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { unsigned i = 0; if (capslock) { - hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; + hid_event.data[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; ++i; } if (numlock) { - hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK; + hid_event.data[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK; ++i; } diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index 06e2a224..45ae6441 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -133,13 +133,13 @@ static const unsigned char mouse_report_desc[] = { static bool sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { - unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE); - if (!buffer) { + unsigned char *data = calloc(1, HID_MOUSE_EVENT_SIZE); + if (!data) { LOG_OOM(); return false; } - sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer, + sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, data, HID_MOUSE_EVENT_SIZE); return true; } @@ -175,11 +175,11 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, return; } - unsigned char *buffer = hid_event.buffer; - buffer[0] = buttons_state_to_hid_buttons(event->buttons_state); - buffer[1] = CLAMP(event->xrel, -127, 127); - buffer[2] = CLAMP(event->yrel, -127, 127); - buffer[3] = 0; // wheel coordinates only used for scrolling + unsigned char *data = hid_event.data; + data[0] = buttons_state_to_hid_buttons(event->buttons_state); + data[1] = CLAMP(event->xrel, -127, 127); + data[2] = CLAMP(event->yrel, -127, 127); + data[3] = 0; // wheel coordinates only used for scrolling if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); @@ -197,11 +197,11 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, return; } - unsigned char *buffer = hid_event.buffer; - buffer[0] = buttons_state_to_hid_buttons(event->buttons_state); - buffer[1] = 0; // no x motion - buffer[2] = 0; // no y motion - buffer[3] = 0; // wheel coordinates only used for scrolling + unsigned char *data = hid_event.data; + data[0] = buttons_state_to_hid_buttons(event->buttons_state); + data[1] = 0; // no x motion + data[2] = 0; // no y motion + data[3] = 0; // wheel coordinates only used for scrolling if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { sc_hid_event_destroy(&hid_event); @@ -219,13 +219,13 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, return; } - unsigned char *buffer = hid_event.buffer; - buffer[0] = 0; // buttons state irrelevant (and unknown) - buffer[1] = 0; // no x motion - buffer[2] = 0; // no y motion + unsigned char *data = hid_event.data; + data[0] = 0; // buttons state irrelevant (and unknown) + data[1] = 0; // no x motion + data[2] = 0; // no y motion // In practice, vscroll is always -1, 0 or 1, but in theory other values // are possible - buffer[3] = CLAMP(event->vscroll, -127, 127); + data[3] = CLAMP(event->vscroll, -127, 127); // Horizontal scrolling ignored if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { From 2d32557fdea3e83dd8ec2a73e122338cbe5d417b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 20:17:37 +0100 Subject: [PATCH 1101/1133] Embed HID event data In the implementation, an HID event is at most 8 bytes. Embed the data in the HID event structure to avoid allocations and simplify the code. PR #4473 --- app/src/usb/aoa_hid.c | 26 ++------------------------ app/src/usb/aoa_hid.h | 14 ++++---------- app/src/usb/hid_keyboard.c | 28 ++++++++-------------------- app/src/usb/hid_mouse.c | 31 +++++++++---------------------- 4 files changed, 23 insertions(+), 76 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 1f2f7c79..5db7ab94 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -33,20 +33,6 @@ sc_hid_event_log(const struct sc_hid_event *event) { free(buffer); } -void -sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, - unsigned char *data, uint16_t size) { - hid_event->accessory_id = accessory_id; - hid_event->data = data; - hid_event->size = size; - hid_event->ack_to_wait = SC_SEQUENCE_INVALID; -} - -void -sc_hid_event_destroy(struct sc_hid_event *hid_event) { - free(hid_event->data); -} - bool sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { @@ -76,12 +62,7 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, void sc_aoa_destroy(struct sc_aoa *aoa) { - // Destroy remaining events - while (!sc_vecdeque_is_empty(&aoa->queue)) { - struct sc_hid_event *event = sc_vecdeque_popref(&aoa->queue); - assert(event); - sc_hid_event_destroy(event); - } + sc_vecdeque_destroy(&aoa->queue); sc_cond_destroy(&aoa->event_cond); sc_mutex_destroy(&aoa->mutex); @@ -177,7 +158,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { // index (arg1): 0 (unused) uint16_t value = event->accessory_id; uint16_t index = 0; - unsigned char *data = event->data; + unsigned char *data = (uint8_t *) event->data; // discard const uint16_t length = event->size; int result = libusb_control_transfer(aoa->usb->handle, request_type, request, value, index, data, length, @@ -271,17 +252,14 @@ run_aoa_thread(void *data) { if (result == SC_ACKSYNC_WAIT_TIMEOUT) { LOGW("Ack not received after 500ms, discarding HID event"); - sc_hid_event_destroy(&event); continue; } else if (result == SC_ACKSYNC_WAIT_INTR) { // stopped - sc_hid_event_destroy(&event); break; } } bool ok = sc_aoa_send_hid_event(aoa, &event); - sc_hid_event_destroy(&event); if (!ok) { LOGW("Could not send HID event to USB device"); } diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index a726938a..2cbd1a23 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -12,21 +12,15 @@ #include "util/tick.h" #include "util/vecdeque.h" +#define SC_HID_MAX_SIZE 8 + struct sc_hid_event { uint16_t accessory_id; - unsigned char *data; - uint16_t size; + uint8_t data[SC_HID_MAX_SIZE]; + uint8_t size; uint64_t ack_to_wait; }; -// Takes ownership of buffer -void -sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, - unsigned char *data, uint16_t size); - -void -sc_hid_event_destroy(struct sc_hid_event *hid_event); - struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event); struct sc_aoa { diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index 8bd0866a..dcf56313 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -231,21 +231,17 @@ sdl_keymod_to_hid_modifiers(uint16_t mod) { return modifiers; } -static bool +static void sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { - unsigned char *data = malloc(HID_KEYBOARD_EVENT_SIZE); - if (!data) { - LOG_OOM(); - return false; - } + hid_event->accessory_id = HID_KEYBOARD_ACCESSORY_ID; + hid_event->size = HID_KEYBOARD_EVENT_SIZE; + hid_event->ack_to_wait = SC_SEQUENCE_INVALID; + + uint8_t *data = hid_event->data; data[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; data[1] = HID_RESERVED; memset(&data[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); - - sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, data, - HID_KEYBOARD_EVENT_SIZE); - return true; } static inline bool @@ -268,10 +264,7 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, return false; } - if (!sc_hid_keyboard_event_init(hid_event)) { - LOGW("Could not initialize HID keyboard event"); - return false; - } + sc_hid_keyboard_event_init(hid_event); unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state); @@ -324,10 +317,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { } struct sc_hid_event hid_event; - if (!sc_hid_keyboard_event_init(&hid_event)) { - LOGW("Could not initialize HID keyboard event"); - return false; - } + sc_hid_keyboard_event_init(&hid_event); unsigned i = 0; if (capslock) { @@ -340,7 +330,6 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { } if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { - sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mod lock state)"); return false; } @@ -382,7 +371,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { - sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (key)"); } } diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index 45ae6441..a47534c1 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -131,17 +131,13 @@ static const unsigned char mouse_report_desc[] = { * +---------------+ */ -static bool +static void sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { - unsigned char *data = calloc(1, HID_MOUSE_EVENT_SIZE); - if (!data) { - LOG_OOM(); - return false; - } - - sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, data, - HID_MOUSE_EVENT_SIZE); - return true; + hid_event->accessory_id = HID_MOUSE_ACCESSORY_ID; + hid_event->size = HID_MOUSE_EVENT_SIZE; + hid_event->ack_to_wait = SC_SEQUENCE_INVALID; + // Leave hid_event->data uninitialized, it will be fully initialized by + // callers } static unsigned char @@ -171,9 +167,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, struct sc_hid_mouse *mouse = DOWNCAST(mp); struct sc_hid_event hid_event; - if (!sc_hid_mouse_event_init(&hid_event)) { - return; - } + sc_hid_mouse_event_init(&hid_event); unsigned char *data = hid_event.data; data[0] = buttons_state_to_hid_buttons(event->buttons_state); @@ -182,7 +176,6 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, data[3] = 0; // wheel coordinates only used for scrolling if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { - sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mouse motion)"); } } @@ -193,9 +186,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, struct sc_hid_mouse *mouse = DOWNCAST(mp); struct sc_hid_event hid_event; - if (!sc_hid_mouse_event_init(&hid_event)) { - return; - } + sc_hid_mouse_event_init(&hid_event); unsigned char *data = hid_event.data; data[0] = buttons_state_to_hid_buttons(event->buttons_state); @@ -204,7 +195,6 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, data[3] = 0; // wheel coordinates only used for scrolling if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { - sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mouse click)"); } } @@ -215,9 +205,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, struct sc_hid_mouse *mouse = DOWNCAST(mp); struct sc_hid_event hid_event; - if (!sc_hid_mouse_event_init(&hid_event)) { - return; - } + sc_hid_mouse_event_init(&hid_event); unsigned char *data = hid_event.data; data[0] = 0; // buttons state irrelevant (and unknown) @@ -229,7 +217,6 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, // Horizontal scrolling ignored if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { - sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mouse scroll)"); } } From f2d62031561b4d1660c2c5bb3910e7a723d317f7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 20:32:37 +0100 Subject: [PATCH 1102/1133] Extract HID events struct An event contained several fields: - the accessory id - the HID event data - a field ack_to_wait specific to the AOA implementation. Extract the HID event part to prepare the factorization of HID event creation. PR #4473 --- app/src/hid/hid_event.h | 15 +++++++++++++++ app/src/usb/aoa_hid.c | 34 ++++++++++++++++++++++------------ app/src/usb/aoa_hid.h | 22 ++++++++++++++++------ app/src/usb/hid_keyboard.c | 21 ++++++++++----------- app/src/usb/hid_mouse.c | 11 ++++++----- 5 files changed, 69 insertions(+), 34 deletions(-) create mode 100644 app/src/hid/hid_event.h diff --git a/app/src/hid/hid_event.h b/app/src/hid/hid_event.h new file mode 100644 index 00000000..e17f8569 --- /dev/null +++ b/app/src/hid/hid_event.h @@ -0,0 +1,15 @@ +#ifndef SC_HID_EVENT_H +#define SC_HID_EVENT_H + +#include "common.h" + +#include + +#define SC_HID_MAX_SIZE 8 + +struct sc_hid_event { + uint8_t data[SC_HID_MAX_SIZE]; + uint8_t size; +}; + +#endif diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 5db7ab94..d6b418a0 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -14,10 +14,10 @@ #define DEFAULT_TIMEOUT 1000 -#define SC_HID_EVENT_QUEUE_MAX 64 +#define SC_AOA_EVENT_QUEUE_MAX 64 static void -sc_hid_event_log(const struct sc_hid_event *event) { +sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) { // HID Event: [00] FF FF FF FF... assert(event->size); unsigned buffer_size = event->size * 3 + 1; @@ -29,7 +29,7 @@ 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->data[i]); } - LOGV("HID Event: [%d]%s", event->accessory_id, buffer); + LOGV("HID Event: [%d]%s", accessory_id, buffer); free(buffer); } @@ -38,7 +38,7 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync) { sc_vecdeque_init(&aoa->queue); - if (!sc_vecdeque_reserve(&aoa->queue, SC_HID_EVENT_QUEUE_MAX)) { + if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) { return false; } @@ -150,13 +150,14 @@ 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) { +sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id, + const struct sc_hid_event *event) { uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request = ACCESSORY_SEND_HID_EVENT; // // value (arg0): accessory assigned ID for the HID device // index (arg1): 0 (unused) - uint16_t value = event->accessory_id; + uint16_t value = accessory_id; uint16_t index = 0; unsigned char *data = (uint8_t *) event->data; // discard const uint16_t length = event->size; @@ -173,7 +174,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) { +sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) { uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request = ACCESSORY_UNREGISTER_HID; // @@ -196,16 +197,25 @@ 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) { +sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, + uint16_t accessory_id, + const struct sc_hid_event *event, + uint64_t ack_to_wait) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { - sc_hid_event_log(event); + sc_hid_event_log(accessory_id, event); } sc_mutex_lock(&aoa->mutex); bool full = sc_vecdeque_is_full(&aoa->queue); if (!full) { bool was_empty = sc_vecdeque_is_empty(&aoa->queue); - sc_vecdeque_push_noresize(&aoa->queue, *event); + + struct sc_aoa_event *aoa_event = + sc_vecdeque_push_hole_noresize(&aoa->queue); + aoa_event->hid = *event; + aoa_event->accessory_id = accessory_id; + aoa_event->ack_to_wait = ack_to_wait; + if (was_empty) { sc_cond_signal(&aoa->event_cond); } @@ -233,7 +243,7 @@ run_aoa_thread(void *data) { } assert(!sc_vecdeque_is_empty(&aoa->queue)); - struct sc_hid_event event = sc_vecdeque_pop(&aoa->queue); + struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue); uint64_t ack_to_wait = event.ack_to_wait; sc_mutex_unlock(&aoa->mutex); @@ -259,7 +269,7 @@ run_aoa_thread(void *data) { } } - bool ok = sc_aoa_send_hid_event(aoa, &event); + bool ok = sc_aoa_send_hid_event(aoa, event.accessory_id, &event.hid); if (!ok) { LOGW("Could not send HID event to USB device"); } diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 2cbd1a23..33a1f136 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -6,6 +6,7 @@ #include +#include "hid/hid_event.h" #include "usb.h" #include "util/acksync.h" #include "util/thread.h" @@ -14,14 +15,13 @@ #define SC_HID_MAX_SIZE 8 -struct sc_hid_event { +struct sc_aoa_event { + struct sc_hid_event hid; uint16_t accessory_id; - uint8_t data[SC_HID_MAX_SIZE]; - uint8_t size; uint64_t ack_to_wait; }; -struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event); +struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event); struct sc_aoa { struct sc_usb *usb; @@ -29,7 +29,7 @@ struct sc_aoa { sc_mutex mutex; sc_cond event_cond; bool stopped; - struct sc_hid_event_queue queue; + struct sc_aoa_event_queue queue; struct sc_acksync *acksync; }; @@ -57,6 +57,16 @@ bool sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); bool -sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event); +sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, + uint16_t accessory_id, + const struct sc_hid_event *event, + uint64_t ack_to_wait); + +static inline bool +sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id, + const struct sc_hid_event *event) { + return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event, + SC_SEQUENCE_INVALID); +} #endif diff --git a/app/src/usb/hid_keyboard.c b/app/src/usb/hid_keyboard.c index dcf56313..9b87a27a 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/usb/hid_keyboard.c @@ -233,9 +233,7 @@ sdl_keymod_to_hid_modifiers(uint16_t mod) { static void sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { - hid_event->accessory_id = HID_KEYBOARD_ACCESSORY_ID; hid_event->size = HID_KEYBOARD_EVENT_SIZE; - hid_event->ack_to_wait = SC_SEQUENCE_INVALID; uint8_t *data = hid_event->data; @@ -329,7 +327,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { ++i; } - if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { + if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID, + &hid_event)) { LOGW("Could not request HID event (mod lock state)"); return false; } @@ -362,15 +361,15 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } } - if (ack_to_wait) { - // Ctrl+v is pressed, so clipboard synchronization has been - // requested. Wait until clipboard synchronization is acknowledged - // by the server, otherwise it could paste the old clipboard - // content. - hid_event.ack_to_wait = ack_to_wait; - } + // If ack_to_wait is != SC_SEQUENCE_INVALID, then Ctrl+v is pressed, so + // clipboard synchronization has been requested. Wait until clipboard + // synchronization is acknowledged by the server, otherwise it could + // paste the old clipboard content. - if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { + if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa, + HID_KEYBOARD_ACCESSORY_ID, + &hid_event, + ack_to_wait)) { LOGW("Could not request HID event (key)"); } } diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index a47534c1..de961265 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -133,9 +133,7 @@ static const unsigned char mouse_report_desc[] = { static void sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { - hid_event->accessory_id = HID_MOUSE_ACCESSORY_ID; hid_event->size = HID_MOUSE_EVENT_SIZE; - hid_event->ack_to_wait = SC_SEQUENCE_INVALID; // Leave hid_event->data uninitialized, it will be fully initialized by // callers } @@ -175,7 +173,8 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, data[2] = CLAMP(event->yrel, -127, 127); data[3] = 0; // wheel coordinates only used for scrolling - if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { LOGW("Could not request HID event (mouse motion)"); } } @@ -194,7 +193,8 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, data[2] = 0; // no y motion data[3] = 0; // wheel coordinates only used for scrolling - if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { LOGW("Could not request HID event (mouse click)"); } } @@ -216,7 +216,8 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, data[3] = CLAMP(event->vscroll, -127, 127); // Horizontal scrolling ignored - if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { LOGW("Could not request HID event (mouse scroll)"); } } From 91485e2863603732348d86a4ed06cafa20d1225f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 22:57:02 +0100 Subject: [PATCH 1103/1133] Extract keyboard HID handling Split the keyboard implementation using AOA and the code handling HID events, so that HID events can be reused for another protocol (UHID). PR #4473 --- app/meson.build | 3 +- app/src/{usb => hid}/hid_keyboard.c | 225 +++++++++------------------- app/src/{usb => hid}/hid_keyboard.h | 24 +-- app/src/scrcpy.c | 32 ++-- app/src/usb/keyboard_aoa.c | 109 ++++++++++++++ app/src/usb/keyboard_aoa.h | 27 ++++ app/src/usb/scrcpy_otg.c | 8 +- app/src/usb/screen_otg.h | 6 +- 8 files changed, 245 insertions(+), 189 deletions(-) rename app/src/{usb => hid}/hid_keyboard.c (56%) rename app/src/{usb => hid}/hid_keyboard.h (68%) create mode 100644 app/src/usb/keyboard_aoa.c create mode 100644 app/src/usb/keyboard_aoa.h diff --git a/app/meson.build b/app/meson.build index caf5ee5c..6d572b7b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -31,6 +31,7 @@ src = [ 'src/screen.c', 'src/server.c', 'src/version.c', + 'src/hid/hid_keyboard.c', 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', @@ -88,7 +89,7 @@ usb_support = get_option('usb') if usb_support src += [ 'src/usb/aoa_hid.c', - 'src/usb/hid_keyboard.c', + 'src/usb/keyboard_aoa.c', 'src/usb/hid_mouse.c', 'src/usb/scrcpy_otg.c', 'src/usb/screen_otg.c', diff --git a/app/src/usb/hid_keyboard.c b/app/src/hid/hid_keyboard.c similarity index 56% rename from app/src/usb/hid_keyboard.c rename to app/src/hid/hid_keyboard.c index 9b87a27a..f3001df4 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/hid/hid_keyboard.c @@ -1,40 +1,34 @@ #include "hid_keyboard.h" -#include +#include -#include "input_events.h" #include "util/log.h" -/** Downcast key processor to hid_keyboard */ -#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor) +#define SC_HID_MOD_NONE 0x00 +#define SC_HID_MOD_LEFT_CONTROL (1 << 0) +#define SC_HID_MOD_LEFT_SHIFT (1 << 1) +#define SC_HID_MOD_LEFT_ALT (1 << 2) +#define SC_HID_MOD_LEFT_GUI (1 << 3) +#define SC_HID_MOD_RIGHT_CONTROL (1 << 4) +#define SC_HID_MOD_RIGHT_SHIFT (1 << 5) +#define SC_HID_MOD_RIGHT_ALT (1 << 6) +#define SC_HID_MOD_RIGHT_GUI (1 << 7) -#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_INDEX_MODIFIER 0 -#define HID_KEYBOARD_INDEX_KEYS 2 +#define SC_HID_KEYBOARD_INDEX_MODS 0 +#define SC_HID_KEYBOARD_INDEX_KEYS 2 // USB HID protocol says 6 keys in an event is the requirement for BIOS // keyboard support, though OS could support more keys via modifying the report // desc. 6 should be enough for scrcpy. -#define HID_KEYBOARD_MAX_KEYS 6 -#define HID_KEYBOARD_EVENT_SIZE \ - (HID_KEYBOARD_INDEX_KEYS + HID_KEYBOARD_MAX_KEYS) +#define SC_HID_KEYBOARD_MAX_KEYS 6 +#define SC_HID_KEYBOARD_EVENT_SIZE \ + (SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS) -#define HID_RESERVED 0x00 -#define HID_ERROR_ROLL_OVER 0x01 +#define SC_HID_RESERVED 0x00 +#define SC_HID_ERROR_ROLL_OVER 0x01 /** - * For HID over AOAv2, only report descriptor is needed. + * For HID, only report descriptor is needed. * * The specification is available here: * @@ -53,7 +47,7 @@ * * (change vid:pid' to your device's vendor ID and product ID). */ -static const unsigned char keyboard_report_desc[] = { +const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = { // Usage Page (Generic Desktop) 0x05, 0x01, // Usage (Keyboard) @@ -119,7 +113,7 @@ static const unsigned char keyboard_report_desc[] = { // Report Size (8) 0x75, 0x08, // Report Count (6) - 0x95, HID_KEYBOARD_MAX_KEYS, + 0x95, SC_HID_KEYBOARD_MAX_KEYS, // Input (Data, Array): Keys 0x81, 0x00, @@ -127,6 +121,9 @@ static const unsigned char keyboard_report_desc[] = { 0xC0 }; +const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN = + sizeof(SC_HID_KEYBOARD_REPORT_DESC); + /** * A keyboard HID event is 8 bytes long: * @@ -201,45 +198,50 @@ static const unsigned char keyboard_report_desc[] = { * +---------------+ */ -static unsigned char -sdl_keymod_to_hid_modifiers(uint16_t mod) { - unsigned char modifiers = HID_MODIFIER_NONE; +static void +sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { + hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE; + + uint8_t *data = hid_event->data; + + data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE; + data[1] = SC_HID_RESERVED; + memset(&data[SC_HID_KEYBOARD_INDEX_KEYS], 0, SC_HID_KEYBOARD_MAX_KEYS); +} + +static uint16_t +sc_hid_mod_from_sdl_keymod(uint16_t mod) { + uint16_t mods = SC_HID_MOD_NONE; if (mod & SC_MOD_LCTRL) { - modifiers |= HID_MODIFIER_LEFT_CONTROL; + mods |= SC_HID_MOD_LEFT_CONTROL; } if (mod & SC_MOD_LSHIFT) { - modifiers |= HID_MODIFIER_LEFT_SHIFT; + mods |= SC_HID_MOD_LEFT_SHIFT; } if (mod & SC_MOD_LALT) { - modifiers |= HID_MODIFIER_LEFT_ALT; + mods |= SC_HID_MOD_LEFT_ALT; } if (mod & SC_MOD_LGUI) { - modifiers |= HID_MODIFIER_LEFT_GUI; + mods |= SC_HID_MOD_LEFT_GUI; } if (mod & SC_MOD_RCTRL) { - modifiers |= HID_MODIFIER_RIGHT_CONTROL; + mods |= SC_HID_MOD_RIGHT_CONTROL; } if (mod & SC_MOD_RSHIFT) { - modifiers |= HID_MODIFIER_RIGHT_SHIFT; + mods |= SC_HID_MOD_RIGHT_SHIFT; } if (mod & SC_MOD_RALT) { - modifiers |= HID_MODIFIER_RIGHT_ALT; + mods |= SC_HID_MOD_RIGHT_ALT; } if (mod & SC_MOD_RGUI) { - modifiers |= HID_MODIFIER_RIGHT_GUI; + mods |= SC_HID_MOD_RIGHT_GUI; } - return modifiers; + return mods; } -static void -sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { - hid_event->size = HID_KEYBOARD_EVENT_SIZE; - - uint8_t *data = hid_event->data; - - data[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; - data[1] = HID_RESERVED; - memset(&data[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); +void +sc_hid_keyboard_init(struct sc_hid_keyboard *hid) { + memset(hid->keys, false, SC_HID_KEYBOARD_KEYS); } static inline bool @@ -247,10 +249,10 @@ scancode_is_modifier(enum sc_scancode scancode) { return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI; } -static bool -convert_hid_keyboard_event(struct sc_hid_keyboard *kb, - struct sc_hid_event *hid_event, - const struct sc_key_event *event) { +bool +sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid, + struct sc_hid_event *hid_event, + const struct sc_key_event *event) { enum sc_scancode scancode = event->scancode; assert(scancode >= 0); @@ -264,30 +266,31 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, sc_hid_keyboard_event_init(hid_event); - unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state); + uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state); if (scancode < SC_HID_KEYBOARD_KEYS) { // Pressed is true and released is false - kb->keys[scancode] = (event->action == SC_ACTION_DOWN); + hid->keys[scancode] = (event->action == SC_ACTION_DOWN); LOGV("keys[%02x] = %s", scancode, - kb->keys[scancode] ? "true" : "false"); + hid->keys[scancode] ? "true" : "false"); } - hid_event->data[HID_KEYBOARD_INDEX_MODIFIER] = modifiers; + hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods; - unsigned char *keys_data = &hid_event->data[HID_KEYBOARD_INDEX_KEYS]; + uint8_t *keys_data = &hid_event->data[SC_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]) { + if (hid->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) { + if (keys_pressed_count >= SC_HID_KEYBOARD_MAX_KEYS) { // Phantom state: // - Modifiers // - Reserved // - ErrorRollOver * HID_MAX_KEYS - memset(keys_data, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS); + memset(keys_data, SC_HID_ERROR_ROLL_OVER, + SC_HID_KEYBOARD_MAX_KEYS); goto end; } @@ -299,120 +302,32 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb, end: LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x", event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode, - event->scancode, modifiers); + event->scancode, mods); return true; } - -static bool -push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { +bool +sc_hid_keyboard_event_from_mods(struct sc_hid_event *event, + uint16_t mods_state) { bool capslock = mods_state & SC_MOD_CAPS; bool numlock = mods_state & SC_MOD_NUM; if (!capslock && !numlock) { // Nothing to do - return true; + return false; } - struct sc_hid_event hid_event; - sc_hid_keyboard_event_init(&hid_event); + sc_hid_keyboard_event_init(event); unsigned i = 0; if (capslock) { - hid_event.data[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; + event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; ++i; } if (numlock) { - hid_event.data[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK; + event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK; ++i; } - if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID, - &hid_event)) { - LOGW("Could not request HID event (mod lock state)"); - return false; - } - - LOGD("HID keyboard state synchronized"); - return true; } - -static void -sc_key_processor_process_key(struct sc_key_processor *kp, - const struct sc_key_event *event, - uint64_t ack_to_wait) { - if (event->repeat) { - // In USB HID protocol, key repeat is handled by the host (Android), so - // just ignore key repeat here. - return; - } - - struct sc_hid_keyboard *kb = DOWNCAST(kp); - - struct sc_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->mods_state)) { - kb->mod_lock_synchronized = true; - } - } - - // If ack_to_wait is != SC_SEQUENCE_INVALID, then Ctrl+v is pressed, so - // clipboard synchronization has been requested. Wait until clipboard - // synchronization is acknowledged by the server, otherwise it could - // paste the old clipboard content. - - if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa, - HID_KEYBOARD_ACCESSORY_ID, - &hid_event, - ack_to_wait)) { - LOGW("Could not request HID event (key)"); - } - } -} - -bool -sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { - kb->aoa = aoa; - - bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID, - keyboard_report_desc, - ARRAY_LEN(keyboard_report_desc)); - if (!ok) { - LOGW("Register HID keyboard failed"); - return false; - } - - // Reset all states - memset(kb->keys, false, SC_HID_KEYBOARD_KEYS); - - kb->mod_lock_synchronized = false; - - static const struct sc_key_processor_ops ops = { - .process_key = sc_key_processor_process_key, - // Never forward text input via HID (all the keys are injected - // separately) - .process_text = NULL, - }; - - // Clipboard synchronization is requested over the control socket, while HID - // events are sent over AOA, so it must wait for clipboard synchronization - // to be acknowledged by the device before injecting Ctrl+v. - kb->key_processor.async_paste = true; - kb->key_processor.ops = &ops; - - return true; -} - -void -sc_hid_keyboard_destroy(struct sc_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); - if (!ok) { - LOGW("Could not unregister HID keyboard"); - } -} diff --git a/app/src/usb/hid_keyboard.h b/app/src/hid/hid_keyboard.h similarity index 68% rename from app/src/usb/hid_keyboard.h rename to app/src/hid/hid_keyboard.h index 7173a898..ddd2cc91 100644 --- a/app/src/usb/hid_keyboard.h +++ b/app/src/hid/hid_keyboard.h @@ -5,8 +5,8 @@ #include -#include "aoa_hid.h" -#include "trait/key_processor.h" +#include "hid/hid_event.h" +#include "input_events.h" // See "SDL2/SDL_scancode.h". // Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB @@ -14,6 +14,9 @@ // 0x65 is Application, typically AT-101 Keyboard ends here. #define SC_HID_KEYBOARD_KEYS 0x66 +extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[]; +extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN; + /** * HID keyboard events are sequence-based, every time keyboard state changes * it sends an array of currently pressed keys, the host is responsible for @@ -27,18 +30,19 @@ * phantom state. */ struct sc_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; }; +void +sc_hid_keyboard_init(struct sc_hid_keyboard *hid); + bool -sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa); +sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid, + struct sc_hid_event *hid_event, + const struct sc_key_event *event); -void -sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb); +bool +sc_hid_keyboard_event_from_mods(struct sc_hid_event *event, + uint16_t mods_state); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 24177f15..1d5e67dc 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -27,7 +27,7 @@ #include "server.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" -# include "usb/hid_keyboard.h" +# include "usb/keyboard_aoa.h" # include "usb/hid_mouse.h" # include "usb/usb.h" #endif @@ -65,7 +65,7 @@ struct scrcpy { union { struct sc_keyboard_inject keyboard_inject; #ifdef HAVE_USB - struct sc_hid_keyboard keyboard_hid; + struct sc_keyboard_aoa keyboard_aoa; #endif }; union { @@ -330,7 +330,7 @@ scrcpy(struct scrcpy_options *options) { bool audio_demuxer_started = false; #ifdef HAVE_USB bool aoa_hid_initialized = false; - bool hid_keyboard_initialized = false; + bool keyboard_aoa_initialized = false; bool hid_mouse_initialized = false; #endif bool controller_initialized = false; @@ -543,11 +543,11 @@ scrcpy(struct scrcpy_options *options) { if (options->control) { #ifdef HAVE_USB - bool use_aoa_keyboard = + bool use_keyboard_aoa = options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; bool use_aoa_mouse = options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; - if (use_aoa_keyboard || use_aoa_mouse) { + if (use_keyboard_aoa || use_aoa_mouse) { bool ok = sc_acksync_init(&s->acksync); if (!ok) { goto end; @@ -590,10 +590,10 @@ scrcpy(struct scrcpy_options *options) { goto aoa_hid_end; } - if (use_aoa_keyboard) { - if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { - hid_keyboard_initialized = true; - kp = &s->keyboard_hid.key_processor; + if (use_keyboard_aoa) { + if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) { + keyboard_aoa_initialized = true; + kp = &s->keyboard_aoa.key_processor; } else { LOGE("Could not initialize HID keyboard"); } @@ -608,7 +608,7 @@ scrcpy(struct scrcpy_options *options) { } } - bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized; + bool need_aoa = keyboard_aoa_initialized || hid_mouse_initialized; if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); @@ -624,9 +624,9 @@ scrcpy(struct scrcpy_options *options) { aoa_hid_end: if (!aoa_hid_initialized) { - if (hid_keyboard_initialized) { - sc_hid_keyboard_destroy(&s->keyboard_hid); - hid_keyboard_initialized = false; + if (keyboard_aoa_initialized) { + sc_keyboard_aoa_destroy(&s->keyboard_aoa); + keyboard_aoa_initialized = false; } if (hid_mouse_initialized) { sc_hid_mouse_destroy(&s->mouse_hid); @@ -634,7 +634,7 @@ aoa_hid_end: } } - if (use_aoa_keyboard && !hid_keyboard_initialized) { + if (use_keyboard_aoa && !keyboard_aoa_initialized) { LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK; } @@ -813,8 +813,8 @@ end: // end-of-stream #ifdef HAVE_USB if (aoa_hid_initialized) { - if (hid_keyboard_initialized) { - sc_hid_keyboard_destroy(&s->keyboard_hid); + if (keyboard_aoa_initialized) { + sc_keyboard_aoa_destroy(&s->keyboard_aoa); } if (hid_mouse_initialized) { sc_hid_mouse_destroy(&s->mouse_hid); diff --git a/app/src/usb/keyboard_aoa.c b/app/src/usb/keyboard_aoa.c new file mode 100644 index 00000000..b69d6cd8 --- /dev/null +++ b/app/src/usb/keyboard_aoa.c @@ -0,0 +1,109 @@ +#include "keyboard_aoa.h" + +#include + +#include "input_events.h" +#include "util/log.h" + +/** Downcast key processor to keyboard_aoa */ +#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor) + +#define HID_KEYBOARD_ACCESSORY_ID 1 + +static bool +push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) { + struct sc_hid_event hid_event; + if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) { + // Nothing to do + return true; + } + + if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID, + &hid_event)) { + LOGW("Could not request HID event (mod lock state)"); + return false; + } + + LOGD("HID keyboard state synchronized"); + + return true; +} + +static void +sc_key_processor_process_key(struct sc_key_processor *kp, + const struct sc_key_event *event, + uint64_t ack_to_wait) { + if (event->repeat) { + // In USB HID protocol, key repeat is handled by the host (Android), so + // just ignore key repeat here. + return; + } + + struct sc_keyboard_aoa *kb = DOWNCAST(kp); + + struct sc_hid_event hid_event; + + // Not all keys are supported, just ignore unsupported keys + if (sc_hid_keyboard_event_from_key(&kb->hid, &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->mods_state)) { + kb->mod_lock_synchronized = true; + } + } + + // If ack_to_wait is != SC_SEQUENCE_INVALID, then Ctrl+v is pressed, so + // clipboard synchronization has been requested. Wait until clipboard + // synchronization is acknowledged by the server, otherwise it could + // paste the old clipboard content. + + if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa, + HID_KEYBOARD_ACCESSORY_ID, + &hid_event, + ack_to_wait)) { + LOGW("Could not request HID event (key)"); + } + } +} + +bool +sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) { + kb->aoa = aoa; + + bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID, + SC_HID_KEYBOARD_REPORT_DESC, + SC_HID_KEYBOARD_REPORT_DESC_LEN); + if (!ok) { + LOGW("Register HID keyboard failed"); + return false; + } + + sc_hid_keyboard_init(&kb->hid); + + kb->mod_lock_synchronized = false; + + static const struct sc_key_processor_ops ops = { + .process_key = sc_key_processor_process_key, + // Never forward text input via HID (all the keys are injected + // separately) + .process_text = NULL, + }; + + // Clipboard synchronization is requested over the control socket, while HID + // events are sent over AOA, so it must wait for clipboard synchronization + // to be acknowledged by the device before injecting Ctrl+v. + kb->key_processor.async_paste = true; + kb->key_processor.ops = &ops; + + return true; +} + +void +sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) { + // Unregister HID keyboard so the soft keyboard shows again on Android + bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID); + if (!ok) { + LOGW("Could not unregister HID keyboard"); + } +} diff --git a/app/src/usb/keyboard_aoa.h b/app/src/usb/keyboard_aoa.h new file mode 100644 index 00000000..565b9177 --- /dev/null +++ b/app/src/usb/keyboard_aoa.h @@ -0,0 +1,27 @@ +#ifndef SC_KEYBOARD_AOA_H +#define SC_KEYBOARD_AOA_H + +#include "common.h" + +#include + +#include "aoa_hid.h" +#include "hid/hid_keyboard.h" +#include "trait/key_processor.h" + +struct sc_keyboard_aoa { + struct sc_key_processor key_processor; // key processor trait + + struct sc_hid_keyboard hid; + struct sc_aoa *aoa; + + bool mod_lock_synchronized; +}; + +bool +sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa); + +void +sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb); + +#endif diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 5955e909..9064ad10 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -10,7 +10,7 @@ struct scrcpy_otg { struct sc_usb usb; struct sc_aoa aoa; - struct sc_hid_keyboard keyboard; + struct sc_keyboard_aoa keyboard; struct sc_hid_mouse mouse; struct sc_screen_otg screen_otg; @@ -73,7 +73,7 @@ scrcpy_otg(struct scrcpy_options *options) { enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE; - struct sc_hid_keyboard *keyboard = NULL; + struct sc_keyboard_aoa *keyboard = NULL; struct sc_hid_mouse *mouse = NULL; bool usb_device_initialized = false; bool usb_connected = false; @@ -128,7 +128,7 @@ scrcpy_otg(struct scrcpy_options *options) { options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; if (enable_keyboard) { - ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); + ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa); if (!ok) { goto end; } @@ -188,7 +188,7 @@ end: sc_hid_mouse_destroy(&s->mouse); } if (keyboard) { - sc_hid_keyboard_destroy(&s->keyboard); + sc_keyboard_aoa_destroy(&s->keyboard); } if (aoa_initialized) { diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index a0acf40b..cfc3bfa2 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -6,11 +6,11 @@ #include #include -#include "hid_keyboard.h" +#include "keyboard_aoa.h" #include "hid_mouse.h" struct sc_screen_otg { - struct sc_hid_keyboard *keyboard; + struct sc_keyboard_aoa *keyboard; struct sc_hid_mouse *mouse; SDL_Window *window; @@ -22,7 +22,7 @@ struct sc_screen_otg { }; struct sc_screen_otg_params { - struct sc_hid_keyboard *keyboard; + struct sc_keyboard_aoa *keyboard; struct sc_hid_mouse *mouse; const char *window_title; From d95276467b93eb4b045fd15000eacaecf7c1874c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 23:04:09 +0100 Subject: [PATCH 1104/1133] Extract mouse HID handling Split the mouse implementation using AOA and the code handling HID events, so that HID events can be reused for another protocol (UHID). PR #4473 --- app/meson.build | 3 +- app/src/{usb => hid}/hid_mouse.c | 113 +++++------------------ app/src/hid/hid_mouse.h | 26 ++++++ app/src/scrcpy.c | 32 +++---- app/src/usb/mouse_aoa.c | 89 ++++++++++++++++++ app/src/usb/{hid_mouse.h => mouse_aoa.h} | 10 +- app/src/usb/scrcpy_otg.c | 8 +- app/src/usb/screen_otg.h | 6 +- 8 files changed, 169 insertions(+), 118 deletions(-) rename app/src/{usb => hid}/hid_mouse.c (59%) create mode 100644 app/src/hid/hid_mouse.h create mode 100644 app/src/usb/mouse_aoa.c rename app/src/usb/{hid_mouse.h => mouse_aoa.h} (55%) diff --git a/app/meson.build b/app/meson.build index 6d572b7b..f78afa15 100644 --- a/app/meson.build +++ b/app/meson.build @@ -32,6 +32,7 @@ src = [ 'src/server.c', 'src/version.c', 'src/hid/hid_keyboard.c', + 'src/hid/hid_mouse.c', 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/util/acksync.c', @@ -90,7 +91,7 @@ if usb_support src += [ 'src/usb/aoa_hid.c', 'src/usb/keyboard_aoa.c', - 'src/usb/hid_mouse.c', + 'src/usb/mouse_aoa.c', 'src/usb/scrcpy_otg.c', 'src/usb/screen_otg.c', 'src/usb/usb.c', diff --git a/app/src/usb/hid_mouse.c b/app/src/hid/hid_mouse.c similarity index 59% rename from app/src/usb/hid_mouse.c rename to app/src/hid/hid_mouse.c index de961265..9d814448 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/hid/hid_mouse.c @@ -1,15 +1,5 @@ #include "hid_mouse.h" -#include - -#include "input_events.h" -#include "util/log.h" - -/** Downcast mouse processor to hid_mouse */ -#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor) - -#define HID_MOUSE_ACCESSORY_ID 2 - // 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position, // 1 byte for wheel motion #define HID_MOUSE_EVENT_SIZE 4 @@ -24,7 +14,7 @@ * * §4 Generic Desktop Page (0x01) (p26) */ -static const unsigned char mouse_report_desc[] = { +const uint8_t SC_HID_MOUSE_REPORT_DESC[] = { // Usage Page (Generic Desktop) 0x05, 0x01, // Usage (Mouse) @@ -90,6 +80,9 @@ static const unsigned char mouse_report_desc[] = { 0xC0, }; +const size_t SC_HID_MOUSE_REPORT_DESC_LEN = + sizeof(SC_HID_MOUSE_REPORT_DESC); + /** * A mouse HID event is 4 bytes long: * @@ -138,9 +131,9 @@ sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { // callers } -static unsigned char -buttons_state_to_hid_buttons(uint8_t buttons_state) { - unsigned char c = 0; +static uint8_t +sc_hid_buttons_from_buttons_state(uint8_t buttons_state) { + uint8_t c = 0; if (buttons_state & SC_MOUSE_BUTTON_LEFT) { c |= 1 << 0; } @@ -159,55 +152,36 @@ buttons_state_to_hid_buttons(uint8_t buttons_state) { return c; } -static void -sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, - const struct sc_mouse_motion_event *event) { - struct sc_hid_mouse *mouse = DOWNCAST(mp); - - struct sc_hid_event hid_event; - sc_hid_mouse_event_init(&hid_event); +void +sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event, + const struct sc_mouse_motion_event *event) { + sc_hid_mouse_event_init(hid_event); - unsigned char *data = hid_event.data; - data[0] = buttons_state_to_hid_buttons(event->buttons_state); + uint8_t *data = hid_event->data; + data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[1] = CLAMP(event->xrel, -127, 127); data[2] = CLAMP(event->yrel, -127, 127); data[3] = 0; // wheel coordinates only used for scrolling - - if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, - &hid_event)) { - LOGW("Could not request HID event (mouse motion)"); - } } -static void -sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, - const struct sc_mouse_click_event *event) { - struct sc_hid_mouse *mouse = DOWNCAST(mp); - - struct sc_hid_event hid_event; - sc_hid_mouse_event_init(&hid_event); +void +sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event, + const struct sc_mouse_click_event *event) { + sc_hid_mouse_event_init(hid_event); - unsigned char *data = hid_event.data; - data[0] = buttons_state_to_hid_buttons(event->buttons_state); + uint8_t *data = hid_event->data; + data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[1] = 0; // no x motion data[2] = 0; // no y motion data[3] = 0; // wheel coordinates only used for scrolling - - if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, - &hid_event)) { - LOGW("Could not request HID event (mouse click)"); - } } -static void -sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, - const struct sc_mouse_scroll_event *event) { - struct sc_hid_mouse *mouse = DOWNCAST(mp); - - struct sc_hid_event hid_event; - sc_hid_mouse_event_init(&hid_event); +void +sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event, + const struct sc_mouse_scroll_event *event) { + sc_hid_mouse_event_init(hid_event); - unsigned char *data = hid_event.data; + uint8_t *data = hid_event->data; data[0] = 0; // buttons state irrelevant (and unknown) data[1] = 0; // no x motion data[2] = 0; // no y motion @@ -215,43 +189,4 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, // are possible data[3] = CLAMP(event->vscroll, -127, 127); // Horizontal scrolling ignored - - if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, - &hid_event)) { - LOGW("Could not request HID event (mouse scroll)"); - } -} - -bool -sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) { - mouse->aoa = aoa; - - bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc, - ARRAY_LEN(mouse_report_desc)); - if (!ok) { - LOGW("Register HID mouse failed"); - return false; - } - - static const struct sc_mouse_processor_ops ops = { - .process_mouse_motion = sc_mouse_processor_process_mouse_motion, - .process_mouse_click = sc_mouse_processor_process_mouse_click, - .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, - // Touch events not supported (coordinates are not relative) - .process_touch = NULL, - }; - - mouse->mouse_processor.ops = &ops; - - mouse->mouse_processor.relative_mode = true; - - return true; -} - -void -sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) { - bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); - if (!ok) { - LOGW("Could not unregister HID mouse"); - } } diff --git a/app/src/hid/hid_mouse.h b/app/src/hid/hid_mouse.h new file mode 100644 index 00000000..e514d7d9 --- /dev/null +++ b/app/src/hid/hid_mouse.h @@ -0,0 +1,26 @@ +#ifndef SC_HID_MOUSE_H +#define SC_HID_MOUSE_H + +#endif + +#include "common.h" + +#include + +#include "hid/hid_event.h" +#include "input_events.h" + +extern const uint8_t SC_HID_MOUSE_REPORT_DESC[]; +extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN; + +void +sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event, + const struct sc_mouse_motion_event *event); + +void +sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event, + const struct sc_mouse_click_event *event); + +void +sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event, + const struct sc_mouse_scroll_event *event); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 1d5e67dc..bd448052 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -28,7 +28,7 @@ #ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/keyboard_aoa.h" -# include "usb/hid_mouse.h" +# include "usb/mouse_aoa.h" # include "usb/usb.h" #endif #include "util/acksync.h" @@ -71,7 +71,7 @@ struct scrcpy { union { struct sc_mouse_inject mouse_inject; #ifdef HAVE_USB - struct sc_hid_mouse mouse_hid; + struct sc_mouse_aoa mouse_aoa; #endif }; struct sc_timeout timeout; @@ -331,7 +331,7 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_USB bool aoa_hid_initialized = false; bool keyboard_aoa_initialized = false; - bool hid_mouse_initialized = false; + bool mouse_aoa_initialized = false; #endif bool controller_initialized = false; bool controller_started = false; @@ -545,9 +545,9 @@ scrcpy(struct scrcpy_options *options) { #ifdef HAVE_USB bool use_keyboard_aoa = options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; - bool use_aoa_mouse = + bool use_mouse_aoa = options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; - if (use_keyboard_aoa || use_aoa_mouse) { + if (use_keyboard_aoa || use_mouse_aoa) { bool ok = sc_acksync_init(&s->acksync); if (!ok) { goto end; @@ -599,16 +599,16 @@ scrcpy(struct scrcpy_options *options) { } } - if (use_aoa_mouse) { - if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) { - hid_mouse_initialized = true; - mp = &s->mouse_hid.mouse_processor; + if (use_mouse_aoa) { + if (sc_mouse_aoa_init(&s->mouse_aoa, &s->aoa)) { + mouse_aoa_initialized = true; + mp = &s->mouse_aoa.mouse_processor; } else { LOGE("Could not initialized HID mouse"); } } - bool need_aoa = keyboard_aoa_initialized || hid_mouse_initialized; + bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized; if (!need_aoa || !sc_aoa_start(&s->aoa)) { sc_acksync_destroy(&s->acksync); @@ -628,9 +628,9 @@ aoa_hid_end: sc_keyboard_aoa_destroy(&s->keyboard_aoa); keyboard_aoa_initialized = false; } - if (hid_mouse_initialized) { - sc_hid_mouse_destroy(&s->mouse_hid); - hid_mouse_initialized = false; + if (mouse_aoa_initialized) { + sc_mouse_aoa_destroy(&s->mouse_aoa); + mouse_aoa_initialized = false; } } @@ -639,7 +639,7 @@ aoa_hid_end: options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK; } - if (use_aoa_mouse && !hid_mouse_initialized) { + if (use_mouse_aoa && !mouse_aoa_initialized) { LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; } @@ -816,8 +816,8 @@ end: if (keyboard_aoa_initialized) { sc_keyboard_aoa_destroy(&s->keyboard_aoa); } - if (hid_mouse_initialized) { - sc_hid_mouse_destroy(&s->mouse_hid); + if (mouse_aoa_initialized) { + sc_mouse_aoa_destroy(&s->mouse_aoa); } sc_aoa_stop(&s->aoa); sc_usb_stop(&s->usb); diff --git a/app/src/usb/mouse_aoa.c b/app/src/usb/mouse_aoa.c new file mode 100644 index 00000000..93b32328 --- /dev/null +++ b/app/src/usb/mouse_aoa.c @@ -0,0 +1,89 @@ +#include "mouse_aoa.h" + +#include + +#include "hid/hid_mouse.h" +#include "input_events.h" +#include "util/log.h" + +/** Downcast mouse processor to mouse_aoa */ +#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor) + +#define HID_MOUSE_ACCESSORY_ID 2 + +static void +sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, + const struct sc_mouse_motion_event *event) { + struct sc_mouse_aoa *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_motion(&hid_event, event); + + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { + LOGW("Could not request HID event (mouse motion)"); + } +} + +static void +sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event) { + struct sc_mouse_aoa *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_click(&hid_event, event); + + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { + LOGW("Could not request HID event (mouse click)"); + } +} + +static void +sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event) { + struct sc_mouse_aoa *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_scroll(&hid_event, event); + + if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, + &hid_event)) { + LOGW("Could not request HID event (mouse scroll)"); + } +} + +bool +sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) { + mouse->aoa = aoa; + + bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, + SC_HID_MOUSE_REPORT_DESC, + SC_HID_MOUSE_REPORT_DESC_LEN); + if (!ok) { + LOGW("Register HID mouse failed"); + return false; + } + + static const struct sc_mouse_processor_ops ops = { + .process_mouse_motion = sc_mouse_processor_process_mouse_motion, + .process_mouse_click = sc_mouse_processor_process_mouse_click, + .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, + // Touch events not supported (coordinates are not relative) + .process_touch = NULL, + }; + + mouse->mouse_processor.ops = &ops; + + mouse->mouse_processor.relative_mode = true; + + return true; +} + +void +sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) { + bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); + if (!ok) { + LOGW("Could not unregister HID mouse"); + } +} diff --git a/app/src/usb/hid_mouse.h b/app/src/usb/mouse_aoa.h similarity index 55% rename from app/src/usb/hid_mouse.h rename to app/src/usb/mouse_aoa.h index b89f7795..afaed761 100644 --- a/app/src/usb/hid_mouse.h +++ b/app/src/usb/mouse_aoa.h @@ -1,5 +1,5 @@ -#ifndef SC_HID_MOUSE_H -#define SC_HID_MOUSE_H +#ifndef SC_MOUSE_AOA_H +#define SC_MOUSE_AOA_H #include "common.h" @@ -8,16 +8,16 @@ #include "aoa_hid.h" #include "trait/mouse_processor.h" -struct sc_hid_mouse { +struct sc_mouse_aoa { struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_aoa *aoa; }; bool -sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa); +sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa); void -sc_hid_mouse_destroy(struct sc_hid_mouse *mouse); +sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse); #endif diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 9064ad10..c1d38da3 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -11,7 +11,7 @@ struct scrcpy_otg { struct sc_usb usb; struct sc_aoa aoa; struct sc_keyboard_aoa keyboard; - struct sc_hid_mouse mouse; + struct sc_mouse_aoa mouse; struct sc_screen_otg screen_otg; }; @@ -74,7 +74,7 @@ scrcpy_otg(struct scrcpy_options *options) { enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE; struct sc_keyboard_aoa *keyboard = NULL; - struct sc_hid_mouse *mouse = NULL; + struct sc_mouse_aoa *mouse = NULL; bool usb_device_initialized = false; bool usb_connected = false; bool aoa_started = false; @@ -136,7 +136,7 @@ scrcpy_otg(struct scrcpy_options *options) { } if (enable_mouse) { - ok = sc_hid_mouse_init(&s->mouse, &s->aoa); + ok = sc_mouse_aoa_init(&s->mouse, &s->aoa); if (!ok) { goto end; } @@ -185,7 +185,7 @@ end: sc_usb_stop(&s->usb); if (mouse) { - sc_hid_mouse_destroy(&s->mouse); + sc_mouse_aoa_destroy(&s->mouse); } if (keyboard) { sc_keyboard_aoa_destroy(&s->keyboard); diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index cfc3bfa2..c4e03b87 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -7,11 +7,11 @@ #include #include "keyboard_aoa.h" -#include "hid_mouse.h" +#include "mouse_aoa.h" struct sc_screen_otg { struct sc_keyboard_aoa *keyboard; - struct sc_hid_mouse *mouse; + struct sc_mouse_aoa *mouse; SDL_Window *window; SDL_Renderer *renderer; @@ -23,7 +23,7 @@ struct sc_screen_otg { struct sc_screen_otg_params { struct sc_keyboard_aoa *keyboard; - struct sc_hid_mouse *mouse; + struct sc_mouse_aoa *mouse; const char *window_title; bool always_on_top; From 2e7f6a6fc4b3bf8347b7bba85335cb005d0fc63e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 25 Jan 2024 23:08:20 +0100 Subject: [PATCH 1105/1133] Rename default keyboard implementation to "sdk" Rename {keyboard,mouse}_inject to {keyboard,mouse}_sdk. All implementations "inject" key events and mouse events, what differs is the mechanism. For these implementations, the Android SDK API is used to inject events. Note that the input mode enum variants were already renamed (SC_KEYBOARD_INPUT_MODE_SDK and SC_MOUSE_INPUT_MODE_SDK). PR #4473 --- app/meson.build | 4 +- app/src/{keyboard_inject.c => keyboard_sdk.c} | 46 +++++++++---------- app/src/{keyboard_inject.h => keyboard_sdk.h} | 14 +++--- app/src/{mouse_inject.c => mouse_sdk.c} | 31 ++++++------- app/src/{mouse_inject.h => mouse_sdk.h} | 9 ++-- app/src/scrcpy.c | 20 ++++---- 6 files changed, 61 insertions(+), 63 deletions(-) rename app/src/{keyboard_inject.c => keyboard_sdk.c} (91%) rename app/src/{keyboard_inject.h => keyboard_sdk.h} (61%) rename app/src/{mouse_inject.c => mouse_sdk.c} (84%) rename app/src/{mouse_inject.h => mouse_sdk.h} (58%) diff --git a/app/meson.build b/app/meson.build index f78afa15..3ec9781a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -20,8 +20,8 @@ src = [ 'src/fps_counter.c', 'src/frame_buffer.c', 'src/input_manager.c', - 'src/keyboard_inject.c', - 'src/mouse_inject.c', + 'src/keyboard_sdk.c', + 'src/mouse_sdk.c', 'src/opengl.c', 'src/options.c', 'src/packet_merger.c', diff --git a/app/src/keyboard_inject.c b/app/src/keyboard_sdk.c similarity index 91% rename from app/src/keyboard_inject.c rename to app/src/keyboard_sdk.c index fe297310..726f65a9 100644 --- a/app/src/keyboard_inject.c +++ b/app/src/keyboard_sdk.c @@ -1,4 +1,4 @@ -#include "keyboard_inject.h" +#include "keyboard_sdk.h" #include @@ -9,8 +9,8 @@ #include "util/intmap.h" #include "util/log.h" -/** Downcast key processor to sc_keyboard_inject */ -#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor) +/** Downcast key processor to sc_keyboard_sdk */ +#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_sdk, key_processor) static enum android_keyevent_action convert_keycode_action(enum sc_action action) { @@ -271,20 +271,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp, // is set before injecting Ctrl+v. (void) ack_to_wait; - struct sc_keyboard_inject *ki = DOWNCAST(kp); + struct sc_keyboard_sdk *kb = DOWNCAST(kp); if (event->repeat) { - if (!ki->forward_key_repeat) { + if (!kb->forward_key_repeat) { return; } - ++ki->repeat; + ++kb->repeat; } else { - ki->repeat = 0; + kb->repeat = 0; } struct sc_control_msg msg; - if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { - if (!sc_controller_push_msg(ki->controller, &msg)) { + if (convert_input_key(event, &msg, kb->key_inject_mode, kb->repeat)) { + if (!sc_controller_push_msg(kb->controller, &msg)) { LOGW("Could not request 'inject keycode'"); } } @@ -293,14 +293,14 @@ sc_key_processor_process_key(struct sc_key_processor *kp, static void sc_key_processor_process_text(struct sc_key_processor *kp, const struct sc_text_event *event) { - struct sc_keyboard_inject *ki = DOWNCAST(kp); + struct sc_keyboard_sdk *kb = DOWNCAST(kp); - if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { + if (kb->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { // Never inject text events return; } - if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) { + if (kb->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) { char c = event->text[0]; if (isalpha(c) || c == ' ') { assert(event->text[1] == '\0'); @@ -316,22 +316,22 @@ sc_key_processor_process_text(struct sc_key_processor *kp, LOGW("Could not strdup input text"); return; } - if (!sc_controller_push_msg(ki->controller, &msg)) { + if (!sc_controller_push_msg(kb->controller, &msg)) { free(msg.inject_text.text); LOGW("Could not request 'inject text'"); } } void -sc_keyboard_inject_init(struct sc_keyboard_inject *ki, - struct sc_controller *controller, - enum sc_key_inject_mode key_inject_mode, - bool forward_key_repeat) { - ki->controller = controller; - ki->key_inject_mode = key_inject_mode; - ki->forward_key_repeat = forward_key_repeat; +sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb, + struct sc_controller *controller, + enum sc_key_inject_mode key_inject_mode, + bool forward_key_repeat) { + kb->controller = controller; + kb->key_inject_mode = key_inject_mode; + kb->forward_key_repeat = forward_key_repeat; - ki->repeat = 0; + kb->repeat = 0; static const struct sc_key_processor_ops ops = { .process_key = sc_key_processor_process_key, @@ -339,6 +339,6 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki, }; // Key injection and clipboard synchronization are serialized - ki->key_processor.async_paste = false; - ki->key_processor.ops = &ops; + kb->key_processor.async_paste = false; + kb->key_processor.ops = &ops; } diff --git a/app/src/keyboard_inject.h b/app/src/keyboard_sdk.h similarity index 61% rename from app/src/keyboard_inject.h rename to app/src/keyboard_sdk.h index b7781c1f..700ba90b 100644 --- a/app/src/keyboard_inject.h +++ b/app/src/keyboard_sdk.h @@ -1,5 +1,5 @@ -#ifndef SC_KEYBOARD_INJECT_H -#define SC_KEYBOARD_INJECT_H +#ifndef SC_KEYBOARD_SDK_H +#define SC_KEYBOARD_SDK_H #include "common.h" @@ -9,7 +9,7 @@ #include "options.h" #include "trait/key_processor.h" -struct sc_keyboard_inject { +struct sc_keyboard_sdk { struct sc_key_processor key_processor; // key processor trait struct sc_controller *controller; @@ -23,9 +23,9 @@ struct sc_keyboard_inject { }; void -sc_keyboard_inject_init(struct sc_keyboard_inject *ki, - struct sc_controller *controller, - enum sc_key_inject_mode key_inject_mode, - bool forward_key_repeat); +sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb, + struct sc_controller *controller, + enum sc_key_inject_mode key_inject_mode, + bool forward_key_repeat); #endif diff --git a/app/src/mouse_inject.c b/app/src/mouse_sdk.c similarity index 84% rename from app/src/mouse_inject.c rename to app/src/mouse_sdk.c index 71b7a64d..620fb52c 100644 --- a/app/src/mouse_inject.c +++ b/app/src/mouse_sdk.c @@ -1,4 +1,4 @@ -#include "mouse_inject.h" +#include "mouse_sdk.h" #include @@ -9,8 +9,8 @@ #include "util/intmap.h" #include "util/log.h" -/** Downcast mouse processor to sc_mouse_inject */ -#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor) +/** Downcast mouse processor to sc_mouse_sdk */ +#define DOWNCAST(MP) container_of(MP, struct sc_mouse_sdk, mouse_processor) static enum android_motionevent_buttons convert_mouse_buttons(uint32_t state) { @@ -63,7 +63,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, return; } - struct sc_mouse_inject *mi = DOWNCAST(mp); + struct sc_mouse_sdk *m = DOWNCAST(mp); struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, @@ -76,7 +76,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, }, }; - if (!sc_controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(m->controller, &msg)) { LOGW("Could not request 'inject mouse motion event'"); } } @@ -84,7 +84,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, static void sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, const struct sc_mouse_click_event *event) { - struct sc_mouse_inject *mi = DOWNCAST(mp); + struct sc_mouse_sdk *m = DOWNCAST(mp); struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, @@ -98,7 +98,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, }, }; - if (!sc_controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(m->controller, &msg)) { LOGW("Could not request 'inject mouse click event'"); } } @@ -106,7 +106,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, static void sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, const struct sc_mouse_scroll_event *event) { - struct sc_mouse_inject *mi = DOWNCAST(mp); + struct sc_mouse_sdk *m = DOWNCAST(mp); struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, @@ -118,7 +118,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, }, }; - if (!sc_controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(m->controller, &msg)) { LOGW("Could not request 'inject mouse scroll event'"); } } @@ -126,7 +126,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, static void sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, const struct sc_touch_event *event) { - struct sc_mouse_inject *mi = DOWNCAST(mp); + struct sc_mouse_sdk *m = DOWNCAST(mp); struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, @@ -139,15 +139,14 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, }, }; - if (!sc_controller_push_msg(mi->controller, &msg)) { + if (!sc_controller_push_msg(m->controller, &msg)) { LOGW("Could not request 'inject touch event'"); } } void -sc_mouse_inject_init(struct sc_mouse_inject *mi, - struct sc_controller *controller) { - mi->controller = controller; +sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) { + m->controller = controller; static const struct sc_mouse_processor_ops ops = { .process_mouse_motion = sc_mouse_processor_process_mouse_motion, @@ -156,7 +155,7 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi, .process_touch = sc_mouse_processor_process_touch, }; - mi->mouse_processor.ops = &ops; + m->mouse_processor.ops = &ops; - mi->mouse_processor.relative_mode = false; + m->mouse_processor.relative_mode = false; } diff --git a/app/src/mouse_inject.h b/app/src/mouse_sdk.h similarity index 58% rename from app/src/mouse_inject.h rename to app/src/mouse_sdk.h index 59a6a5d8..444a6ad5 100644 --- a/app/src/mouse_inject.h +++ b/app/src/mouse_sdk.h @@ -1,5 +1,5 @@ -#ifndef SC_MOUSE_INJECT_H -#define SC_MOUSE_INJECT_H +#ifndef SC_MOUSE_SDK_H +#define SC_MOUSE_SDK_H #include "common.h" @@ -9,14 +9,13 @@ #include "screen.h" #include "trait/mouse_processor.h" -struct sc_mouse_inject { +struct sc_mouse_sdk { struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_controller *controller; }; void -sc_mouse_inject_init(struct sc_mouse_inject *mi, - struct sc_controller *controller); +sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index bd448052..876b400a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -20,8 +20,8 @@ #include "demuxer.h" #include "events.h" #include "file_pusher.h" -#include "keyboard_inject.h" -#include "mouse_inject.h" +#include "keyboard_sdk.h" +#include "mouse_sdk.h" #include "recorder.h" #include "screen.h" #include "server.h" @@ -63,13 +63,13 @@ struct scrcpy { struct sc_acksync acksync; #endif union { - struct sc_keyboard_inject keyboard_inject; + struct sc_keyboard_sdk keyboard_sdk; #ifdef HAVE_USB struct sc_keyboard_aoa keyboard_aoa; #endif }; union { - struct sc_mouse_inject mouse_inject; + struct sc_mouse_sdk mouse_sdk; #ifdef HAVE_USB struct sc_mouse_aoa mouse_aoa; #endif @@ -651,16 +651,16 @@ aoa_hid_end: // keyboard_input_mode may have been reset if HID mode failed if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) { - sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, - options->key_inject_mode, - options->forward_key_repeat); - kp = &s->keyboard_inject.key_processor; + sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller, + options->key_inject_mode, + options->forward_key_repeat); + kp = &s->keyboard_sdk.key_processor; } // mouse_input_mode may have been reset if HID mode failed if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { - sc_mouse_inject_init(&s->mouse_inject, &s->controller); - mp = &s->mouse_inject.mouse_processor; + sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); + mp = &s->mouse_sdk.mouse_processor; } if (!sc_controller_init(&s->controller, s->server.control_socket, From 107f7a83abe60ff1a25db42aa864ed13f5a4c0c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 25 Feb 2024 14:54:17 +0100 Subject: [PATCH 1106/1133] Extract binary to hex string conversion PR #4473 --- app/src/usb/aoa_hid.c | 14 +++++--------- app/src/util/str.c | 20 ++++++++++++++++++++ app/src/util/str.h | 6 ++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index d6b418a0..50bc33fe 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -5,6 +5,7 @@ #include "aoa_hid.h" #include "util/log.h" +#include "util/str.h" // See . #define ACCESSORY_REGISTER_HID 54 @@ -20,17 +21,12 @@ static void sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) { // HID Event: [00] FF FF FF FF... assert(event->size); - unsigned buffer_size = event->size * 3 + 1; - char *buffer = malloc(buffer_size); - if (!buffer) { - LOG_OOM(); + char *hex = sc_str_to_hex_string(event->data, event->size); + if (!hex) { return; } - for (unsigned i = 0; i < event->size; ++i) { - snprintf(buffer + i * 3, 4, " %02x", event->data[i]); - } - LOGV("HID Event: [%d]%s", accessory_id, buffer); - free(buffer); + LOGV("HID Event: [%d] %s", accessory_id, hex); + free(hex); } bool diff --git a/app/src/util/str.c b/app/src/util/str.c index d78aa9d7..755369d8 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -333,3 +334,22 @@ sc_str_remove_trailing_cr(char *s, size_t len) { } return len; } + +char * +sc_str_to_hex_string(const uint8_t *data, size_t size) { + size_t buffer_size = size * 3 + 1; + char *buffer = malloc(buffer_size); + if (!buffer) { + LOG_OOM(); + return NULL; + } + + for (size_t i = 0; i < size; ++i) { + snprintf(buffer + i * 3, 4, "%02X ", data[i]); + } + + // Remove the final space + buffer[size * 3] = '\0'; + + return buffer; +} diff --git a/app/src/util/str.h b/app/src/util/str.h index 4f7eeeda..20da26f0 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -138,4 +138,10 @@ sc_str_index_of_column(const char *s, unsigned col, const char *seps); size_t sc_str_remove_trailing_cr(char *s, size_t len); +/** + * Convert binary data to hexadecimal string + */ +char * +sc_str_to_hex_string(const uint8_t *data, size_t len); + #endif From 4d2c2514fc466fc1dd8cfb972c2697a843e28d70 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 24 Feb 2024 22:33:48 +0100 Subject: [PATCH 1107/1133] Initialize controller in two steps There is a dependency cycle in the initialization order: - keyboard depends on controller - controller depends on acksync - acksync depends on keyboard initialization To break this cycle, bind the async instance to the controller in a second step. PR #4473 --- app/src/controller.c | 11 ++++++++--- app/src/controller.h | 7 +++++-- app/src/receiver.c | 5 ++--- app/src/receiver.h | 3 +-- app/src/scrcpy.c | 5 +++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 250321fe..5a5bfde9 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -7,8 +7,7 @@ #define SC_CONTROL_MSG_QUEUE_MAX 64 bool -sc_controller_init(struct sc_controller *controller, sc_socket control_socket, - struct sc_acksync *acksync) { +sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { sc_vecdeque_init(&controller->queue); bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); @@ -16,7 +15,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket, return false; } - ok = sc_receiver_init(&controller->receiver, control_socket, acksync); + ok = sc_receiver_init(&controller->receiver, control_socket); if (!ok) { sc_vecdeque_destroy(&controller->queue); return false; @@ -43,6 +42,12 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket, return true; } +void +sc_controller_set_acksync(struct sc_controller *controller, + struct sc_acksync *acksync) { + controller->receiver.acksync = acksync; +} + void sc_controller_destroy(struct sc_controller *controller) { sc_cond_destroy(&controller->msg_cond); diff --git a/app/src/controller.h b/app/src/controller.h index a044b2bf..767e1731 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -25,8 +25,11 @@ struct sc_controller { }; bool -sc_controller_init(struct sc_controller *controller, sc_socket control_socket, - struct sc_acksync *acksync); +sc_controller_init(struct sc_controller *controller, sc_socket control_socket); + +void +sc_controller_set_acksync(struct sc_controller *controller, + struct sc_acksync *acksync); void sc_controller_destroy(struct sc_controller *controller); diff --git a/app/src/receiver.c b/app/src/receiver.c index 6be705e3..97299b3f 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -8,15 +8,14 @@ #include "util/log.h" bool -sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, - struct sc_acksync *acksync) { +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { return false; } receiver->control_socket = control_socket; - receiver->acksync = acksync; + receiver->acksync = NULL; return true; } diff --git a/app/src/receiver.h b/app/src/receiver.h index eb959fb8..43f89615 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -20,8 +20,7 @@ struct sc_receiver { }; bool -sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, - struct sc_acksync *acksync); +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket); void sc_receiver_destroy(struct sc_receiver *receiver); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 876b400a..7ecda6d0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -663,12 +663,13 @@ aoa_hid_end: mp = &s->mouse_sdk.mouse_processor; } - if (!sc_controller_init(&s->controller, s->server.control_socket, - acksync)) { + if (!sc_controller_init(&s->controller, s->server.control_socket)) { goto end; } controller_initialized = true; + sc_controller_set_acksync(&s->controller, acksync); + if (!sc_controller_start(&s->controller)) { goto end; } From 604e59ac7bd907567348627712185f1fa88e4659 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 24 Feb 2024 22:33:58 +0100 Subject: [PATCH 1108/1133] Initialize controller before keyboards The UHID keyboard initializer will need the controller. PR #4473 --- app/src/scrcpy.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7ecda6d0..a407dff1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -542,6 +542,13 @@ scrcpy(struct scrcpy_options *options) { struct sc_mouse_processor *mp = NULL; if (options->control) { + if (!sc_controller_init(&s->controller, s->server.control_socket)) { + goto end; + } + controller_initialized = true; + + controller = &s->controller; + #ifdef HAVE_USB bool use_keyboard_aoa = options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; @@ -663,18 +670,12 @@ aoa_hid_end: mp = &s->mouse_sdk.mouse_processor; } - if (!sc_controller_init(&s->controller, s->server.control_socket)) { - goto end; - } - controller_initialized = true; - sc_controller_set_acksync(&s->controller, acksync); if (!sc_controller_start(&s->controller)) { goto end; } controller_started = true; - controller = &s->controller; } // There is a controller if and only if control is enabled From 4d5b67cc8018753a02a786cd4ca27e38cad50d65 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Feb 2024 10:20:01 +0100 Subject: [PATCH 1109/1133] Log controller handling errors On close, the controller is expected to throw an IOException because the socket is closed, so the exception was ignored. However, message handling actions may also throw IOException, and they must not be silently ignored. PR #4473 --- .../com/genymobile/scrcpy/Controller.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 73d6ad57..a3508c96 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -81,8 +81,9 @@ public class Controller implements AsyncProcessor { SystemClock.sleep(500); } - while (!Thread.currentThread().isInterrupted()) { - handleEvent(); + boolean alive = true; + while (!Thread.currentThread().isInterrupted() && alive) { + alive = handleEvent(); } } @@ -92,7 +93,7 @@ public class Controller implements AsyncProcessor { try { control(); } catch (IOException e) { - // this is expected on close + Ln.e("Controller error", e); } finally { Ln.d("Controller stopped"); listener.onTerminated(true); @@ -122,8 +123,15 @@ public class Controller implements AsyncProcessor { return sender; } - private void handleEvent() throws IOException { - ControlMessage msg = controlChannel.recv(); + private boolean handleEvent() throws IOException { + ControlMessage msg; + try { + msg = controlChannel.recv(); + } catch (IOException e) { + // this is expected on close + return false; + } + switch (msg.getType()) { case ControlMessage.TYPE_INJECT_KEYCODE: if (device.supportsInputEvents()) { @@ -185,6 +193,8 @@ public class Controller implements AsyncProcessor { default: // do nothing } + + return true; } private boolean injectKeycode(int action, int keycode, int repeat, int metaState) { From 840680f546be59d1581ae4014a6546e137447cbc Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:17:35 +0800 Subject: [PATCH 1110/1133] Add UHID keyboard support Use the following command: scrcpy --keyboard=uhid PR #4473 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/meson.build | 1 + app/scrcpy.1 | 7 +- app/src/cli.c | 25 +++- app/src/control_msg.c | 28 ++++ app/src/control_msg.h | 13 ++ app/src/options.h | 1 + app/src/scrcpy.c | 13 +- app/src/uhid/keyboard_uhid.c | 72 +++++++++ app/src/uhid/keyboard_uhid.h | 23 +++ app/tests/test_control_msg_serialize.c | 49 +++++++ .../com/genymobile/scrcpy/ControlMessage.java | 28 ++++ .../scrcpy/ControlMessageReader.java | 61 +++++++- .../com/genymobile/scrcpy/Controller.java | 10 ++ .../com/genymobile/scrcpy/UhidManager.java | 138 ++++++++++++++++++ .../scrcpy/ControlMessageReaderTest.java | 44 ++++++ 17 files changed, 497 insertions(+), 20 deletions(-) create mode 100644 app/src/uhid/keyboard_uhid.c create mode 100644 app/src/uhid/keyboard_uhid.h create mode 100644 server/src/main/java/com/genymobile/scrcpy/UhidManager.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index b2009c56..904ccdeb 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -116,7 +116,7 @@ _scrcpy() { return ;; --keyboard) - COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur")) return ;; --mouse) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index a4611632..f81d2b22 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -34,7 +34,7 @@ arguments=( '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' - '--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa)' + '--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--kill-adb-on-close[Kill adb when scrcpy terminates]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-camera-sizes[List the valid camera capture sizes]' diff --git a/app/meson.build b/app/meson.build index 3ec9781a..9a2d2838 100644 --- a/app/meson.build +++ b/app/meson.build @@ -35,6 +35,7 @@ src = [ 'src/hid/hid_mouse.c', 'src/trait/frame_source.c', 'src/trait/packet_source.c', + 'src/uhid/keyboard_uhid.c', 'src/util/acksync.c', 'src/util/audiobuf.c', 'src/util/average.c', diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ed2e620e..1dfcab2b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -175,13 +175,14 @@ Print this help. .BI "\-\-keyboard " mode Select how to send keyboard inputs to the device. -Possible values are "disabled", "sdk" and "aoa": +Possible values are "disabled", "sdk", "uhid" and "aoa": - "disabled" does not send keyboard inputs to the device. - "sdk" uses the Android system API to deliver keyboard events to applications. - - "aoa" simulates a physical keyboard using the AOAv2 protocol. It may only work over USB. + - "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device. + - "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB. -For "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: +For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS diff --git a/app/src/cli.c b/app/src/cli.c index 364590a4..59cd5699 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -365,19 +365,22 @@ static const struct sc_option options[] = { .longopt = "keyboard", .argdesc = "mode", .text = "Select how to send keyboard inputs to the device.\n" - "Possible values are \"disabled\", \"sdk\" and \"aoa\".\n" + "Possible values are \"disabled\", \"sdk\", \"uhid\" and " + "\"aoa\".\n" "\"disabled\" does not send keyboard inputs to the device.\n" "\"sdk\" uses the Android system API to deliver keyboard " "events to applications.\n" + "\"uhid\" simulates a physical HID keyboard using the Linux " + "UHID kernel module on the device.\n" "\"aoa\" simulates a physical keyboard using the AOAv2 " "protocol. It may only work over USB.\n" - "For \"aoa\", the keyboard layout must be configured (once and " - "for all) on the device, via Settings -> System -> Languages " - "and input -> Physical keyboard. This settings page can be " - "started directly: `adb shell am start -a " + "For \"uhid\" and \"aoa\", the keyboard layout must be " + "configured (once and for all) on the device, via Settings -> " + "System -> Languages and input -> Physical keyboard. This " + "settings page can be started directly: `adb shell am start -a " "android.settings.HARD_KEYBOARD_SETTINGS`.\n" - "This option is only available when the HID keyboard is " - "enabled (or a physical keyboard is connected).\n" + "This option is only available when a HID keyboard is enabled " + "(or a physical keyboard is connected).\n" "Also see --mouse.", }, { @@ -1939,6 +1942,11 @@ parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) { return true; } + if (!strcmp(optarg, "uhid")) { + *mode = SC_KEYBOARD_INPUT_MODE_UHID; + return true; + } + if (!strcmp(optarg, "aoa")) { #ifdef HAVE_USB *mode = SC_KEYBOARD_INPUT_MODE_AOA; @@ -1949,7 +1957,8 @@ parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) { #endif } - LOGE("Unsupported keyboard: %s (expected disabled, sdk or aoa)", optarg); + LOGE("Unsupported keyboard: %s (expected disabled, sdk, uhid and aoa)", + optarg); return false; } diff --git a/app/src/control_msg.c b/app/src/control_msg.c index e173dac7..88575b4e 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -146,6 +146,17 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: buf[1] = msg->set_screen_power_mode.mode; return 2; + case SC_CONTROL_MSG_TYPE_UHID_CREATE: + sc_write16be(&buf[1], msg->uhid_create.id); + sc_write16be(&buf[3], msg->uhid_create.report_desc_size); + memcpy(&buf[5], msg->uhid_create.report_desc, + msg->uhid_create.report_desc_size); + return 5 + msg->uhid_create.report_desc_size; + case SC_CONTROL_MSG_TYPE_UHID_INPUT: + sc_write16be(&buf[1], msg->uhid_input.id); + sc_write16be(&buf[3], msg->uhid_input.size); + memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size); + return 5 + msg->uhid_input.size; case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: @@ -242,6 +253,23 @@ sc_control_msg_log(const struct sc_control_msg *msg) { case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: LOG_CMSG("rotate device"); break; + case SC_CONTROL_MSG_TYPE_UHID_CREATE: + LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16, + msg->uhid_create.id, msg->uhid_create.report_desc_size); + break; + case SC_CONTROL_MSG_TYPE_UHID_INPUT: { + char *hex = sc_str_to_hex_string(msg->uhid_input.data, + msg->uhid_input.size); + if (hex) { + LOG_CMSG("UHID input [%" PRIu16 "] %s", + msg->uhid_input.id, hex); + free(hex); + } else { + LOG_CMSG("UHID input [%" PRIu16 "] size=%" PRIu16, + msg->uhid_input.id, msg->uhid_input.size); + } + break; + } default: LOG_CMSG("unknown type: %u", (unsigned) msg->type); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 04eeb83b..550168c2 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -10,6 +10,7 @@ #include "android/input.h" #include "android/keycodes.h" #include "coords.h" +#include "hid/hid_event.h" #define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k @@ -37,6 +38,8 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, + SC_CONTROL_MSG_TYPE_UHID_CREATE, + SC_CONTROL_MSG_TYPE_UHID_INPUT, }; enum sc_screen_power_mode { @@ -92,6 +95,16 @@ struct sc_control_msg { struct { enum sc_screen_power_mode mode; } set_screen_power_mode; + struct { + uint16_t id; + uint16_t report_desc_size; + const uint8_t *report_desc; // pointer to static data + } uhid_create; + struct { + uint16_t id; + uint16_t size; + uint8_t data[SC_HID_MAX_SIZE]; + } uhid_input; }; }; diff --git a/app/src/options.h b/app/src/options.h index 1fb31c1a..6d62fac0 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -143,6 +143,7 @@ enum sc_keyboard_input_mode { SC_KEYBOARD_INPUT_MODE_AUTO, SC_KEYBOARD_INPUT_MODE_DISABLED, SC_KEYBOARD_INPUT_MODE_SDK, + SC_KEYBOARD_INPUT_MODE_UHID, SC_KEYBOARD_INPUT_MODE_AOA, }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a407dff1..d01d3619 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -25,6 +25,7 @@ #include "recorder.h" #include "screen.h" #include "server.h" +#include "uhid/keyboard_uhid.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/keyboard_aoa.h" @@ -64,6 +65,7 @@ struct scrcpy { #endif union { struct sc_keyboard_sdk keyboard_sdk; + struct sc_keyboard_uhid keyboard_uhid; #ifdef HAVE_USB struct sc_keyboard_aoa keyboard_aoa; #endif @@ -656,15 +658,22 @@ aoa_hid_end: assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA); #endif - // keyboard_input_mode may have been reset if HID mode failed + // keyboard_input_mode may have been reset if AOA mode failed if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) { sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller, options->key_inject_mode, options->forward_key_repeat); kp = &s->keyboard_sdk.key_processor; + } else if (options->keyboard_input_mode + == SC_KEYBOARD_INPUT_MODE_UHID) { + bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller); + if (!ok) { + goto end; + } + kp = &s->keyboard_uhid.key_processor; } - // mouse_input_mode may have been reset if HID mode failed + // mouse_input_mode may have been reset if AOA mode failed if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); mp = &s->mouse_sdk.mouse_processor; diff --git a/app/src/uhid/keyboard_uhid.c b/app/src/uhid/keyboard_uhid.c new file mode 100644 index 00000000..d974d578 --- /dev/null +++ b/app/src/uhid/keyboard_uhid.c @@ -0,0 +1,72 @@ +#include "keyboard_uhid.h" + +#include "util/log.h" + +/** Downcast key processor to keyboard_uhid */ +#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor) + +#define UHID_KEYBOARD_ID 1 + +static void +sc_key_processor_process_key(struct sc_key_processor *kp, + const struct sc_key_event *event, + uint64_t ack_to_wait) { + (void) ack_to_wait; + + if (event->repeat) { + // In USB HID protocol, key repeat is handled by the host (Android), so + // just ignore key repeat here. + return; + } + + struct sc_keyboard_uhid *kb = DOWNCAST(kp); + + struct sc_hid_event hid_event; + + // Not all keys are supported, just ignore unsupported keys + if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) { + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; + msg.uhid_input.id = UHID_KEYBOARD_ID; + + assert(hid_event.size <= SC_HID_MAX_SIZE); + memcpy(msg.uhid_input.data, hid_event.data, hid_event.size); + msg.uhid_input.size = hid_event.size; + + if (!sc_controller_push_msg(kb->controller, &msg)) { + LOGE("Could not send UHID_INPUT message (key)"); + } + } +} + +bool +sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, + struct sc_controller *controller) { + sc_hid_keyboard_init(&kb->hid); + + kb->controller = controller; + + static const struct sc_key_processor_ops ops = { + .process_key = sc_key_processor_process_key, + // Never forward text input via HID (all the keys are injected + // separately) + .process_text = NULL, + }; + + // Clipboard synchronization is requested over the same control socket, so + // there is no need for a specific synchronization mechanism + kb->key_processor.async_paste = false; + kb->key_processor.ops = &ops; + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; + msg.uhid_create.id = UHID_KEYBOARD_ID; + msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC; + msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN; + if (!sc_controller_push_msg(controller, &msg)) { + LOGE("Could not send UHID_CREATE message (keyboard)"); + return false; + } + + return true; +} diff --git a/app/src/uhid/keyboard_uhid.h b/app/src/uhid/keyboard_uhid.h new file mode 100644 index 00000000..854ba008 --- /dev/null +++ b/app/src/uhid/keyboard_uhid.h @@ -0,0 +1,23 @@ +#ifndef SC_KEYBOARD_UHID_H +#define SC_KEYBOARD_UHID_H + +#include "common.h" + +#include + +#include "controller.h" +#include "hid/hid_keyboard.h" +#include "trait/key_processor.h" + +struct sc_keyboard_uhid { + struct sc_key_processor key_processor; // key processor trait + + struct sc_hid_keyboard hid; + struct sc_controller *controller; +}; + +bool +sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, + struct sc_controller *controller); + +#endif diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 80d33fc3..0ab61153 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -323,6 +323,53 @@ static void test_serialize_rotate_device(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_uhid_create(void) { + const uint8_t report_desc[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_CREATE, + .uhid_create = { + .id = 42, + .report_desc_size = sizeof(report_desc), + .report_desc = report_desc, + }, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 16); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_UHID_CREATE, + 0, 42, // id + 0, 11, // size + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + +static void test_serialize_uhid_input(void) { + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_INPUT, + .uhid_input = { + .id = 42, + .size = 5, + .data = {1, 2, 3, 4, 5}, + }, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 10); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_UHID_INPUT, + 0, 42, // id + 0, 5, // size + 1, 2, 3, 4, 5, + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -341,5 +388,7 @@ int main(int argc, char *argv[]) { test_serialize_set_clipboard_long(); test_serialize_set_screen_power_mode(); test_serialize_rotate_device(); + test_serialize_uhid_create(); + test_serialize_uhid_input(); return 0; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index e1800374..74bf5610 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -17,6 +17,8 @@ public final class ControlMessage { public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_ROTATE_DEVICE = 11; + public static final int TYPE_UHID_CREATE = 12; + public static final int TYPE_UHID_INPUT = 13; public static final long SEQUENCE_INVALID = 0; @@ -40,6 +42,8 @@ public final class ControlMessage { private boolean paste; private int repeat; private long sequence; + private int id; + private byte[] data; private ControlMessage() { } @@ -123,6 +127,22 @@ public final class ControlMessage { return msg; } + public static ControlMessage createUhidCreate(int id, byte[] reportDesc) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_CREATE; + msg.id = id; + msg.data = reportDesc; + return msg; + } + + public static ControlMessage createUhidInput(int id, byte[] data) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_INPUT; + msg.id = id; + msg.data = data; + return msg; + } + public int getType() { return type; } @@ -186,4 +206,12 @@ public final class ControlMessage { public long getSequence() { return sequence; } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index d95c36d8..24aa73c0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -15,6 +15,8 @@ public class ControlMessageReader { static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; + static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4; + static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4; private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k @@ -86,6 +88,12 @@ public class ControlMessageReader { case ControlMessage.TYPE_ROTATE_DEVICE: msg = ControlMessage.createEmpty(type); break; + case ControlMessage.TYPE_UHID_CREATE: + msg = parseUhidCreate(); + break; + case ControlMessage.TYPE_UHID_INPUT: + msg = parseUhidInput(); + break; default: Ln.w("Unknown event type: " + type); msg = null; @@ -110,12 +118,21 @@ public class ControlMessageReader { return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState); } - private String parseString() { - if (buffer.remaining() < 4) { - return null; + private int parseBufferLength(int sizeBytes) { + assert sizeBytes > 0 && sizeBytes <= 4; + if (buffer.remaining() < sizeBytes) { + return -1; + } + int value = 0; + for (int i = 0; i < sizeBytes; ++i) { + value = (value << 8) | (buffer.get() & 0xFF); } - int len = buffer.getInt(); - if (buffer.remaining() < len) { + return value; + } + + private String parseString() { + int len = parseBufferLength(4); + if (len == -1 || buffer.remaining() < len) { return null; } int position = buffer.position(); @@ -124,6 +141,16 @@ public class ControlMessageReader { return new String(rawBuffer, position, len, StandardCharsets.UTF_8); } + private byte[] parseByteArray(int sizeBytes) { + int len = parseBufferLength(sizeBytes); + if (len == -1 || buffer.remaining() < len) { + return null; + } + byte[] data = new byte[len]; + buffer.get(data); + return data; + } + private ControlMessage parseInjectText() { String text = parseString(); if (text == null) { @@ -193,6 +220,30 @@ public class ControlMessageReader { return ControlMessage.createSetScreenPowerMode(mode); } + private ControlMessage parseUhidCreate() { + if (buffer.remaining() < UHID_CREATE_FIXED_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + byte[] data = parseByteArray(2); + if (data == null) { + return null; + } + return ControlMessage.createUhidCreate(id, data); + } + + private ControlMessage parseUhidInput() { + if (buffer.remaining() < UHID_INPUT_FIXED_PAYLOAD_LENGTH) { + return null; + } + int id = buffer.getShort(); + byte[] data = parseByteArray(2); + if (data == null) { + return null; + } + return ControlMessage.createUhidInput(id, data); + } + private static Position readPosition(ByteBuffer buffer) { int x = buffer.getInt(); int y = buffer.getInt(); diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index a3508c96..d757d577 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -26,6 +26,8 @@ public class Controller implements AsyncProcessor { private Thread thread; + private final UhidManager uhidManager; + private final Device device; private final ControlChannel controlChannel; private final CleanUp cleanUp; @@ -50,6 +52,7 @@ public class Controller implements AsyncProcessor { this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(controlChannel); + uhidManager = new UhidManager(); } private void initPointers() { @@ -96,6 +99,7 @@ public class Controller implements AsyncProcessor { Ln.e("Controller error", e); } finally { Ln.d("Controller stopped"); + uhidManager.closeAll(); listener.onTerminated(true); } }, "control-recv"); @@ -190,6 +194,12 @@ public class Controller implements AsyncProcessor { case ControlMessage.TYPE_ROTATE_DEVICE: device.rotateDevice(); break; + case ControlMessage.TYPE_UHID_CREATE: + uhidManager.open(msg.getId(), msg.getData()); + break; + case ControlMessage.TYPE_UHID_INPUT: + uhidManager.writeInput(msg.getId(), msg.getData()); + break; default: // do nothing } diff --git a/server/src/main/java/com/genymobile/scrcpy/UhidManager.java b/server/src/main/java/com/genymobile/scrcpy/UhidManager.java new file mode 100644 index 00000000..96458bf0 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/UhidManager.java @@ -0,0 +1,138 @@ +package com.genymobile.scrcpy; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.ArrayMap; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +public final class UhidManager { + + // Linux: include/uapi/linux/uhid.h + private static final int UHID_CREATE2 = 11; + private static final int UHID_INPUT2 = 12; + + // Linux: include/uapi/linux/input.h + private static final short BUS_VIRTUAL = 0x06; + + private final ArrayMap fds = new ArrayMap<>(); + + public void open(int id, byte[] reportDesc) throws IOException { + try { + FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); + try { + FileDescriptor old = fds.put(id, fd); + if (old != null) { + Ln.w("Duplicate UHID id: " + id); + close(old); + } + + byte[] req = buildUhidCreate2Req(reportDesc); + Os.write(fd, req, 0, req.length); + } catch (Exception e) { + close(fd); + throw e; + } + } catch (ErrnoException e) { + throw new IOException(e); + } + } + + public void writeInput(int id, byte[] data) throws IOException { + FileDescriptor fd = fds.get(id); + if (fd == null) { + Ln.w("Unknown UHID id: " + id); + return; + } + + try { + byte[] req = buildUhidInput2Req(data); + Os.write(fd, req, 0, req.length); + } catch (ErrnoException e) { + throw new IOException(e); + } + } + + private static byte[] buildUhidCreate2Req(byte[] reportDesc) { + /* + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_create2_req { + * uint8_t name[128]; + * uint8_t phys[64]; + * uint8_t uniq[64]; + * uint16_t rd_size; + * uint16_t bus; + * uint32_t vendor; + * uint32_t product; + * uint32_t version; + * uint32_t country; + * uint8_t rd_data[HID_MAX_DESCRIPTOR_SIZE]; + * }; + * }; + * } __attribute__((__packed__)); + */ + + byte[] empty = new byte[256]; + ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_CREATE2); + buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII)); + buf.put(empty, 0, 256 - "scrcpy".length()); + buf.putShort((short) reportDesc.length); + buf.putShort(BUS_VIRTUAL); + buf.putInt(0); // vendor id + buf.putInt(0); // product id + buf.putInt(0); // version + buf.putInt(0); // country; + buf.put(reportDesc); + return buf.array(); + } + + private static byte[] buildUhidInput2Req(byte[] data) { + /* + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_input2_req { + * uint16_t size; + * uint8_t data[UHID_DATA_MAX]; + * }; + * }; + * } __attribute__((__packed__)); + */ + + ByteBuffer buf = ByteBuffer.allocate(6 + data.length).order(ByteOrder.nativeOrder()); + buf.putInt(UHID_INPUT2); + buf.putShort((short) data.length); + buf.put(data); + return buf.array(); + } + + public void close(int id) { + FileDescriptor fd = fds.get(id); + assert fd != null; + close(fd); + } + + public void closeAll() { + for (FileDescriptor fd : fds.values()) { + close(fd); + } + } + + private static void close(FileDescriptor fd) { + try { + Os.close(fd); + } catch (ErrnoException e) { + Ln.e("Failed to close uhid: " + e.getMessage()); + } + } +} diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 47097c78..7cc67c3e 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -322,6 +322,50 @@ public class ControlMessageReaderTest { Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType()); } + @Test + public void testParseUhidCreate() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_UHID_CREATE); + dos.writeShort(42); // id + byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + dos.writeShort(data.length); // size + dos.write(data); + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType()); + Assert.assertEquals(42, event.getId()); + Assert.assertArrayEquals(data, event.getData()); + } + + @Test + public void testParseUhidInput() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_UHID_INPUT); + dos.writeShort(42); // id + byte[] data = {1, 2, 3, 4, 5}; + dos.writeShort(data.length); // size + dos.write(data); + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_UHID_INPUT, event.getType()); + Assert.assertEquals(42, event.getId()); + Assert.assertArrayEquals(data, event.getData()); + } + @Test public void testMultiEvents() throws IOException { ControlMessageReader reader = new ControlMessageReader(); From 021c5d371ad0d2c932b981151f45f794d8843ebe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 24 Feb 2024 22:27:53 +0100 Subject: [PATCH 1111/1133] Refactor DeviceMessageSender Refactor DeviceMessage as a queue of message. This will allow to add other message types. PR #4473 --- .../com/genymobile/scrcpy/Controller.java | 6 ++- .../com/genymobile/scrcpy/DeviceMessage.java | 2 - .../scrcpy/DeviceMessageSender.java | 42 ++++--------------- .../java/com/genymobile/scrcpy/Server.java | 5 ++- 4 files changed, 17 insertions(+), 38 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index d757d577..b925dd80 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -413,7 +413,8 @@ public class Controller implements AsyncProcessor { if (!clipboardAutosync) { String clipboardText = Device.getClipboardText(); if (clipboardText != null) { - sender.pushClipboardText(clipboardText); + DeviceMessage msg = DeviceMessage.createClipboard(clipboardText); + sender.send(msg); } } } @@ -431,7 +432,8 @@ public class Controller implements AsyncProcessor { if (sequence != ControlMessage.SEQUENCE_INVALID) { // Acknowledgement requested - sender.pushAckClipboard(sequence); + DeviceMessage msg = DeviceMessage.createAckClipboard(sequence); + sender.send(msg); } return ok; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java index 5b7c4de5..2e333e3f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -5,8 +5,6 @@ public final class DeviceMessage { public static final int TYPE_CLIPBOARD = 0; public static final int TYPE_ACK_CLIPBOARD = 1; - public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID; - private int type; private String text; private long sequence; diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index efb7b975..af14bb4e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -1,54 +1,30 @@ package com.genymobile.scrcpy; import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; public final class DeviceMessageSender { private final ControlChannel controlChannel; private Thread thread; - - private String clipboardText; - - private long ack; + private final BlockingQueue queue = new ArrayBlockingQueue<>(16); public DeviceMessageSender(ControlChannel controlChannel) { this.controlChannel = controlChannel; } - public synchronized void pushClipboardText(String text) { - clipboardText = text; - notify(); - } - - public synchronized void pushAckClipboard(long sequence) { - ack = sequence; - notify(); + public void send(DeviceMessage msg) { + if (!queue.offer(msg)) { + Ln.w("Device message dropped: " + msg.getType()); + } } private void loop() throws IOException, InterruptedException { while (!Thread.currentThread().isInterrupted()) { - String text; - long sequence; - synchronized (this) { - while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) { - wait(); - } - text = clipboardText; - clipboardText = null; - - sequence = ack; - ack = DeviceMessage.SEQUENCE_INVALID; - } - - if (sequence != DeviceMessage.SEQUENCE_INVALID) { - DeviceMessage event = DeviceMessage.createAckClipboard(sequence); - controlChannel.send(event); - } - if (text != null) { - DeviceMessage event = DeviceMessage.createClipboard(text); - controlChannel.send(event); - } + DeviceMessage msg = queue.take(); + controlChannel.send(msg); } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 3936648d..587a46df 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -133,7 +133,10 @@ public final class Server { if (control) { ControlChannel controlChannel = connection.getControlChannel(); Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn()); - device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); + device.setClipboardListener(text -> { + DeviceMessage msg = DeviceMessage.createClipboard(text); + controller.getSender().send(msg); + }); asyncProcessors.add(controller); } From 87da68ee0d74831a2b44230c573a3b315c8fd7d3 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:32:30 +0800 Subject: [PATCH 1112/1133] Handle UHID output Use UHID output reports to synchronize CapsLock and VerrNum states. PR #4473 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/meson.build | 1 + app/src/controller.c | 6 +- app/src/controller.h | 5 +- app/src/device_msg.c | 37 +++++- app/src/device_msg.h | 6 + app/src/receiver.c | 38 ++++++ app/src/receiver.h | 2 + app/src/scrcpy.c | 9 +- app/src/uhid/keyboard_uhid.c | 109 ++++++++++++++++-- app/src/uhid/keyboard_uhid.h | 6 +- app/src/uhid/uhid_output.c | 25 ++++ app/src/uhid/uhid_output.h | 45 ++++++++ app/tests/test_device_msg_deserialize.c | 23 ++++ .../com/genymobile/scrcpy/Controller.java | 2 +- .../com/genymobile/scrcpy/DeviceMessage.java | 19 +++ .../scrcpy/DeviceMessageWriter.java | 7 ++ .../com/genymobile/scrcpy/UhidManager.java | 80 +++++++++++++ .../scrcpy/DeviceMessageWriterTest.java | 23 ++++ 18 files changed, 423 insertions(+), 20 deletions(-) create mode 100644 app/src/uhid/uhid_output.c create mode 100644 app/src/uhid/uhid_output.h diff --git a/app/meson.build b/app/meson.build index 9a2d2838..3695e0f9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -36,6 +36,7 @@ src = [ 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/uhid/keyboard_uhid.c', + 'src/uhid/uhid_output.c', 'src/util/acksync.c', 'src/util/audiobuf.c', 'src/util/average.c', diff --git a/app/src/controller.c b/app/src/controller.c index 5a5bfde9..499cfd3c 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -43,9 +43,11 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { } void -sc_controller_set_acksync(struct sc_controller *controller, - struct sc_acksync *acksync) { +sc_controller_configure(struct sc_controller *controller, + struct sc_acksync *acksync, + struct sc_uhid_devices *uhid_devices) { controller->receiver.acksync = acksync; + controller->receiver.uhid_devices = uhid_devices; } void diff --git a/app/src/controller.h b/app/src/controller.h index 767e1731..1e44427e 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -28,8 +28,9 @@ bool sc_controller_init(struct sc_controller *controller, sc_socket control_socket); void -sc_controller_set_acksync(struct sc_controller *controller, - struct sc_acksync *acksync); +sc_controller_configure(struct sc_controller *controller, + struct sc_acksync *acksync, + struct sc_uhid_devices *uhid_devices); void sc_controller_destroy(struct sc_controller *controller); diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 0cadc49c..7621c040 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -46,6 +46,31 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len, msg->ack_clipboard.sequence = sequence; return 9; } + case DEVICE_MSG_TYPE_UHID_OUTPUT: { + if (len < 5) { + // at least id + size + return 0; // not available + } + uint16_t id = sc_read16be(&buf[1]); + size_t size = sc_read16be(&buf[3]); + if (size < len - 5) { + return 0; // not available + } + uint8_t *data = malloc(size); + if (!data) { + LOG_OOM(); + return -1; + } + if (size) { + memcpy(data, &buf[5], size); + } + + msg->uhid_output.id = id; + msg->uhid_output.size = size; + msg->uhid_output.data = data; + + return 5 + size; + } default: LOGW("Unknown device message type: %d", (int) msg->type); return -1; // error, we cannot recover @@ -54,7 +79,15 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len, void sc_device_msg_destroy(struct sc_device_msg *msg) { - if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { - free(msg->clipboard.text); + switch (msg->type) { + case DEVICE_MSG_TYPE_CLIPBOARD: + free(msg->clipboard.text); + break; + case DEVICE_MSG_TYPE_UHID_OUTPUT: + free(msg->uhid_output.data); + break; + default: + // nothing to do + break; } } diff --git a/app/src/device_msg.h b/app/src/device_msg.h index 3f541cf5..86b2ccb7 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -14,6 +14,7 @@ enum sc_device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_ACK_CLIPBOARD, + DEVICE_MSG_TYPE_UHID_OUTPUT, }; struct sc_device_msg { @@ -25,6 +26,11 @@ struct sc_device_msg { struct { uint64_t sequence; } ack_clipboard; + struct { + uint16_t id; + uint16_t size; + uint8_t *data; // owned, to be freed by free() + } uhid_output; }; }; diff --git a/app/src/receiver.c b/app/src/receiver.c index 97299b3f..f4ebd3f8 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -1,11 +1,13 @@ #include "receiver.h" #include +#include #include #include #include "device_msg.h" #include "util/log.h" +#include "util/str.h" bool sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { @@ -16,6 +18,7 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { receiver->control_socket = control_socket; receiver->acksync = NULL; + receiver->uhid_devices = NULL; return true; } @@ -57,6 +60,41 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) { sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence); break; + case DEVICE_MSG_TYPE_UHID_OUTPUT: + if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { + char *hex = sc_str_to_hex_string(msg->uhid_output.data, + msg->uhid_output.size); + if (hex) { + LOGV("UHID output [%" PRIu16 "] %s", + msg->uhid_output.id, hex); + free(hex); + } else { + LOGV("UHID output [%" PRIu16 "] size=%" PRIu16, + msg->uhid_output.id, msg->uhid_output.size); + } + } + + // This is a programming error to receive this message if there is + // no uhid_devices instance + assert(receiver->uhid_devices); + + // Also check at runtime (do not trust the server) + if (!receiver->uhid_devices) { + LOGE("Received unexpected HID output message"); + return; + } + + struct sc_uhid_receiver *uhid_receiver = + sc_uhid_devices_get_receiver(receiver->uhid_devices, + msg->uhid_output.id); + if (uhid_receiver) { + uhid_receiver->ops->process_output(uhid_receiver, + msg->uhid_output.data, + msg->uhid_output.size); + } else { + LOGW("No UHID receiver for id %" PRIu16, msg->uhid_output.id); + } + break; } } diff --git a/app/src/receiver.h b/app/src/receiver.h index 43f89615..ba84c0ab 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -5,6 +5,7 @@ #include +#include "uhid/uhid_output.h" #include "util/acksync.h" #include "util/net.h" #include "util/thread.h" @@ -17,6 +18,7 @@ struct sc_receiver { sc_mutex mutex; struct sc_acksync *acksync; + struct sc_uhid_devices *uhid_devices; }; bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d01d3619..7b1a6d5c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -62,6 +62,7 @@ struct scrcpy { struct sc_aoa aoa; // sequence/ack helper to synchronize clipboard and Ctrl+v via HID struct sc_acksync acksync; + struct sc_uhid_devices uhid_devices; #endif union { struct sc_keyboard_sdk keyboard_sdk; @@ -342,6 +343,7 @@ scrcpy(struct scrcpy_options *options) { bool timeout_started = false; struct sc_acksync *acksync = NULL; + struct sc_uhid_devices *uhid_devices = NULL; uint32_t scid = scrcpy_generate_scid(); @@ -666,10 +668,13 @@ aoa_hid_end: kp = &s->keyboard_sdk.key_processor; } else if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_UHID) { - bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller); + sc_uhid_devices_init(&s->uhid_devices); + bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller, + &s->uhid_devices); if (!ok) { goto end; } + uhid_devices = &s->uhid_devices; kp = &s->keyboard_uhid.key_processor; } @@ -679,7 +684,7 @@ aoa_hid_end: mp = &s->mouse_sdk.mouse_processor; } - sc_controller_set_acksync(&s->controller, acksync); + sc_controller_configure(&s->controller, acksync, uhid_devices); if (!sc_controller_start(&s->controller)) { goto end; diff --git a/app/src/uhid/keyboard_uhid.c b/app/src/uhid/keyboard_uhid.c index d974d578..f537bc29 100644 --- a/app/src/uhid/keyboard_uhid.c +++ b/app/src/uhid/keyboard_uhid.c @@ -5,8 +5,52 @@ /** Downcast key processor to keyboard_uhid */ #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor) +/** Downcast uhid_receiver to keyboard_uhid */ +#define DOWNCAST_RECEIVER(UR) \ + container_of(UR, struct sc_keyboard_uhid, uhid_receiver) + #define UHID_KEYBOARD_ID 1 +static void +sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb, + const struct sc_hid_event *event) { + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; + msg.uhid_input.id = UHID_KEYBOARD_ID; + + assert(event->size <= SC_HID_MAX_SIZE); + memcpy(msg.uhid_input.data, event->data, event->size); + msg.uhid_input.size = event->size; + + if (!sc_controller_push_msg(kb->controller, &msg)) { + LOGE("Could not send UHID_INPUT message (key)"); + } +} + +static void +sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) { + SDL_Keymod sdl_mod = SDL_GetModState(); + uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM); + + uint16_t device_mod = + atomic_load_explicit(&kb->device_mod, memory_order_relaxed); + uint16_t diff = mod ^ device_mod; + + if (diff) { + // Inherently racy (the HID output reports arrive asynchronously in + // response to key presses), but will re-synchronize on next key press + // or HID output anyway + atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed); + + struct sc_hid_event hid_event; + sc_hid_keyboard_event_from_mods(&hid_event, diff); + + LOGV("HID keyboard state synchronized"); + + sc_keyboard_uhid_send_input(kb, &hid_event); + } +} + static void sc_key_processor_process_key(struct sc_key_processor *kp, const struct sc_key_event *event, @@ -25,26 +69,63 @@ sc_key_processor_process_key(struct sc_key_processor *kp, // Not all keys are supported, just ignore unsupported keys if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) { - struct sc_control_msg msg; - msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; - msg.uhid_input.id = UHID_KEYBOARD_ID; + if (event->scancode == SC_SCANCODE_CAPSLOCK) { + atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS, + memory_order_relaxed); + } else if (event->scancode == SC_SCANCODE_NUMLOCK) { + atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM, + memory_order_relaxed); + } else { + // Synchronize modifiers (only if the scancode itself does not + // change the modifiers) + sc_keyboard_uhid_synchronize_mod(kb); + } + sc_keyboard_uhid_send_input(kb, &hid_event); + } +} - assert(hid_event.size <= SC_HID_MAX_SIZE); - memcpy(msg.uhid_input.data, hid_event.data, hid_event.size); - msg.uhid_input.size = hid_event.size; +static unsigned +sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) { + // + // (chapter 11: LED page) + unsigned mod = 0; + if (hid_led & 0x01) { + mod |= SC_MOD_NUM; + } + if (hid_led & 0x02) { + mod |= SC_MOD_CAPS; + } + return mod; +} - if (!sc_controller_push_msg(kb->controller, &msg)) { - LOGE("Could not send UHID_INPUT message (key)"); - } +static void +sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver, + const uint8_t *data, size_t len) { + // Called from the thread receiving device messages + + assert(len); + + // Also check at runtime (do not trust the server) + if (!len) { + LOGE("Unexpected empty HID output message"); + return; } + + struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver); + + uint8_t hid_led = data[0]; + uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led); + atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed); } bool sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, - struct sc_controller *controller) { + struct sc_controller *controller, + struct sc_uhid_devices *uhid_devices) { sc_hid_keyboard_init(&kb->hid); kb->controller = controller; + atomic_init(&kb->device_mod, 0); static const struct sc_key_processor_ops ops = { .process_key = sc_key_processor_process_key, @@ -58,6 +139,14 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, kb->key_processor.async_paste = false; kb->key_processor.ops = &ops; + static const struct sc_uhid_receiver_ops uhid_receiver_ops = { + .process_output = sc_uhid_receiver_process_output, + }; + + kb->uhid_receiver.id = UHID_KEYBOARD_ID; + kb->uhid_receiver.ops = &uhid_receiver_ops; + sc_uhid_devices_add_receiver(uhid_devices, &kb->uhid_receiver); + struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.uhid_create.id = UHID_KEYBOARD_ID; diff --git a/app/src/uhid/keyboard_uhid.h b/app/src/uhid/keyboard_uhid.h index 854ba008..5e1be70c 100644 --- a/app/src/uhid/keyboard_uhid.h +++ b/app/src/uhid/keyboard_uhid.h @@ -7,17 +7,21 @@ #include "controller.h" #include "hid/hid_keyboard.h" +#include "uhid/uhid_output.h" #include "trait/key_processor.h" struct sc_keyboard_uhid { struct sc_key_processor key_processor; // key processor trait + struct sc_uhid_receiver uhid_receiver; struct sc_hid_keyboard hid; struct sc_controller *controller; + atomic_uint_least16_t device_mod; }; bool sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, - struct sc_controller *controller); + struct sc_controller *controller, + struct sc_uhid_devices *uhid_devices); #endif diff --git a/app/src/uhid/uhid_output.c b/app/src/uhid/uhid_output.c new file mode 100644 index 00000000..3b095faf --- /dev/null +++ b/app/src/uhid/uhid_output.c @@ -0,0 +1,25 @@ +#include "uhid_output.h" + +#include + +void +sc_uhid_devices_init(struct sc_uhid_devices *devices) { + devices->count = 0; +} + +void +sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices, + struct sc_uhid_receiver *receiver) { + assert(devices->count < SC_UHID_MAX_RECEIVERS); + devices->receivers[devices->count++] = receiver; +} + +struct sc_uhid_receiver * +sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) { + for (size_t i = 0; i < devices->count; ++i) { + if (devices->receivers[i]->id == id) { + return devices->receivers[i]; + } + } + return NULL; +} diff --git a/app/src/uhid/uhid_output.h b/app/src/uhid/uhid_output.h new file mode 100644 index 00000000..e13eed87 --- /dev/null +++ b/app/src/uhid/uhid_output.h @@ -0,0 +1,45 @@ +#ifndef SC_UHID_OUTPUT_H +#define SC_UHID_OUTPUT_H + +#include "common.h" + +#include +#include + +/** + * The communication with UHID devices is bidirectional. + * + * This component manages the registration of receivers to handle UHID output + * messages (sent from the device to the computer). + */ + +struct sc_uhid_receiver { + uint16_t id; + + const struct sc_uhid_receiver_ops *ops; +}; + +struct sc_uhid_receiver_ops { + void + (*process_output)(struct sc_uhid_receiver *receiver, + const uint8_t *data, size_t len); +}; + +#define SC_UHID_MAX_RECEIVERS 1 + +struct sc_uhid_devices { + struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS]; + unsigned count; +}; + +void +sc_uhid_devices_init(struct sc_uhid_devices *devices); + +void +sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices, + struct sc_uhid_receiver *receiver); + +struct sc_uhid_receiver * +sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id); + +#endif diff --git a/app/tests/test_device_msg_deserialize.c b/app/tests/test_device_msg_deserialize.c index bfbcefd6..a64a3eb7 100644 --- a/app/tests/test_device_msg_deserialize.c +++ b/app/tests/test_device_msg_deserialize.c @@ -61,6 +61,28 @@ static void test_deserialize_ack_set_clipboard(void) { assert(msg.ack_clipboard.sequence == UINT64_C(0x0102030405060708)); } +static void test_deserialize_uhid_output(void) { + const uint8_t input[] = { + DEVICE_MSG_TYPE_UHID_OUTPUT, + 0, 42, // id + 0, 5, // size + 0x01, 0x02, 0x03, 0x04, 0x05, // data + }; + + struct sc_device_msg msg; + ssize_t r = sc_device_msg_deserialize(input, sizeof(input), &msg); + assert(r == 10); + + assert(msg.type == DEVICE_MSG_TYPE_UHID_OUTPUT); + assert(msg.uhid_output.id == 42); + assert(msg.uhid_output.size == 5); + + uint8_t expected[] = {1, 2, 3, 4, 5}; + assert(!memcmp(msg.uhid_output.data, expected, sizeof(expected))); + + sc_device_msg_destroy(&msg); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -68,5 +90,6 @@ int main(int argc, char *argv[]) { test_deserialize_clipboard(); test_deserialize_clipboard_big(); test_deserialize_ack_set_clipboard(); + test_deserialize_uhid_output(); return 0; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index b925dd80..5ba0c577 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -52,7 +52,7 @@ public class Controller implements AsyncProcessor { this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(controlChannel); - uhidManager = new UhidManager(); + uhidManager = new UhidManager(sender); } private void initPointers() { diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java index 2e333e3f..a8987eb6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -4,10 +4,13 @@ public final class DeviceMessage { public static final int TYPE_CLIPBOARD = 0; public static final int TYPE_ACK_CLIPBOARD = 1; + public static final int TYPE_UHID_OUTPUT = 2; private int type; private String text; private long sequence; + private int id; + private byte[] data; private DeviceMessage() { } @@ -26,6 +29,14 @@ public final class DeviceMessage { return event; } + public static DeviceMessage createUhidOutput(int id, byte[] data) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_UHID_OUTPUT; + event.id = id; + event.data = data; + return event; + } + public int getType() { return type; } @@ -37,4 +48,12 @@ public final class DeviceMessage { public long getSequence() { return sequence; } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index bcd8d206..f5d57c98 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -29,6 +29,13 @@ public class DeviceMessageWriter { buffer.putLong(msg.getSequence()); output.write(rawBuffer, 0, buffer.position()); break; + case DeviceMessage.TYPE_UHID_OUTPUT: + buffer.putShort((short) msg.getId()); + byte[] data = msg.getData(); + buffer.putShort((short) data.length); + buffer.put(data); + output.write(rawBuffer, 0, buffer.position()); + break; default: Ln.w("Unknown device message: " + msg.getType()); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/UhidManager.java b/server/src/main/java/com/genymobile/scrcpy/UhidManager.java index 96458bf0..a39288a5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/UhidManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/UhidManager.java @@ -1,5 +1,8 @@ package com.genymobile.scrcpy; +import android.os.Build; +import android.os.HandlerThread; +import android.os.MessageQueue; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -7,6 +10,7 @@ import android.util.ArrayMap; import java.io.FileDescriptor; import java.io.IOException; +import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -14,13 +18,31 @@ import java.nio.charset.StandardCharsets; public final class UhidManager { // Linux: include/uapi/linux/uhid.h + private static final int UHID_OUTPUT = 6; private static final int UHID_CREATE2 = 11; private static final int UHID_INPUT2 = 12; // Linux: include/uapi/linux/input.h private static final short BUS_VIRTUAL = 0x06; + private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event) + private final ArrayMap fds = new ArrayMap<>(); + private final ByteBuffer buffer = ByteBuffer.allocate(SIZE_OF_UHID_EVENT).order(ByteOrder.nativeOrder()); + + private final DeviceMessageSender sender; + private final HandlerThread thread = new HandlerThread("UHidManager"); + private final MessageQueue queue; + + public UhidManager(DeviceMessageSender sender) { + this.sender = sender; + thread.start(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + queue = thread.getLooper().getQueue(); + } else { + queue = null; + } + } public void open(int id, byte[] reportDesc) throws IOException { try { @@ -34,6 +56,8 @@ public final class UhidManager { byte[] req = buildUhidCreate2Req(reportDesc); Os.write(fd, req, 0, req.length); + + registerUhidListener(id, fd); } catch (Exception e) { close(fd); throw e; @@ -43,6 +67,62 @@ public final class UhidManager { } } + private void registerUhidListener(int id, FileDescriptor fd) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT, (fd2, events) -> { + try { + buffer.clear(); + int r = Os.read(fd2, buffer); + buffer.flip(); + if (r > 0) { + int type = buffer.getInt(); + if (type == UHID_OUTPUT) { + byte[] data = extractHidOutputData(buffer); + if (data != null) { + DeviceMessage msg = DeviceMessage.createUhidOutput(id, data); + sender.send(msg); + } + } + } + } catch (ErrnoException | InterruptedIOException e) { + Ln.e("Failed to read UHID output", e); + return 0; + } + return events; + }); + } + } + + private static byte[] extractHidOutputData(ByteBuffer buffer) { + /* + * #define UHID_DATA_MAX 4096 + * struct uhid_event { + * uint32_t type; + * union { + * // ... + * struct uhid_output_req { + * __u8 data[UHID_DATA_MAX]; + * __u16 size; + * __u8 rtype; + * }; + * }; + * } __attribute__((__packed__)); + */ + + if (buffer.remaining() < 4099) { + Ln.w("Incomplete HID output"); + return null; + } + int size = buffer.getShort(buffer.position() + 4096) & 0xFFFF; + if (size > 4096) { + Ln.w("Incorrect HID output size: " + size); + return null; + } + byte[] data = new byte[size]; + buffer.get(data); + return data; + } + public void writeInput(int id, byte[] data) throws IOException { FileDescriptor fd = fds.get(id); if (fd == null) { diff --git a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java index 7b917d33..d7f926ba 100644 --- a/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/DeviceMessageWriterTest.java @@ -52,4 +52,27 @@ public class DeviceMessageWriterTest { Assert.assertArrayEquals(expected, actual); } + + @Test + public void testSerializeUhidOutput() throws IOException { + DeviceMessageWriter writer = new DeviceMessageWriter(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(DeviceMessage.TYPE_UHID_OUTPUT); + dos.writeShort(42); // id + byte[] data = {1, 2, 3, 4, 5}; + dos.writeShort(data.length); + dos.write(data); + + byte[] expected = bos.toByteArray(); + + DeviceMessage msg = DeviceMessage.createUhidOutput(42, data); + bos = new ByteArrayOutputStream(); + writer.writeTo(msg, bos); + + byte[] actual = bos.toByteArray(); + + Assert.assertArrayEquals(expected, actual); + } } From f557188dc835e0a1b108d56b30641510901ecf13 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 24 Feb 2024 22:38:32 +0100 Subject: [PATCH 1113/1133] Create UhidManager only on first use There is no need to create a UhidManager instance (with its thread) if no UHID is used. PR #4473 --- .../java/com/genymobile/scrcpy/Controller.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 5ba0c577..fd320d3f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -26,7 +26,7 @@ public class Controller implements AsyncProcessor { private Thread thread; - private final UhidManager uhidManager; + private UhidManager uhidManager; private final Device device; private final ControlChannel controlChannel; @@ -52,7 +52,13 @@ public class Controller implements AsyncProcessor { this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(controlChannel); - uhidManager = new UhidManager(sender); + } + + private UhidManager getUhidManager() { + if (uhidManager == null) { + uhidManager = new UhidManager(sender); + } + return uhidManager; } private void initPointers() { @@ -99,7 +105,9 @@ public class Controller implements AsyncProcessor { Ln.e("Controller error", e); } finally { Ln.d("Controller stopped"); - uhidManager.closeAll(); + if (uhidManager != null) { + uhidManager.closeAll(); + } listener.onTerminated(true); } }, "control-recv"); @@ -195,10 +203,10 @@ public class Controller implements AsyncProcessor { device.rotateDevice(); break; case ControlMessage.TYPE_UHID_CREATE: - uhidManager.open(msg.getId(), msg.getData()); + getUhidManager().open(msg.getId(), msg.getData()); break; case ControlMessage.TYPE_UHID_INPUT: - uhidManager.writeInput(msg.getId(), msg.getData()); + getUhidManager().writeInput(msg.getId(), msg.getData()); break; default: // do nothing From 54dede36307edc69553d7de620f6b4318e48c678 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 26 Feb 2024 20:22:34 +0100 Subject: [PATCH 1114/1133] Fix startActivity() for supporting API < 30 Call the older startActivityAsUser() instead of startActivityAsUserWithFeature() so that it also works on older Android versions. Fixes #4704 PR #4473 --- .../com/genymobile/scrcpy/AudioCapture.java | 2 +- .../scrcpy/wrappers/ActivityManager.java | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java index 45634c70..3934ad49 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioCapture.java @@ -79,7 +79,7 @@ public final class AudioCapture { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity")); - ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); + ServiceManager.getActivityManager().startActivity(intent); } private static void stopWorkaroundAndroid11() { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 367ea2e7..d4bee165 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -22,7 +22,7 @@ public final class ActivityManager { private Method getContentProviderExternalMethod; private boolean getContentProviderExternalMethodNewVersion = true; private Method removeContentProviderExternalMethod; - private Method startActivityAsUserWithFeatureMethod; + private Method startActivityAsUserMethod; private Method forceStopPackageMethod; static ActivityManager create() { @@ -107,26 +107,25 @@ public final class ActivityManager { return getContentProviderExternal("settings", new Binder()); } - private Method getStartActivityAsUserWithFeatureMethod() throws NoSuchMethodException, ClassNotFoundException { - if (startActivityAsUserWithFeatureMethod == null) { + private Method getStartActivityAsUserMethod() throws NoSuchMethodException, ClassNotFoundException { + if (startActivityAsUserMethod == null) { Class iApplicationThreadClass = Class.forName("android.app.IApplicationThread"); Class profilerInfo = Class.forName("android.app.ProfilerInfo"); - startActivityAsUserWithFeatureMethod = manager.getClass() - .getMethod("startActivityAsUserWithFeature", iApplicationThreadClass, String.class, String.class, Intent.class, String.class, - IBinder.class, String.class, int.class, int.class, profilerInfo, Bundle.class, int.class); + startActivityAsUserMethod = manager.getClass() + .getMethod("startActivityAsUser", iApplicationThreadClass, String.class, Intent.class, String.class, IBinder.class, String.class, + int.class, int.class, profilerInfo, Bundle.class, int.class); } - return startActivityAsUserWithFeatureMethod; + return startActivityAsUserMethod; } @SuppressWarnings("ConstantConditions") - public int startActivityAsUserWithFeature(Intent intent) { + public int startActivity(Intent intent) { try { - Method method = getStartActivityAsUserWithFeatureMethod(); + Method method = getStartActivityAsUserMethod(); return (int) method.invoke( /* this */ manager, /* caller */ null, /* callingPackage */ FakeContext.PACKAGE_NAME, - /* callingFeatureId */ null, /* intent */ intent, /* resolvedType */ null, /* resultTo */ null, From 151a6225d44f795bcc6066e8af9ccc65fefbaded Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 24 Feb 2024 22:30:30 +0100 Subject: [PATCH 1115/1133] Add shortcut to open keyboard settings The keyboard settings can be opened by: adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS Add a shortcut (MOD+k) for convenience if the current keyboard is HID. PR #4473 --- app/scrcpy.1 | 6 +++++- app/src/cli.c | 9 +++++++-- app/src/control_msg.c | 4 ++++ app/src/control_msg.h | 1 + app/src/input_manager.c | 19 +++++++++++++++++++ app/src/keyboard_sdk.c | 1 + app/src/trait/key_processor.h | 7 +++++++ app/src/uhid/keyboard_uhid.c | 1 + app/src/usb/keyboard_aoa.c | 1 + app/tests/test_control_msg_serialize.c | 16 ++++++++++++++++ doc/shortcuts.md | 1 + .../com/genymobile/scrcpy/ControlMessage.java | 1 + .../scrcpy/ControlMessageReader.java | 1 + .../com/genymobile/scrcpy/Controller.java | 10 ++++++++++ .../scrcpy/ControlMessageReaderTest.java | 16 ++++++++++++++++ 15 files changed, 91 insertions(+), 3 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1dfcab2b..7e856664 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -182,7 +182,7 @@ Possible values are "disabled", "sdk", "uhid" and "aoa": - "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device. - "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB. -For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: +For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly using the shortcut MOD+k (except in OTG mode), or by executing: adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS @@ -644,6 +644,10 @@ Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= .B MOD+Shift+v Inject computer clipboard text as a sequence of key events +.TP +.B MOD+k +Open keyboard settings on the device (for HID keyboard only) + .TP .B MOD+i Enable/disable FPS counter (print frames/second in logs) diff --git a/app/src/cli.c b/app/src/cli.c index 59cd5699..c1c68e92 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -377,8 +377,9 @@ static const struct sc_option options[] = { "For \"uhid\" and \"aoa\", the keyboard layout must be " "configured (once and for all) on the device, via Settings -> " "System -> Languages and input -> Physical keyboard. This " - "settings page can be started directly: `adb shell am start -a " - "android.settings.HARD_KEYBOARD_SETTINGS`.\n" + "settings page can be started directly using the shortcut " + "MOD+k (except in OTG mode) or by executing: `adb shell am " + "start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n" "This option is only available when a HID keyboard is enabled " "(or a physical keyboard is connected).\n" "Also see --mouse.", @@ -965,6 +966,10 @@ static const struct sc_shortcut shortcuts[] = { .shortcuts = { "MOD+Shift+v" }, .text = "Inject computer clipboard text as a sequence of key events", }, + { + .shortcuts = { "MOD+k" }, + .text = "Open keyboard settings on the device (for HID keyboard only)", + }, { .shortcuts = { "MOD+i" }, .text = "Enable/disable FPS counter (print frames/second in logs)", diff --git a/app/src/control_msg.c b/app/src/control_msg.c index 88575b4e..b3da5fe5 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -161,6 +161,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: + case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: // no additional data return 1; default: @@ -270,6 +271,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) { } break; } + case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + LOG_CMSG("open hard keyboard settings"); + break; default: LOG_CMSG("unknown type: %u", (unsigned) msg->type); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 550168c2..cd1340ef 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -40,6 +40,7 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_INPUT, + SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, }; enum sc_screen_power_mode { diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7186186f..f26c4164 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -318,6 +318,18 @@ rotate_device(struct sc_input_manager *im) { } } +static void +open_hard_keyboard_settings(struct sc_input_manager *im) { + assert(im->controller); + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS; + + if (!sc_controller_push_msg(im->controller, &msg)) { + LOGW("Could not request opening hard keyboard settings"); + } +} + static void apply_orientation_transform(struct sc_input_manager *im, enum sc_orientation transform) { @@ -550,6 +562,13 @@ sc_input_manager_process_key(struct sc_input_manager *im, rotate_device(im); } return; + case SDLK_k: + if (control && !shift && !repeat && down + && im->kp && im->kp->hid) { + // Only if the current keyboard is hid + open_hard_keyboard_settings(im); + } + return; } return; diff --git a/app/src/keyboard_sdk.c b/app/src/keyboard_sdk.c index 726f65a9..00b7f92a 100644 --- a/app/src/keyboard_sdk.c +++ b/app/src/keyboard_sdk.c @@ -340,5 +340,6 @@ sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb, // Key injection and clipboard synchronization are serialized kb->key_processor.async_paste = false; + kb->key_processor.hid = false; kb->key_processor.ops = &ops; } diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index 8c51b11d..96374413 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -23,6 +23,13 @@ struct sc_key_processor { */ bool async_paste; + /** + * Set by the implementation to indicate that the keyboard is HID. In + * practice, it is used to react on a shortcut to open the hard keyboard + * settings only if the keyboard is HID. + */ + bool hid; + const struct sc_key_processor_ops *ops; }; diff --git a/app/src/uhid/keyboard_uhid.c b/app/src/uhid/keyboard_uhid.c index f537bc29..515a3fd9 100644 --- a/app/src/uhid/keyboard_uhid.c +++ b/app/src/uhid/keyboard_uhid.c @@ -137,6 +137,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, // Clipboard synchronization is requested over the same control socket, so // there is no need for a specific synchronization mechanism kb->key_processor.async_paste = false; + kb->key_processor.hid = true; kb->key_processor.ops = &ops; static const struct sc_uhid_receiver_ops uhid_receiver_ops = { diff --git a/app/src/usb/keyboard_aoa.c b/app/src/usb/keyboard_aoa.c index b69d6cd8..736c97b0 100644 --- a/app/src/usb/keyboard_aoa.c +++ b/app/src/usb/keyboard_aoa.c @@ -94,6 +94,7 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) { // events are sent over AOA, so it must wait for clipboard synchronization // to be acknowledged by the device before injecting Ctrl+v. kb->key_processor.async_paste = true; + kb->key_processor.hid = true; kb->key_processor.ops = &ops; return true; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 0ab61153..7a978f2b 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -370,6 +370,21 @@ static void test_serialize_uhid_input(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_open_hard_keyboard(void) { + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 1); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -390,5 +405,6 @@ int main(int argc, char *argv[]) { test_serialize_rotate_device(); test_serialize_uhid_create(); test_serialize_uhid_input(); + test_serialize_open_hard_keyboard(); return 0; } diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 21bccbd9..8c402855 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -48,6 +48,7 @@ _[Super] is typically the Windows or Cmd key._ | Cut to clipboard⁵ | MOD+x | Synchronize clipboards and paste⁵ | MOD+v | Inject computer clipboard text | MOD+Shift+v + | Open keyboard settings (HID keyboard only) | MOD+k | Enable/disable FPS counter (on stdout) | MOD+i | Pinch-to-zoom/rotate | Ctrl+_click-and-move_ | Tilt (slide vertically with 2 fingers) | Shift+_click-and-move_ diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 74bf5610..bcbacb4b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -19,6 +19,7 @@ public final class ControlMessage { public static final int TYPE_ROTATE_DEVICE = 11; public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_INPUT = 13; + public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14; public static final long SEQUENCE_INVALID = 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 24aa73c0..1761d228 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -86,6 +86,7 @@ public class ControlMessageReader { case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_ROTATE_DEVICE: + case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: msg = ControlMessage.createEmpty(type); break; case ControlMessage.TYPE_UHID_CREATE: diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index fd320d3f..87faf8ba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -1,7 +1,9 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.InputManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.content.Intent; import android.os.Build; import android.os.SystemClock; import android.view.InputDevice; @@ -208,6 +210,9 @@ public class Controller implements AsyncProcessor { case ControlMessage.TYPE_UHID_INPUT: getUhidManager().writeInput(msg.getId(), msg.getData()); break; + case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + openHardKeyboardSettings(); + break; default: // do nothing } @@ -446,4 +451,9 @@ public class Controller implements AsyncProcessor { return ok; } + + private void openHardKeyboardSettings() { + Intent intent = new Intent("android.settings.HARD_KEYBOARD_SETTINGS"); + ServiceManager.getActivityManager().startActivity(intent); + } } diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 7cc67c3e..0c8086f7 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -366,6 +366,22 @@ public class ControlMessageReaderTest { Assert.assertArrayEquals(data, event.getData()); } + @Test + public void testParseOpenHardKeyboardSettings() throws IOException { + ControlMessageReader reader = new ControlMessageReader(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS); + + byte[] packet = bos.toByteArray(); + + reader.readFrom(new ByteArrayInputStream(packet)); + ControlMessage event = reader.next(); + + Assert.assertEquals(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS, event.getType()); + } + @Test public void testMultiEvents() throws IOException { ControlMessageReader reader = new ControlMessageReader(); From 6a103c809f4a208c25d7fd5d019bc6bc5b3046b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 25 Feb 2024 15:43:36 +0100 Subject: [PATCH 1116/1133] Add UHID mouse support Use the following command: scrcpy --mouse=uhid PR #4473 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/meson.build | 1 + app/scrcpy.1 | 5 +- app/src/cli.c | 16 ++++-- app/src/options.h | 1 + app/src/scrcpy.c | 8 +++ app/src/uhid/mouse_uhid.c | 89 +++++++++++++++++++++++++++++++++ app/src/uhid/mouse_uhid.h | 19 +++++++ 9 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 app/src/uhid/mouse_uhid.c create mode 100644 app/src/uhid/mouse_uhid.h diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 904ccdeb..8cc0b157 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -120,7 +120,7 @@ _scrcpy() { return ;; --mouse) - COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur")) return ;; --orientation|--display-orientation) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index f81d2b22..1cf2ae41 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -44,7 +44,7 @@ arguments=( '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' {-m,--max-size=}'[Limit both the width and height of the video to value]' '--max-fps=[Limit the frame rate of screen capture]' - '--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)' + '--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' diff --git a/app/meson.build b/app/meson.build index 3695e0f9..b0a6aadb 100644 --- a/app/meson.build +++ b/app/meson.build @@ -36,6 +36,7 @@ src = [ 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/uhid/keyboard_uhid.c', + 'src/uhid/mouse_uhid.c', 'src/uhid/uhid_output.c', 'src/util/acksync.c', 'src/util/audiobuf.c', diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7e856664..8c0c4cc6 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -240,13 +240,14 @@ Limit the framerate of screen capture (officially supported since Android 10, bu .BI "\-\-mouse " mode Select how to send mouse inputs to the device. -Possible values are "disabled", "sdk" and "aoa": +Possible values are "disabled", "sdk", "uhid" and "aoa": - "disabled" does not send mouse inputs to the device. - "sdk" uses the Android system API to deliver mouse events to applications. + - "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device. - "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB. -In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode). +In "uhid" and "aoa" modes, the computer mouse is captured to control the device directly (relative mouse mode). LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. diff --git a/app/src/cli.c b/app/src/cli.c index c1c68e92..dceb8fff 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -462,14 +462,17 @@ static const struct sc_option options[] = { .longopt = "mouse", .argdesc = "mode", .text = "Select how to send mouse inputs to the device.\n" - "Possible values are \"disabled\", \"sdk\" and \"aoa\".\n" + "Possible values are \"disabled\", \"sdk\", \"uhid\" and " + "\"aoa\".\n" "\"disabled\" does not send mouse inputs to the device.\n" "\"sdk\" uses the Android system API to deliver mouse events" "to applications.\n" + "\"uhid\" simulates a physical HID mouse using the Linux UHID " + "kernel module on the device.\n" "\"aoa\" simulates a physical mouse using the AOAv2 protocol. " "It may only work over USB.\n" - "In \"aoa\" mode, the computer mouse is captured to control " - "the device directly (relative mouse mode).\n" + "In \"uhid\" and \"aoa\" modes, the computer mouse is captured " + "to control the device directly (relative mouse mode).\n" "LAlt, LSuper or RSuper toggle the capture mode, to give " "control of the mouse back to the computer.\n" "Also see --keyboard.", @@ -1979,6 +1982,11 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { return true; } + if (!strcmp(optarg, "uhid")) { + *mode = SC_MOUSE_INPUT_MODE_UHID; + return true; + } + if (!strcmp(optarg, "aoa")) { #ifdef HAVE_USB *mode = SC_MOUSE_INPUT_MODE_AOA; @@ -1989,7 +1997,7 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { #endif } - LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg); + LOGE("Unsupported mouse: %s (expected disabled, sdk, uhid or aoa)", optarg); return false; } diff --git a/app/src/options.h b/app/src/options.h index 6d62fac0..5445e7c8 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -151,6 +151,7 @@ enum sc_mouse_input_mode { SC_MOUSE_INPUT_MODE_AUTO, SC_MOUSE_INPUT_MODE_DISABLED, SC_MOUSE_INPUT_MODE_SDK, + SC_MOUSE_INPUT_MODE_UHID, SC_MOUSE_INPUT_MODE_AOA, }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7b1a6d5c..a40a4dec 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -26,6 +26,7 @@ #include "screen.h" #include "server.h" #include "uhid/keyboard_uhid.h" +#include "uhid/mouse_uhid.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/keyboard_aoa.h" @@ -73,6 +74,7 @@ struct scrcpy { }; union { struct sc_mouse_sdk mouse_sdk; + struct sc_mouse_uhid mouse_uhid; #ifdef HAVE_USB struct sc_mouse_aoa mouse_aoa; #endif @@ -682,6 +684,12 @@ aoa_hid_end: if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); mp = &s->mouse_sdk.mouse_processor; + } else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) { + bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller); + if (!ok) { + goto end; + } + mp = &s->mouse_uhid.mouse_processor; } sc_controller_configure(&s->controller, acksync, uhid_devices); diff --git a/app/src/uhid/mouse_uhid.c b/app/src/uhid/mouse_uhid.c new file mode 100644 index 00000000..77446f9e --- /dev/null +++ b/app/src/uhid/mouse_uhid.c @@ -0,0 +1,89 @@ +#include "mouse_uhid.h" + +#include "hid/hid_mouse.h" +#include "input_events.h" +#include "util/log.h" + +/** Downcast mouse processor to mouse_uhid */ +#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor) + +#define UHID_MOUSE_ID 2 + +static void +sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse, + const struct sc_hid_event *event, const char *name) { + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; + msg.uhid_input.id = UHID_MOUSE_ID; + + assert(event->size <= SC_HID_MAX_SIZE); + memcpy(msg.uhid_input.data, event->data, event->size); + msg.uhid_input.size = event->size; + + if (!sc_controller_push_msg(mouse->controller, &msg)) { + LOGE("Could not send UHID_INPUT message (%s)", name); + } +} + +static void +sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, + const struct sc_mouse_motion_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_motion(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion"); +} + +static void +sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_click(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click"); +} + +static void +sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_scroll(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll"); +} + +bool +sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, + struct sc_controller *controller) { + mouse->controller = controller; + + static const struct sc_mouse_processor_ops ops = { + .process_mouse_motion = sc_mouse_processor_process_mouse_motion, + .process_mouse_click = sc_mouse_processor_process_mouse_click, + .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, + // Touch events not supported (coordinates are not relative) + .process_touch = NULL, + }; + + mouse->mouse_processor.ops = &ops; + + mouse->mouse_processor.relative_mode = true; + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; + msg.uhid_create.id = UHID_MOUSE_ID; + msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC; + msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN; + if (!sc_controller_push_msg(controller, &msg)) { + LOGE("Could not send UHID_CREATE message (mouse)"); + return false; + } + + return true; +} diff --git a/app/src/uhid/mouse_uhid.h b/app/src/uhid/mouse_uhid.h new file mode 100644 index 00000000..f117ba97 --- /dev/null +++ b/app/src/uhid/mouse_uhid.h @@ -0,0 +1,19 @@ +#ifndef SC_MOUSE_UHID_H +#define SC_MOUSE_UHID_H + +#include + +#include "controller.h" +#include "trait/mouse_processor.h" + +struct sc_mouse_uhid { + struct sc_mouse_processor mouse_processor; // mouse processor trait + + struct sc_controller *controller; +}; + +bool +sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, + struct sc_controller *controller); + +#endif From 1c5ad0e8131c6e051e940c29acdc81a500df4673 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 25 Feb 2024 18:35:52 +0100 Subject: [PATCH 1117/1133] Reassign -K and -M to UHID keyboard and mouse The options were deprecated, but for convenience, reassign them to aliases for --keyboard=uhid and --mouse=uhid respectively. Their long version (--hid-keyboard and --hid-mouse) remain deprecated. PR #4473 --- app/data/bash-completion/scrcpy | 2 ++ app/data/zsh-completion/_scrcpy | 2 ++ app/scrcpy.1 | 8 +++++++ app/src/cli.c | 41 +++++++++++++++++++-------------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 8cc0b157..e6b2c91a 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -27,6 +27,7 @@ _scrcpy() { --force-adb-forward --forward-all-clicks -h --help + -K --keyboard= --kill-adb-on-close --legacy-paste @@ -37,6 +38,7 @@ _scrcpy() { --lock-video-orientation --lock-video-orientation= -m --max-size= + -M --max-fps= --mouse= -n --no-control diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 1cf2ae41..a23240ec 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -34,6 +34,7 @@ arguments=( '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' + '-K[Use UHID keyboard (same as --keyboard=uhid)]' '--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--kill-adb-on-close[Kill adb when scrcpy terminates]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' @@ -43,6 +44,7 @@ arguments=( '--list-encoders[List video and audio encoders available on the device]' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' {-m,--max-size=}'[Limit both the width and height of the video to value]' + '-M[Use UHID mouse (same as --mouse=uhid)]' '--max-fps=[Limit the frame rate of screen capture]' '--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 8c0c4cc6..13ad28f9 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -171,6 +171,10 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO .B \-h, \-\-help Print this help. +.TP +.B \-K +Same as \fB\-\-keyboard=uhid\fR. + .TP .BI "\-\-keyboard " mode Select how to send keyboard inputs to the device. @@ -232,6 +236,10 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension Default is 0 (unlimited). +.TP +.B \-M +Same as \fB\-\-mouse=uhid\fR. + .TP .BI "\-\-max\-fps " value Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions). diff --git a/app/src/cli.c b/app/src/cli.c index dceb8fff..cb5be008 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -95,6 +95,8 @@ enum { OPT_ORIENTATION, OPT_KEYBOARD, OPT_MOUSE, + OPT_HID_KEYBOARD_DEPRECATED, + OPT_HID_MOUSE_DEPRECATED, }; struct sc_option { @@ -360,6 +362,10 @@ static const struct sc_option options[] = { .longopt = "help", .text = "Print this help.", }, + { + .shortopt = 'K', + .text = "Same as --keyboard=uhid.", + }, { .longopt_id = OPT_KEYBOARD, .longopt = "keyboard", @@ -391,7 +397,8 @@ static const struct sc_option options[] = { }, { // deprecated - .shortopt = 'K', + //.shortopt = 'K', // old, reassigned + .longopt_id = OPT_HID_KEYBOARD_DEPRECATED, .longopt = "hid-keyboard", }, { @@ -447,9 +454,14 @@ static const struct sc_option options[] = { }, { // deprecated - .shortopt = 'M', + //.shortopt = 'M', // old, reassigned + .longopt_id = OPT_HID_MOUSE_DEPRECATED, .longopt = "hid-mouse", }, + { + .shortopt = 'M', + .text = "Same as --mouse=uhid.", + }, { .longopt_id = OPT_MAX_FPS, .longopt = "max-fps", @@ -2089,20 +2101,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], args->help = true; break; case 'K': -#ifdef HAVE_USB - LOGW("-K/--hid-keyboard is deprecated, use --keyboard=aoa " - "instead."); - opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AOA; + opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID; break; -#else - LOGE("HID over AOA (-K/--hid-keyboard) is disabled."); - return false; -#endif case OPT_KEYBOARD: if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) { return false; } break; + case OPT_HID_KEYBOARD_DEPRECATED: + LOGE("--hid-keyboard has been removed, use --keyboard=aoa or " + "--keyboard=uhid instead."); + return false; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; @@ -2114,19 +2123,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } break; case 'M': -#ifdef HAVE_USB - LOGW("-M/--hid-mouse is deprecated, use --mouse=aoa instead."); - opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA; + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID; break; -#else - LOGE("HID over AOA (-M/--hid-mouse) is disabled."); - return false; -#endif case OPT_MOUSE: if (!parse_mouse(optarg, &opts->mouse_input_mode)) { return false; } break; + case OPT_HID_MOUSE_DEPRECATED: + LOGE("--hid-mouse has been removed, use --mouse=aoa or " + "--mouse=uhid instead."); + return false; case OPT_LOCK_VIDEO_ORIENTATION: if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) { From 5f12132c47178d88ca73f17e90de6891aee33f2d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 28 Feb 2024 22:46:48 +0100 Subject: [PATCH 1118/1133] Do not fallback keyboard mode if AOA fails Initially, if AOA initialization failed, default injection method was used, in order to use the same command/shortcut when the device is connected via USB or via TCP/IP, without changing the arguments. Now that there are 3 keyboard modes, it seems unexpected to switch to another specific mode if AOA fails (and it is inconsistent). If the user explicitly requests AOA, then use AOA or fail. Refs #2632 comment PR #4473 --- app/src/scrcpy.c | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a40a4dec..c63a95c2 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -570,7 +570,7 @@ scrcpy(struct scrcpy_options *options) { if (!ok) { LOGE("Failed to initialize USB"); sc_acksync_destroy(&s->acksync); - goto aoa_hid_end; + goto end; } assert(serial); @@ -578,7 +578,7 @@ scrcpy(struct scrcpy_options *options) { ok = sc_usb_select_device(&s->usb, serial, &usb_device); if (!ok) { sc_usb_destroy(&s->usb); - goto aoa_hid_end; + goto end; } LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", @@ -591,7 +591,7 @@ scrcpy(struct scrcpy_options *options) { LOGE("Failed to connect to USB device %s", serial); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); - goto aoa_hid_end; + goto end; } ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync); @@ -600,7 +600,7 @@ scrcpy(struct scrcpy_options *options) { sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); sc_acksync_destroy(&s->acksync); - goto aoa_hid_end; + goto end; } if (use_keyboard_aoa) { @@ -628,41 +628,18 @@ scrcpy(struct scrcpy_options *options) { sc_usb_disconnect(&s->usb); sc_usb_destroy(&s->usb); sc_aoa_destroy(&s->aoa); - goto aoa_hid_end; + goto end; } acksync = &s->acksync; aoa_hid_initialized = true; - -aoa_hid_end: - if (!aoa_hid_initialized) { - if (keyboard_aoa_initialized) { - sc_keyboard_aoa_destroy(&s->keyboard_aoa); - keyboard_aoa_initialized = false; - } - if (mouse_aoa_initialized) { - sc_mouse_aoa_destroy(&s->mouse_aoa); - mouse_aoa_initialized = false; - } - } - - if (use_keyboard_aoa && !keyboard_aoa_initialized) { - LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); - options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK; - } - - if (use_mouse_aoa && !mouse_aoa_initialized) { - LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)"); - options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; - } } #else assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA); assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA); #endif - // keyboard_input_mode may have been reset if AOA mode failed if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) { sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller, options->key_inject_mode, @@ -680,7 +657,6 @@ aoa_hid_end: kp = &s->keyboard_uhid.key_processor; } - // mouse_input_mode may have been reset if AOA mode failed if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); mp = &s->mouse_sdk.mouse_processor; From dd479ed17613e8f7d7c2cf2447f57045815192b8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 08:49:18 +0100 Subject: [PATCH 1119/1133] Check options specific to SDK keyboard Fail if an option specific to --keyboard=sdk is passed with another keyboard input mode. PR #4473 --- app/src/cli.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index cb5be008..daa041cf 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2611,6 +2611,23 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_SDK) { + if (opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT) { + LOGE("--prefer-text is specific to --keyboard=sdk"); + return false; + } + + if (opts->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { + LOGE("--raw-key-events is specific to --keyboard=sdk"); + return false; + } + + if (!opts->forward_key_repeat) { + LOGE("--no-key-repeat is specific to --keyboard=sdk"); + return false; + } + } + if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { LOGI("Tunnel host/port is set, " "--force-adb-forward automatically enabled."); From b9d244b4c9eaca05f9202126519200603ebd0cbe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 10:00:56 +0100 Subject: [PATCH 1120/1133] Document UHID Rework the documentation to present the keyboard and mouse input modes. PR #4473 --- FAQ.md | 9 ++-- README.md | 14 +++-- doc/control.md | 51 ++---------------- doc/develop.md | 2 +- doc/hid-otg.md | 112 --------------------------------------- doc/keyboard.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/mouse.md | 70 +++++++++++++++++++++++++ doc/otg.md | 37 +++++++++++++ 8 files changed, 262 insertions(+), 169 deletions(-) delete mode 100644 doc/hid-otg.md create mode 100644 doc/keyboard.md create mode 100644 doc/mouse.md create mode 100644 doc/otg.md diff --git a/FAQ.md b/FAQ.md index 6d02361b..5f089cd7 100644 --- a/FAQ.md +++ b/FAQ.md @@ -133,9 +133,9 @@ Try with another USB cable or plug it into another USB port. See [#281] and [#283]: https://github.com/Genymobile/scrcpy/issues/283 -## HID/OTG issues on Windows +## OTG issues on Windows -On Windows, if `scrcpy --otg` (or `--hid-keyboard`/`--hid-mouse`) results in: +On Windows, if `scrcpy --otg` (or `--keyboard=aoa`/`--mouse=aoa`) results in: > ERROR: Could not find any USB device @@ -170,12 +170,13 @@ 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]. -It is also possible to simulate a [physical keyboard][hid] (HID). +To avoid the problem, [change the keyboard mode to simulate a physical +keyboard][hid]. [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]: doc/hid-otg.md +[hid]: doc/keyboard.md#physical-keyboard-simulation ## Client issues diff --git a/README.md b/README.md index 8fabd556..7a671018 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,13 @@ Its features include: - [configurable quality](doc/video.md) - [camera mirroring](doc/camera.md) (Android 12+) - [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only) - - [physical keyboard/mouse simulation (HID)](doc/hid-otg.md) - - [OTG mode](doc/hid-otg.md#otg) + - physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID) + - [OTG mode](doc/otg.md) - and more… +[hid-keyboard]: doc/keyboard.md#physical-keyboard-simulation +[hid-mouse]: doc/mouse.md#physical-mouse-simulation + ## Prerequisites The Android device requires at least API 21 (Android 5.0). @@ -53,8 +56,7 @@ this option is set. [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 -Note that USB debugging is not required to run scrcpy in [OTG -mode](doc/hid-otg.md#otg). +Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md). ## Get the app @@ -73,11 +75,13 @@ documented in the following pages: - [Video](doc/video.md) - [Audio](doc/audio.md) - [Control](doc/control.md) + - [Keyboard](doc/keyboard.md) + - [Mouse](doc/mouse.md) - [Device](doc/device.md) - [Window](doc/window.md) - [Recording](doc/recording.md) - [Tunnels](doc/tunnels.md) - - [HID/OTG](doc/hid-otg.md) + - [OTG](doc/otg.md) - [Camera](doc/camera.md) - [Video4Linux](doc/v4l2.md) - [Shortcuts](doc/shortcuts.md) diff --git a/doc/control.md b/doc/control.md index 595e910e..d6d1265c 100644 --- a/doc/control.md +++ b/doc/control.md @@ -10,36 +10,9 @@ scrcpy --no-control scrcpy -n # short version ``` +## Keyboard and mouse -## Text injection preference - -Two kinds of [events][textevents] are generated when typing text: - - _key events_, signaling that a key is pressed or released; - - _text events_, signaling that a text has been entered. - -By default, letters are injected using key events, so that the keyboard behaves -as expected in games (typically for WASD keys). - -But this may [cause issues][prefertext]. If you encounter such a problem, you -can avoid it by: - -```bash -scrcpy --prefer-text -``` - -(but this will break keyboard behavior in games) - -On the contrary, you could force to always inject raw key events: - -```bash -scrcpy --raw-key-events -``` - -These options have 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 +Read [keyboard](keyboard.md) and [mouse](mouse.md). ## Copy-paste @@ -85,6 +58,7 @@ way as MOD+Shift+v). To disable automatic clipboard synchronization, use `--no-clipboard-autosync`. + ## Pinch-to-zoom, rotate and tilt simulation To simulate "pinch-to-zoom": Ctrl+_click-and-move_. @@ -100,20 +74,7 @@ at a location inverted through the center of the screen. When pressing Ctrl the x and y coordinates are inverted. Using Shift only inverts x. - -## Key repeat - -By default, holding a key down generates repeated key events. This can cause -performance problems in some games, where these events are useless anyway. - -To avoid forwarding repeated key events: - -```bash -scrcpy --no-key-repeat -``` - -This option has no effect on HID keyboard (key repeat is handled by Android -directly in this mode). +This only works for the default mouse mode (`--mouse=sdk`). ## Right-click and middle-click @@ -147,7 +108,3 @@ The target directory can be changed on start: ```bash scrcpy --push-target=/sdcard/Movies/ ``` - -## Physical keyboard and mouse simulation - -See the dedicated [HID/OTG](hid-otg.md) page. diff --git a/doc/develop.md b/doc/develop.md index 67d7f9b0..e5274783 100644 --- a/doc/develop.md +++ b/doc/develop.md @@ -234,7 +234,7 @@ The video and audio streams are decoded by [FFmpeg]. The client parses the command line arguments, then [runs one of two code paths][run]: - scrcpy in "normal" mode ([`scrcpy.c`]) - - scrcpy in [OTG mode](hid-otg.md) ([`scrcpy_otg.c`]) + - scrcpy in [OTG mode](otg.md) ([`scrcpy_otg.c`]) [run]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/main.c#L81-L82 [`scrcpy.c`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/scrcpy.c#L292-L293 diff --git a/doc/hid-otg.md b/doc/hid-otg.md deleted file mode 100644 index 7dfc60fc..00000000 --- a/doc/hid-otg.md +++ /dev/null @@ -1,112 +0,0 @@ -# HID/OTG - -By default, _scrcpy_ injects input events at the Android API level. As an -alternative, when connected over USB, it is possible to send HID events, so that -scrcpy behaves as if it was a physical keyboard and/or mouse connected to the -Android device. - -A special [OTG](#otg) mode allows to control the device without mirroring (and -without USB debugging). - - -## Physical keyboard simulation - -By default, _scrcpy_ uses Android key or text injection. It works everywhere, -but is limited to ASCII. - -Instead, it can simulate a physical USB 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 via USB. - -Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it -is not possible to open a USB device if it is already open by another process -like the _adb daemon_). - -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 using 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]. - -This settings page can be started directly: - -```bash -adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS -``` - -However, the option is only available when the HID keyboard is enabled (or when -a physical keyboard is connected). - -[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 - - -## Physical mouse simulation - -By default, _scrcpy_ uses Android mouse events injection with absolute -coordinates. By simulating a physical mouse, a mouse pointer appears on the -Android device, and relative mouse motion, clicks and scrolls are injected. - -To enable this mode: - -```bash -scrcpy --hid-mouse -scrcpy -M # short version -``` - -When this mode is enabled, the computer mouse is "captured" (the mouse pointer -disappears from the computer and appears on the Android device instead). - -Special capture keys, either Alt or Super, toggle -(disable or enable) the mouse capture. Use one of them to give the control of -the mouse back to the computer. - - -## OTG - -It is possible to run _scrcpy_ with only physical keyboard and mouse simulation -(HID), as if the computer keyboard and mouse were plugged directly to the device -via an OTG cable. - -In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. - -This is similar to `--hid-keyboard --hid-mouse`, but without mirroring. - -To enable OTG mode: - -```bash -scrcpy --otg -# Pass the serial if several USB devices are available -scrcpy --otg -s 0123456789abcdef -``` - -It is possible to enable only HID keyboard or HID mouse: - -```bash -scrcpy --otg --hid-keyboard # keyboard only -scrcpy --otg --hid-mouse # mouse only -scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse -# for convenience, enable both by default -scrcpy --otg # keyboard and mouse -``` - -Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is -connected over USB. - -## HID/OTG issues on Windows - -See [FAQ](/FAQ.md#hidotg-issues-on-windows). diff --git a/doc/keyboard.md b/doc/keyboard.md new file mode 100644 index 00000000..80dfe070 --- /dev/null +++ b/doc/keyboard.md @@ -0,0 +1,136 @@ +# Keyboard + +Several keyboard input modes are available: + + - `--keyboard=sdk` (default) + - `--keyboard=uhid` (or `-K`): simulates a physical HID keyboard using the UHID + kernel module on the device + - `--keyboard=aoa`: simulates a physical HID keyboard using the AOAv2 protocol + - `--keyboard=disabled` + +By default, `sdk` is used, but if you use scrcpy regularly, it is recommended to +use [`uhid`](#uhid) and configure the keyboard layout once and for all. + + +## SDK keyboard + +In this mode (`--keyboard=sdk`, or if the parameter is omitted), keyboard input +events are injected at the Android API level. It works everywhere, but it is +limited to ASCII and some other characters. + +Note that on some devices, an additional option must be enabled in developer +options for this keyboard mode to work. See +[prerequisites](/README.md#prerequisites). + +Additional parameters (specific to `--keyboard=sdk`) described below allow to +customize the behavior. + + +### Text injection preference + +Two kinds of [events][textevents] are generated when typing text: + - _key events_, signaling that a key is pressed or released; + - _text events_, signaling that a text has been entered. + +By default, numbers and "special characters" are inserted using text events, but +letters are injected using key events, so that the keyboard behaves as expected +in games (typically for WASD keys). + +But this may [cause issues][prefertext]. If you encounter such a problem, you +can inject letters as text (or just switch to [UHID](#uhid)): + +```bash +scrcpy --prefer-text +``` + +(but this will break keyboard behavior in games) + +On the contrary, you could force to always inject raw key events: + +```bash +scrcpy --raw-key-events +``` + +[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input +[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 + + +### Key repeat + +By default, holding a key down generates repeated key events. Ths can cause +performance problems in some games, where these events are useless anyway. + +To avoid forwarding repeated key events: + +```bash +scrcpy --no-key-repeat +``` + + +## Physical keyboard simulation + +Two modes allow to simulate a physical HID keyboard on the device. + +To work properly, it is necessary to configure (once and for all) the keyboard +layout on the device to match that of the computer. + +The configuration page can be opened in one of the following ways: + - from the scrcpy window (when `uhid` or `aoa` is used), by pressing + MOD+k (see [shortcuts](shortcuts.md)) + - from the device, in Settings → System → Languages and input → Physical + devices + - from a terminal on the computer, by executing `adb shell am start -a + android.settings.HARD_KEYBOARD_SETTINGS` + +From this configuration page, it is also possible to enable or disable on-screen +keyboard. + + +### UHID + +This mode simulates a physical HID keyboard using the [UHID] kernel module on the +device. + +[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt + +To enable UHID keyboard, use: + +```bash +scrcpy --keyboard=uhid +scrcpy -K # short version +``` + +Once the keyboard layout is configured (see above), it is the best mode for +using the keyboard while mirroring: + + - it works for all characters and IME (contrary to `--keyboard=sdk`) + - the on-screen keyboard can be disabled (contrary to `--keyboard=sdk`) + - it works over TCP/IP (wirelessly) (contrary to `--keyboard=aoa`) + - there are no issues on Windows (contrary to `--keyboard=aoa`) + +One drawback is that it may not work on old Android versions due to permission +errors. + + +### AOA + +This mode simulates a physical HID keyboard using the [AOAv2] protocol. + +[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support + +To enable AOA keyboard, use: + +```bash +scrcpy --keyboard=aoa +``` + +Contrary to the other modes, it works at the USB level directly (so it only +works over USB). + +It does not use the scrcpy server, and does not require `adb` (USB debugging). +Therefore, it is possible to control the device (but not mirror) even with USB +debugging disabled (see [OTG](otg.md)). + +Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring +(it is not possible to open a USB device if it is already open by another +process like the _adb daemon_). diff --git a/doc/mouse.md b/doc/mouse.md new file mode 100644 index 00000000..d0342954 --- /dev/null +++ b/doc/mouse.md @@ -0,0 +1,70 @@ +# Mouse + +Several mouse input modes are available: + + - `--mouse=sdk` (default) + - `--mouse=uhid` (or `-M`): simulates a physical HID mouse using the UHID + kernel module on the device + - `--mouse=aoa`: simulates a physical HID mouse using the AOAv2 protocol + - `--mouse=disabled` + + +## SDK mouse + +In this mode (`--mouse=sdk`, or if the parameter is omitted), mouse input events +are injected at the Android API level with absolute coordinates. + +Note that on some devices, an additional option must be enabled in developer +options for this mouse mode to work. See +[prerequisites](/README.md#prerequisites). + + +## Physical mouse simulation + +Two modes allow to simulate a physical HID mouse on the device. + +In these modes, the computer mouse is "captured": the mouse pointer disappears +from the computer and appears on the Android device instead. + +Special capture keys, either Alt or Super, toggle +(disable or enable) the mouse capture. Use one of them to give the control of +the mouse back to the computer. + + +### UHID + +This mode simulates a physical HID mouse using the [UHID] kernel module on the +device. + +[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt + +To enable UHID mouse, use: + +```bash +scrcpy --mouse=uhid +scrcpy -M # short version +``` + + +### AOA + +This mode simulates a physical HID mouse using the [AOAv2] protocol. + +[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support + +To enable AOA mouse, use: + +```bash +scrcpy --mouse=aoa +``` + +Contrary to the other modes, it works at the USB level directly (so it only +works over USB). + +It does not use the scrcpy server, and does not require `adb` (USB debugging). +Therefore, it is possible to control the device (but not mirror) even with USB +debugging disabled (see [OTG](otg.md)). + +Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring +(it is not possible to open a USB device if it is already open by another +process like the _adb daemon_). diff --git a/doc/otg.md b/doc/otg.md new file mode 100644 index 00000000..3c7ed467 --- /dev/null +++ b/doc/otg.md @@ -0,0 +1,37 @@ +# OTG + +By default, _scrcpy_ injects input events at the Android API level. As an +alternative, when connected over USB, it is possible to send HID events, so that +scrcpy behaves as if it was a physical keyboard and/or mouse connected to the +Android device. + +A special mode allows to control the device without mirroring, using AOA +[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible +to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if +the computer keyboard and mouse were plugged directly to the device via an OTG +cable. + +In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. + +This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring. + +To enable OTG mode: + +```bash +scrcpy --otg +# Pass the serial if several USB devices are available +scrcpy --otg -s 0123456789abcdef +``` + +It is possible to disable HID keyboard or HID mouse: + +```bash +scrcpy --otg --keyboard=disabled +scrcpy --otg --mouse=disabled +``` + +It only works if the device is connected over USB. + +## OTG issues on Windows + +See [FAQ](/FAQ.md#otg-issues-on-windows). From bf069bd37bb064fb49b7f75c6ea665706b599784 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 09:01:25 +0100 Subject: [PATCH 1121/1133] Document usage examples This exposes several common options on the front page. --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 7a671018..701bb075 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,41 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md). - [macOS](doc/macos.md) +## Usage examples + +There are a lot of options, [documented](#user-documentation) in separate pages. +Here are just some common examples. + + - Capture the screen in H.265 (better quality), limit the size to 1920, limit + the frame rate to 60fps, disable audio, and control the device by simulating + a physical keyboard: + + ```bash + scrcpy --video-codec=h265 --max-size=1920 --max-fps=60 --no-audio --keyboard=uhid + scrcpy --video-codec=h265 -m1920 --max-fps=60 --no-audio -K # short version + ``` + + - Record the device camera in H.265 at 1920x1080 (and microphone) to an MP4 + file: + + ```bash + scrcpy --video-source=camera --video-codec=h265 --camera-size=1920x1080 --record=file.mp4 + ``` + + - Capture the device front camera and expose it as a webcam on the computer (on + Linux): + + ```bash + scrcpy --video-source=camera --camera-size=1920x1080 --camera-facing=front --v4l2-sink=/dev/video2 --no-playback + ``` + + - Control the device without mirroring by simulating a physical keyboard and + mouse (USB debugging not required): + + ```bash + scrcpy --otg + ``` + ## User documentation The application provides a lot of features and configuration options. They are From cdf09805c042cc760397b08d9e7cf58fbf8f76a4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 19:37:05 +0100 Subject: [PATCH 1122/1133] Add missing initialization --- app/src/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/display.c b/app/src/display.c index ba15cd14..c8df615d 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -62,6 +62,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); } + display->texture = NULL; display->pending.flags = 0; display->pending.frame = NULL; From fd0f432e877153d83ed435474fb7b04e41de4269 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 29 Feb 2024 19:37:14 +0100 Subject: [PATCH 1123/1133] Detect missing initializations Write invalid data in memory to easily detect missing initializations in debug mode. --- app/src/scrcpy.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index c63a95c2..eb9cd201 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -312,6 +312,10 @@ scrcpy_generate_scid(void) { enum scrcpy_exit_code scrcpy(struct scrcpy_options *options) { static struct scrcpy scrcpy; +#ifndef NDEBUG + // Detect missing initializations + memset(&scrcpy, 42, sizeof(scrcpy)); +#endif struct scrcpy *s = &scrcpy; // Minimal SDL initialization From 4dca08cfe3eadd4438bf235bd62050059aec1801 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Mar 2024 09:54:31 +0100 Subject: [PATCH 1124/1133] Set SDL hints before creating any thread To avoid race conditions in SDL (reported by TSAN). --- app/src/scrcpy.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index eb9cd201..961f6202 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -409,6 +409,12 @@ scrcpy(struct scrcpy_options *options) { return SCRCPY_EXIT_FAILURE; } + if (options->video_playback) { + // Set hints before starting the server thread to avoid race conditions + // in SDL + sdl_set_hints(options->render_driver); + } + if (!sc_server_start(&s->server)) { goto end; } @@ -425,10 +431,6 @@ scrcpy(struct scrcpy_options *options) { assert(!options->video_playback || options->video); assert(!options->audio_playback || options->audio); - if (options->video_playback) { - sdl_set_hints(options->render_driver); - } - if (options->video_playback || (options->control && options->clipboard_autosync)) { // Initialize the video subsystem even if --no-video or From 36189b90ea815d8fced961c36c80f146d5952324 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Mar 2024 09:55:32 +0100 Subject: [PATCH 1125/1133] Remove spurious line --- app/src/scrcpy.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 961f6202..f43af35e 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -106,7 +106,6 @@ static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { static void sdl_set_hints(const char *render_driver) { - if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) { LOGW("Could not set render driver"); } From 125b1103e1cdfe676d66a9223df82c423c5e75bf Mon Sep 17 00:00:00 2001 From: inson1 <75314629+inson1@users.noreply.github.com> Date: Sat, 2 Mar 2024 15:39:56 +0100 Subject: [PATCH 1126/1133] Happy new year 2024! PR #4716 Signed-off-by: Romain Vimont --- LICENSE | 2 +- README.md | 2 +- app/scrcpy.1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 55f96811..d9326a74 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ identification within third-party archives. Copyright (C) 2018 Genymobile - Copyright (C) 2018-2023 Romain Vimont + Copyright (C) 2018-2024 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 0a3d03cb..5d9f04a9 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ work][donate]: ## Licence Copyright (C) 2018 Genymobile - Copyright (C) 2018-2023 Romain Vimont + Copyright (C) 2018-2024 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0c34b4e2..eed1f355 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -689,7 +689,7 @@ Report bugs to . .SH COPYRIGHT Copyright \(co 2018 Genymobile -Copyright \(co 2018\-2023 Romain Vimont +Copyright \(co 2018\-2024 Romain Vimont Licensed under the Apache License, Version 2.0. From 8d87b91f692914ada1c146bd911ab4623552174b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 1 Mar 2024 20:02:00 +0100 Subject: [PATCH 1127/1133] Build dependencies from sources The project has 3 build dependencies: - SDL - FFmpeg - libusb For Windows, the release script downloaded pre-built build dependencies (either from upstream, or from the scrcpy-deps repository). Instead, download the source releases and build locally. This offers more flexibility. The official adb release is still downloaded and included as is in the release archive (it is not a build dependency). Also upgrade FFmpeg to 6.1.1 and libusb to 1.0.27. PR #4713 --- app/deps/.gitignore | 1 + app/deps/README | 27 ++++++ app/deps/adb.sh | 32 ++++++++ app/deps/common | 55 +++++++++++++ app/deps/ffmpeg.sh | 91 +++++++++++++++++++++ app/deps/libusb.sh | 44 ++++++++++ app/deps/patches/ffmpeg-6.1-fix-build.patch | 27 ++++++ app/deps/sdl.sh | 47 +++++++++++ app/prebuilt-deps/.gitignore | 1 - app/prebuilt-deps/common | 22 ----- app/prebuilt-deps/prepare-adb.sh | 32 -------- app/prebuilt-deps/prepare-ffmpeg.sh | 30 ------- app/prebuilt-deps/prepare-libusb.sh | 37 --------- app/prebuilt-deps/prepare-sdl.sh | 34 -------- release.mk | 52 ++++++------ 15 files changed, 348 insertions(+), 184 deletions(-) create mode 100644 app/deps/.gitignore create mode 100644 app/deps/README create mode 100755 app/deps/adb.sh create mode 100644 app/deps/common create mode 100755 app/deps/ffmpeg.sh create mode 100755 app/deps/libusb.sh create mode 100644 app/deps/patches/ffmpeg-6.1-fix-build.patch create mode 100755 app/deps/sdl.sh delete mode 100644 app/prebuilt-deps/.gitignore delete mode 100755 app/prebuilt-deps/common delete mode 100755 app/prebuilt-deps/prepare-adb.sh delete mode 100755 app/prebuilt-deps/prepare-ffmpeg.sh delete mode 100755 app/prebuilt-deps/prepare-libusb.sh delete mode 100755 app/prebuilt-deps/prepare-sdl.sh diff --git a/app/deps/.gitignore b/app/deps/.gitignore new file mode 100644 index 00000000..ccf6a49e --- /dev/null +++ b/app/deps/.gitignore @@ -0,0 +1 @@ +/work diff --git a/app/deps/README b/app/deps/README new file mode 100644 index 00000000..9cfb5c06 --- /dev/null +++ b/app/deps/README @@ -0,0 +1,27 @@ +This directory (app/deps/) contains: + +*.sh : shell scripts to download and build dependencies + +patches/ : patches to fix dependencies (used by scripts) + +work/sources/ : downloaded tarballs and extracted folders + ffmpeg-6.1.1.tar.xz + ffmpeg-6.1.1/ + libusb-1.0.27.tar.gz + libusb-1.0.27/ + ... +work/build/ : build dirs for each dependency/version/architecture + ffmpeg-6.1.1/win32/ + ffmpeg-6.1.1/win64/ + libusb-1.0.27/win32/ + libusb-1.0.27/win64/ + ... +work/install/ : install dirs for each architexture + win32/bin/ + win32/include/ + win32/lib/ + win32/share/ + win64/bin/ + win64/include/ + win64/lib/ + win64/share/ diff --git a/app/deps/adb.sh b/app/deps/adb.sh new file mode 100755 index 00000000..e2408216 --- /dev/null +++ b/app/deps/adb.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -ex +DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DEPS_DIR" +. common + +VERSION=34.0.5 +FILENAME=platform-tools_r$VERSION-windows.zip +PROJECT_DIR=platform-tools-$VERSION +SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571 + +cd "$SOURCES_DIR" + +if [[ -d "$PROJECT_DIR" ]] +then + echo "$PWD/$PROJECT_DIR" found +else + get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM" + mkdir -p "$PROJECT_DIR" + cd "$PROJECT_DIR" + ZIP_PREFIX=platform-tools + unzip "../$FILENAME" \ + "$ZIP_PREFIX"/AdbWinApi.dll \ + "$ZIP_PREFIX"/AdbWinUsbApi.dll \ + "$ZIP_PREFIX"/adb.exe + mv "$ZIP_PREFIX"/* . + rmdir "$ZIP_PREFIX" +fi + +mkdir -p "$INSTALL_DIR/$HOST/bin" +cd "$INSTALL_DIR/$HOST/bin" +cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/" diff --git a/app/deps/common b/app/deps/common new file mode 100644 index 00000000..c1cc7729 --- /dev/null +++ b/app/deps/common @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# This file is intended to be sourced by other scripts, not executed + +if [[ $# != 1 ]] +then + # : win32 or win64 + echo "Syntax: $0 " >&2 + exit 1 +fi + +HOST="$1" + +if [[ "$HOST" = win32 ]] +then + HOST_TRIPLET=i686-w64-mingw32 +elif [[ "$HOST" = win64 ]] +then + HOST_TRIPLET=x86_64-w64-mingw32 +else + echo "Unsupported host: $HOST" >&2 + exit 1 +fi + +DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DEPS_DIR" + +PATCHES_DIR="$PWD/patches" + +WORK_DIR="$PWD/work" +SOURCES_DIR="$WORK_DIR/sources" +BUILD_DIR="$WORK_DIR/build" +INSTALL_DIR="$WORK_DIR/install" + +mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR" + +checksum() { + local file="$1" + local sum="$2" + echo "$file: verifying checksum..." + echo "$sum $file" | sha256sum -c +} + +get_file() { + local url="$1" + local file="$2" + local sum="$3" + if [[ -f "$file" ]] + then + echo "$file: found" + else + echo "$file: not found, downloading..." + wget "$url" -O "$file" + fi + checksum "$file" "$sum" +} diff --git a/app/deps/ffmpeg.sh b/app/deps/ffmpeg.sh new file mode 100755 index 00000000..19fb2991 --- /dev/null +++ b/app/deps/ffmpeg.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -ex +DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DEPS_DIR" +. common + +VERSION=6.1.1 +FILENAME=ffmpeg-$VERSION.tar.xz +PROJECT_DIR=ffmpeg-$VERSION +SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968 + +cd "$SOURCES_DIR" + +if [[ -d "$PROJECT_DIR" ]] +then + echo "$PWD/$PROJECT_DIR" found +else + get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM" + tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" + patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch +fi + +mkdir -p "$BUILD_DIR/$PROJECT_DIR" +cd "$BUILD_DIR/$PROJECT_DIR" + +if [[ "$HOST" = win32 ]] +then + ARCH=x86 +elif [[ "$HOST" = win64 ]] +then + ARCH=x86_64 +else + echo "Unsupported host: $HOST" >&2 + exit 1 +fi + +# -static-libgcc to avoid missing libgcc_s_dw2-1.dll +# -static to avoid dynamic dependency to zlib +export CFLAGS='-static-libgcc -static' +export CXXFLAGS="$CFLAGS" +export LDFLAGS='-static-libgcc -static' + +if [[ -d "$HOST" ]] +then + echo "'$PWD/$HOST' already exists, not reconfigured" + cd "$HOST" +else + mkdir "$HOST" + cd "$HOST" + + "$SOURCES_DIR/$PROJECT_DIR"/configure \ + --prefix="$INSTALL_DIR/$HOST" \ + --enable-cross-compile \ + --target-os=mingw32 \ + --arch="$ARCH" \ + --cross-prefix="${HOST_TRIPLET}-" \ + --cc="${HOST_TRIPLET}-gcc" \ + --extra-cflags="-O2 -fPIC" \ + --enable-shared \ + --disable-static \ + --disable-programs \ + --disable-doc \ + --disable-swscale \ + --disable-postproc \ + --disable-avfilter \ + --disable-avdevice \ + --disable-network \ + --disable-everything \ + --enable-swresample \ + --enable-decoder=h264 \ + --enable-decoder=hevc \ + --enable-decoder=av1 \ + --enable-decoder=pcm_s16le \ + --enable-decoder=opus \ + --enable-decoder=aac \ + --enable-decoder=flac \ + --enable-decoder=png \ + --enable-protocol=file \ + --enable-demuxer=image2 \ + --enable-parser=png \ + --enable-zlib \ + --enable-muxer=matroska \ + --enable-muxer=mp4 \ + --enable-muxer=opus \ + --enable-muxer=flac \ + --enable-muxer=wav \ + --disable-vulkan +fi + +make -j +make install diff --git a/app/deps/libusb.sh b/app/deps/libusb.sh new file mode 100755 index 00000000..97fc3c72 --- /dev/null +++ b/app/deps/libusb.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -ex +DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DEPS_DIR" +. common + +VERSION=1.0.27 +FILENAME=libusb-$VERSION.tar.bz2 +PROJECT_DIR=libusb-$VERSION +SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575 + +cd "$SOURCES_DIR" + +if [[ -d "$PROJECT_DIR" ]] +then + echo "$PWD/$PROJECT_DIR" found +else + get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM" + tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" +fi + +mkdir -p "$BUILD_DIR/$PROJECT_DIR" +cd "$BUILD_DIR/$PROJECT_DIR" + +export CFLAGS='-O2' +export CXXFLAGS="$CFLAGS" + +if [[ -d "$HOST" ]] +then + echo "'$PWD/$HOST' already exists, not reconfigured" + cd "$HOST" +else + mkdir "$HOST" + cd "$HOST" + + "$SOURCES_DIR/$PROJECT_DIR"/configure \ + --prefix="$INSTALL_DIR/$HOST" \ + --host="$HOST_TRIPLET" \ + --enable-shared \ + --disable-static +fi + +make -j +make install-strip diff --git a/app/deps/patches/ffmpeg-6.1-fix-build.patch b/app/deps/patches/ffmpeg-6.1-fix-build.patch new file mode 100644 index 00000000..ed4df48d --- /dev/null +++ b/app/deps/patches/ffmpeg-6.1-fix-build.patch @@ -0,0 +1,27 @@ +From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001 +From: Romain Vimont +Date: Sun, 12 Nov 2023 17:58:50 +0100 +Subject: [PATCH] Fix FFmpeg 6.1 build + +Build failed on tag n6.1 With --enable-decoder=av1 but without +--enable-muxer=av1. +--- + libavcodec/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libavcodec/Makefile b/libavcodec/Makefile +index 580a8d6b54..aff19b670c 100644 +--- a/libavcodec/Makefile ++++ b/libavcodec/Makefile +@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \ + OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o + OBJS-$(CONFIG_AURA_DECODER) += cyuv.o + OBJS-$(CONFIG_AURA2_DECODER) += aura.o +-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o ++OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o + OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o + OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o + OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o +-- +2.42.0 + diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh new file mode 100755 index 00000000..36c7ab1c --- /dev/null +++ b/app/deps/sdl.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -ex +DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) +cd "$DEPS_DIR" +. common + +VERSION=2.28.5 +FILENAME=SDL-$VERSION.tar.gz +PROJECT_DIR=SDL-release-$VERSION +SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023 + +cd "$SOURCES_DIR" + +if [[ -d "$PROJECT_DIR" ]] +then + echo "$PWD/$PROJECT_DIR" found +else + get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" + tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" +fi + +mkdir -p "$BUILD_DIR/$PROJECT_DIR" +cd "$BUILD_DIR/$PROJECT_DIR" + +export CFLAGS='-O2' +export CXXFLAGS="$CFLAGS" + +if [[ -d "$HOST" ]] +then + echo "'$PWD/$HOST' already exists, not reconfigured" + cd "$HOST" +else + mkdir "$HOST" + cd "$HOST" + + "$SOURCES_DIR/$PROJECT_DIR"/configure \ + --prefix="$INSTALL_DIR/$HOST" \ + --host="$HOST_TRIPLET" \ + --enable-shared \ + --disable-static +fi + +make -j +# There is no "make install-strip" +make install +# Strip manually +${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll" diff --git a/app/prebuilt-deps/.gitignore b/app/prebuilt-deps/.gitignore deleted file mode 100644 index 3af0ccb6..00000000 --- a/app/prebuilt-deps/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/data diff --git a/app/prebuilt-deps/common b/app/prebuilt-deps/common deleted file mode 100755 index c97f7de4..00000000 --- a/app/prebuilt-deps/common +++ /dev/null @@ -1,22 +0,0 @@ -PREBUILT_DATA_DIR=data - -checksum() { - local file="$1" - local sum="$2" - echo "$file: verifying checksum..." - echo "$sum $file" | sha256sum -c -} - -get_file() { - local url="$1" - local file="$2" - local sum="$3" - if [[ -f "$file" ]] - then - echo "$file: found" - else - echo "$file: not found, downloading..." - wget "$url" -O "$file" - fi - checksum "$file" "$sum" -} diff --git a/app/prebuilt-deps/prepare-adb.sh b/app/prebuilt-deps/prepare-adb.sh deleted file mode 100755 index 4fb6fd7d..00000000 --- a/app/prebuilt-deps/prepare-adb.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -DEP_DIR=platform-tools-34.0.5 - -FILENAME=platform-tools_r34.0.5-windows.zip -SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571 - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://dl.google.com/android/repository/$FILENAME" \ - "$FILENAME" "$SHA256SUM" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -ZIP_PREFIX=platform-tools -unzip "../$FILENAME" \ - "$ZIP_PREFIX"/AdbWinApi.dll \ - "$ZIP_PREFIX"/AdbWinUsbApi.dll \ - "$ZIP_PREFIX"/adb.exe -mv "$ZIP_PREFIX"/* . -rmdir "$ZIP_PREFIX" diff --git a/app/prebuilt-deps/prepare-ffmpeg.sh b/app/prebuilt-deps/prepare-ffmpeg.sh deleted file mode 100755 index 19840afb..00000000 --- a/app/prebuilt-deps/prepare-ffmpeg.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -VERSION=6.1-scrcpy-3 -DEP_DIR="ffmpeg-$VERSION" - -FILENAME="$DEP_DIR".7z -SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \ - "$FILENAME" "$SHA256SUM" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -ZIP_PREFIX=ffmpeg -7z x "../$FILENAME" -mv "$ZIP_PREFIX"/* . -rmdir "$ZIP_PREFIX" diff --git a/app/prebuilt-deps/prepare-libusb.sh b/app/prebuilt-deps/prepare-libusb.sh deleted file mode 100755 index b31c45eb..00000000 --- a/app/prebuilt-deps/prepare-libusb.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -VERSION=1.0.26 -DEP_DIR="libusb-$VERSION" - -FILENAME="libusb-$VERSION-binaries.7z" -SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5 - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \ - "$FILENAME" "$SHA256SUM" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -7z x "../$FILENAME" \ - "libusb-$VERSION-binaries/libusb-MinGW-Win32/" \ - "libusb-$VERSION-binaries/libusb-MinGW-x64/" - -mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" . -mv "libusb-$VERSION-binaries/libusb-MinGW-x64" . -rm -rf "libusb-$VERSION-binaries" - -# Rename the dll to get the same library name on all platforms -mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll -mv libusb-MinGW-x64/bin/msys-usb-1.0.dll libusb-MinGW-x64/bin/libusb-1.0.dll diff --git a/app/prebuilt-deps/prepare-sdl.sh b/app/prebuilt-deps/prepare-sdl.sh deleted file mode 100755 index 7569744f..00000000 --- a/app/prebuilt-deps/prepare-sdl.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -e -DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DIR" -. common -mkdir -p "$PREBUILT_DATA_DIR" -cd "$PREBUILT_DATA_DIR" - -VERSION=2.28.5 -DEP_DIR="SDL2-$VERSION" - -FILENAME="SDL2-devel-$VERSION-mingw.tar.gz" -SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21 - -if [[ -d "$DEP_DIR" ]] -then - echo "$DEP_DIR" found - exit 0 -fi - -get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \ - "$FILENAME" "$SHA256SUM" - -mkdir "$DEP_DIR" -cd "$DEP_DIR" - -TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name -tar xf "../$FILENAME" --strip-components=1 \ - "$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \ - "$TAR_PREFIX"/i686-w64-mingw32/include/ \ - "$TAR_PREFIX"/i686-w64-mingw32/lib/ \ - "$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \ - "$TAR_PREFIX"/x86_64-w64-mingw32/include/ \ - "$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \ diff --git a/release.mk b/release.mk index fd969e5a..89f3da21 100644 --- a/release.mk +++ b/release.mk @@ -62,38 +62,38 @@ build-server: meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) ninja -C "$(SERVER_BUILD_DIR)" -prepare-deps: - @app/prebuilt-deps/prepare-adb.sh - @app/prebuilt-deps/prepare-sdl.sh - @app/prebuilt-deps/prepare-ffmpeg.sh - @app/prebuilt-deps/prepare-libusb.sh - -build-win32: prepare-deps +prepare-deps-win32: + @app/deps/adb.sh win32 + @app/deps/sdl.sh win32 + @app/deps/ffmpeg.sh win32 + @app/deps/libusb.sh win32 + +prepare-deps-win64: + @app/deps/adb.sh win64 + @app/deps/sdl.sh win64 + @app/deps/ffmpeg.sh win64 + @app/deps/libusb.sh win64 + +build-win32: prepare-deps-win32 rm -rf "$(WIN32_BUILD_DIR)" mkdir -p "$(WIN32_BUILD_DIR)/local" - cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/SDL2-2.28.5/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/" meson setup "$(WIN32_BUILD_DIR)" \ - --pkg-config-path="$(WIN32_BUILD_DIR)/local/lib/pkgconfig" \ - -Dc_args="-I$(PWD)/$(WIN32_BUILD_DIR)/local/include" \ - -Dc_link_args="-L$(PWD)/$(WIN32_BUILD_DIR)/local/lib" \ + --pkg-config-path="app/deps/work/install/win32/lib/pkgconfig" \ + -Dc_args="-I$(PWD)/app/deps/work/install/win32/include" \ + -Dc_link_args="-L$(PWD)/app/deps/work/install/win32/lib" \ --cross-file=cross_win32.txt \ --buildtype=release --strip -Db_lto=true \ -Dcompile_server=false \ -Dportable=true ninja -C "$(WIN32_BUILD_DIR)" -build-win64: prepare-deps +build-win64: prepare-deps-win64 rm -rf "$(WIN64_BUILD_DIR)" mkdir -p "$(WIN64_BUILD_DIR)/local" - cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/SDL2-2.28.5/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/" - cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/" meson setup "$(WIN64_BUILD_DIR)" \ - --pkg-config-path="$(WIN64_BUILD_DIR)/local/lib/pkgconfig" \ - -Dc_args="-I$(PWD)/$(WIN64_BUILD_DIR)/local/include" \ - -Dc_link_args="-L$(PWD)/$(WIN64_BUILD_DIR)/local/lib" \ + --pkg-config-path="app/deps/work/install/win64/lib/pkgconfig" \ + -Dc_args="-I$(PWD)/app/deps/work/install/win64/include" \ + -Dc_link_args="-L$(PWD)/app/deps/work/install/win64/lib" \ --cross-file=cross_win64.txt \ --buildtype=release --strip -Db_lto=true \ -Dcompile_server=false \ @@ -108,10 +108,8 @@ dist-win32: build-server build-win32 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" - cp "$(WIN32_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/deps/work/install/win32/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp app/deps/work/install/win32/bin/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" dist-win64: build-server build-win64 mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" @@ -121,10 +119,8 @@ dist-win64: build-server build-win64 cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" - cp "$(WIN64_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/deps/work/install/win64/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp app/deps/work/install/win64/bin/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" zip-win32: dist-win32 cd "$(DIST)"; \ From af573090741e73c66c4a543dcd94fd771c51b7be Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 2 Mar 2024 23:22:09 +0100 Subject: [PATCH 1128/1133] Bump version to 2.4 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 895b9c93..059e91d4 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.3.1" + VALUE "ProductVersion", "2.4" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 4ae91f69..22d0f4ef 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.3.1', + version: '2.4', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 1a18d997..6a1b09df 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 34 - versionCode 20301 - versionName "2.3.1" + versionCode 20400 + versionName "2.4" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 69d85679..7f7d7921 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.3.1 +SCRCPY_VERSION_NAME=2.4 PLATFORM=${ANDROID_PLATFORM:-34} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} From 0c94b75eefee510d3de6bc724eaeedfc600faadc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Mar 2024 00:00:24 +0100 Subject: [PATCH 1129/1133] Update links to 2.4 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 30fc0a04..a672b327 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v2.3.1) +# scrcpy (v2.4) scrcpy diff --git a/doc/build.md b/doc/build.md index 7e3c84e9..751cf831 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.3.1`][direct-scrcpy-server] - SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b` + - [`scrcpy-server-v2.4`][direct-scrcpy-server] + SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index 60fd7986..a3711f26 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit) - SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d` - - [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit) - SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff` + - [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit) + SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9` + - [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit) + SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip and extract it. diff --git a/install_release.sh b/install_release.sh index d8dbd951..0be5675c 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1 -PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4 +PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From cc7719079ab6301e6ab42b7cd078a1da5acfa5b2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Mar 2024 00:05:26 +0100 Subject: [PATCH 1130/1133] Italicize coordinates letters in documentation --- doc/control.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/control.md b/doc/control.md index d6d1265c..abc9d1bf 100644 --- a/doc/control.md +++ b/doc/control.md @@ -71,8 +71,8 @@ To simulate a tilt gesture: Shift+_click-and-move-up-or-down_. Technically, _scrcpy_ generates additional touch events from a "virtual finger" at a location inverted through the center of the screen. When pressing -Ctrl the x and y coordinates are inverted. Using Shift -only inverts x. +Ctrl the _x_ and _y_ coordinates are inverted. Using Shift +only inverts _x_. This only works for the default mouse mode (`--mouse=sdk`). From 7f23ff3f2ca64dae6eb8b0ca9a881230184ae756 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 3 Mar 2024 00:06:54 +0100 Subject: [PATCH 1131/1133] Add videos for pinch-to-zoom and tilt A video is worth a thousand words. --- doc/control.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/control.md b/doc/control.md index abc9d1bf..e9fd9e9b 100644 --- a/doc/control.md +++ b/doc/control.md @@ -67,8 +67,12 @@ More precisely, hold down Ctrl while pressing the left-click button. Until the left-click button is released, all mouse movements scale and rotate the content (if supported by the app) relative to the center of the screen. +https://github.com/Genymobile/scrcpy/assets/543275/26c4a920-9805-43f1-8d4c-608752d04767 + To simulate a tilt gesture: Shift+_click-and-move-up-or-down_. +https://github.com/Genymobile/scrcpy/assets/543275/1e252341-4a90-4b29-9d11-9153b324669f + Technically, _scrcpy_ generates additional touch events from a "virtual finger" at a location inverted through the center of the screen. When pressing Ctrl the _x_ and _y_ coordinates are inverted. Using Shift From 79968a0ae63179c39d5f1d4f4d97da020c9e07dd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 11 Mar 2024 18:05:27 +0100 Subject: [PATCH 1132/1133] Reorder documentation Present the --tcpip option without arguments first. --- doc/connection.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/connection.md b/doc/connection.md index 90ced010..17efbbdc 100644 --- a/doc/connection.md +++ b/doc/connection.md @@ -67,14 +67,6 @@ computer. An option `--tcpip` allows to configure the connection automatically. There are two variants. -If the device (accessible at 192.168.1.1 in this example) already listens on a -port (typically 5555) for incoming _adb_ connections, then run: - -```bash -scrcpy --tcpip=192.168.1.1 # default port is 5555 -scrcpy --tcpip=192.168.1.1:5555 -``` - If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP address), connect the device over USB, then run: @@ -85,6 +77,14 @@ scrcpy --tcpip # without arguments It will automatically find the device IP address and adb port, enable TCP/IP mode if necessary, then connect to the device before starting. +If the device (accessible at 192.168.1.1 in this example) already listens on a +port (typically 5555) for incoming _adb_ connections, then run: + +```bash +scrcpy --tcpip=192.168.1.1 # default port is 5555 +scrcpy --tcpip=192.168.1.1:5555 +``` + ### Manual From 206809a99affad9a7aa58fcf7593cea71f48954d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 2 Apr 2024 18:01:21 +0200 Subject: [PATCH 1133/1133] Fix typo in documentation --- doc/audio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/audio.md b/doc/audio.md index ecae4468..f1d4d8e7 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -35,7 +35,7 @@ scrcpy --no-video # interrupt with Ctrl+C ``` -Without video, the audio latency is typically not criticial, so it might be +Without video, the audio latency is typically not critical, so it might be interesting to add [buffering](#buffering) to minimize glitches: ```