Compare commits

..

4 Commits

Author SHA1 Message Date
d5d1b59b68 Log server pushed
Now that "adb push" stdout is disabled, add a log to notify server
pushed.
2021-11-20 00:02:30 +01:00
d921e2d7a3 Use inherit flags for adb commands
Explicitly indicate, for each call, if stdout and stderr must be
inherited.
2021-11-20 00:02:30 +01:00
4156771077 Expose inherit flag 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.
2021-11-20 00:02:30 +01:00
c96dc6d2c4 Simplify Windows process inheritance configuration
Merge if-blocks together.
2021-11-19 22:27:01 +01:00
182 changed files with 3668 additions and 12658 deletions

View File

@ -15,7 +15,7 @@ First, you need to install the required packages:
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \ gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0 libusb-1.0-0-dev libusb-1.0-0 libusb-dev
``` ```
Then clone the repo and execute the installation script Then clone the repo and execute the installation script
@ -46,7 +46,7 @@ sudo ninja -Cbuild-auto uninstall
### `master` ### `master`
The `master` branch concerns the latest release, and is the home page of the The `master` branch concerns the latest release, and is the home page of the
project on GitHub. project on Github.
### `dev` ### `dev`
@ -94,7 +94,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies # client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \ 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-1.0-0-dev libusb-dev
# server build dependencies # server build dependencies
sudo apt install openjdk-11-jdk sudo apt install openjdk-11-jdk
@ -161,8 +161,7 @@ install the required packages:
```bash ```bash
# runtime dependencies # runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \ pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg \ mingw-w64-x86_64-ffmpeg
mingw-w64-x86_64-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-x86_64-make \ pacman -S mingw-w64-x86_64-make \
@ -176,8 +175,7 @@ For a 32 bits version, replace `x86_64` by `i686`:
```bash ```bash
# runtime dependencies # runtime dependencies
pacman -S mingw-w64-i686-SDL2 \ pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg \ mingw-w64-i686-ffmpeg
mingw-w64-i686-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-i686-make \ pacman -S mingw-w64-i686-make \
@ -201,7 +199,7 @@ Install the packages with [Homebrew]:
```bash ```bash
# runtime dependencies # runtime dependencies
brew install sdl2 ffmpeg libusb brew install sdl2 ffmpeg
# client build dependencies # client build dependencies
brew install pkg-config meson brew install pkg-config meson
@ -260,7 +258,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
Then, build: Then, build:
```bash ```bash
meson x --buildtype=release --strip -Db_lto=true meson x --buildtype release --strip -Db_lto=true
ninja -Cx # DO NOT RUN AS ROOT ninja -Cx # DO NOT RUN AS ROOT
``` ```
@ -272,16 +270,16 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.24`][direct-scrcpy-server] - [`scrcpy-server-v1.20`][direct-scrcpy-server]
<sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub> _(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_
[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.20/scrcpy-server-v1.20
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:
```bash ```bash
meson x --buildtype=release --strip -Db_lto=true \ meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server -Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx # DO NOT RUN AS ROOT ninja -Cx # DO NOT RUN AS ROOT
``` ```
@ -305,16 +303,13 @@ After a successful build, you can install _scrcpy_ on the system:
sudo ninja -Cx install # without sudo on Windows sudo ninja -Cx install # without sudo on Windows
``` ```
This installs several files: This installs three files:
- `/usr/local/bin/scrcpy` (main app) - `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device) - `/usr/local/share/scrcpy/scrcpy-server`
- `/usr/local/share/man/man1/scrcpy.1` (manpage) - `/usr/local/share/man/man1/scrcpy.1`
- `/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`. You can then [run](README.md#run) _scrcpy_.
### Uninstall ### Uninstall

98
FAQ.md
View File

@ -4,16 +4,23 @@
Here are the common reported problems and their status. 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` issues
`scrcpy` execute `adb` commands to initialize the connection with the device. If `scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work. `adb` fails, then scrcpy will not work.
In that case, it will print this error:
> ERROR: "adb push" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment. 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 ### `adb` not found
@ -23,63 +30,38 @@ 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. in the release, so it should work out-of-the-box.
### Device unauthorized
Check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected ### Device not detected
> ERROR: Could not find any ADB device > adb: error: failed to get feature set: no devices/emulators found
Check that you correctly enabled [adb debugging][enable-adb]. Check that you correctly enabled [adb debugging][enable-adb].
Your device must be detected by `adb`: If your device is not detected, you may need some [drivers] (on Windows).
```
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 [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
[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 ### Several devices connected
If several devices are connected, you will encounter this error: If several devices are connected, you will encounter this error:
ERROR: Multiple (2) ADB devices: > adb: error: failed to get feature set: more than one device/emulator
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 the identifier of the device you want to mirror must be provided:
mirror:
```bash ```bash
scrcpy -s 0123456789abcdef scrcpy -s 01234567890abcdef
``` ```
Or request the single USB (or TCP/IP) device: Note that if your device is connected over TCP/IP, you'll get this message:
```bash
scrcpy -d # USB device
scrcpy -e # TCP/IP device
```
Note that if your device is connected over TCP/IP, you might get this message:
> adb: error: more than one device/emulator > adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1 > ERROR: "adb reverse" returned with value 1
@ -158,24 +140,22 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40 [#40]: https://github.com/Genymobile/scrcpy/issues/40
This problem should be fixed in scrcpy v1.22: **update to the latest version**. To improve downscaling quality, trilinear filtering is enabled automatically
if the renderer is OpenGL and if it supports mipmapping.
On older versions, you must configure the [scaling behavior]: On Windows, you might want to force OpenGL:
```
scrcpy --render-driver=opengl
```
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_. > Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [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 ### Issue with Wayland
@ -239,9 +219,6 @@ scrcpy -m 1024
scrcpy -m 800 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). You could also try another [encoder](README.md#encoder).
@ -268,15 +245,8 @@ Caused by: java.lang.IllegalArgumentException: displayToken must not be null
## Command line on Windows ## Command line on Windows
Since v1.22, a "shortcut" has been added to directly open a terminal in the Some Windows users are not familiar with the command line. Here is how to open a
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your terminal and run `scrcpy` with arguments:
command. For example:
```
scrcpy --record file.mkv
```
You could also open a terminal and go to the scrcpy folder manually:
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box. 1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal. 2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.
@ -315,4 +285,4 @@ This FAQ is available in other languages:
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md) - [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)

View File

@ -1,12 +1,7 @@
_Only the original [FAQ.md](FAQ.md) is guaranteed to be up-to-date._ 只有原版的[FAQ](FAQ.md)会保持更新。
本文根据[d6aaa5]翻译。
_只有原版的 [FAQ.md](FAQ.md)是保证最新的。_ [d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md
Current version is based on [28054cd]
本文根据[28054cd]进行翻译。
[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md
# 常见问题 # 常见问题
@ -14,11 +9,11 @@ Current version is based on [28054cd]
## `adb` 相关问题 ## `adb` 相关问题
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果 `adb` 执行失败了, scrcpy 就无法工作。 `scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果`adb` 执行失败了, scrcpy 就无法工作。
在这种情况中,将会输出这个错误: 在这种情况中,将会输出这个错误:
> ERROR: "adb get-serialno" returned with value 1 > ERROR: "adb push" returned with value 1
这通常不是 _scrcpy_ 的bug而是你的环境的问题。 这通常不是 _scrcpy_ 的bug而是你的环境的问题。
@ -38,37 +33,28 @@ 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 [device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### 未检测到设备 ### 未检测到设备
> error: no devices/emulators found > adb: error: failed to get feature set: no devices/emulators found
确认已经正确启用 [adb debugging][enable-adb]. 确认已经正确启用 [adb debugging][enable-adb].
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver]. 如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html [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 > adb: error: failed to get feature set: more than one device/emulator
必须提供要镜像的设备的标识符: 必须提供要镜像的设备的标识符:
@ -104,19 +90,19 @@ scrcpy
### 设备断开连接 ### 设备断开连接
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。 如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
请尝试使用另一条USB线或者电脑上的另一个USB接口。
请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。 请参看 [#281] 和 [#283]。
[#281]: https://github.com/Genymobile/scrcpy/issues/281 [#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283 [#283]: https://github.com/Genymobile/scrcpy/issues/283
## 控制相关问题 ## 控制相关问题
### 鼠标和键盘不起作用 ### 鼠标和键盘不起作用
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。 在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
在开发者选项中,打开: 在开发者选项中,打开:
> **USB调试 (安全设置)** > **USB调试 (安全设置)**
@ -129,12 +115,10 @@ scrcpy
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。 可输入的文本[被限制为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 [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37 [#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## 客户端相关问题 ## 客户端相关问题
@ -145,6 +129,7 @@ scrcpy
[#40]: https://github.com/Genymobile/scrcpy/issues/40 [#40]: https://github.com/Genymobile/scrcpy/issues/40
为了提升降尺度的质量如果渲染器是OpenGL并且支持mip映射就会自动开启三线性过滤。 为了提升降尺度的质量如果渲染器是OpenGL并且支持mip映射就会自动开启三线性过滤。
在Windows上你可能希望强制使用OpenGL 在Windows上你可能希望强制使用OpenGL
@ -192,7 +177,6 @@ scrcpy
## 崩溃 ## 崩溃
### 异常 ### 异常
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码: 可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
> ``` > ```
@ -220,40 +204,12 @@ scrcpy -m 1024
scrcpy -m 800 scrcpy -m 800
``` ```
自 scrcpy v1.22以来scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。
你也可以尝试另一种 [编码器](README.md#encoder)。 你也可以尝试另一种 [编码器](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命令行
从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如 一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`
```
scrcpy --record file.mkv
```
您也可以打开终端并手动转到 scrcpy 文件夹:
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。 1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。 2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
@ -277,7 +233,7 @@ scrcpy --record file.mkv
scrcpy --prefer-text --turn-screen-off --stay-awake scrcpy --prefer-text --turn-screen-off --stay-awake
``` ```
然后只需双击刚刚创建的文件。 然后双击刚刚创建的文件。
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。 你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。

View File

@ -188,7 +188,7 @@
identification within third-party archives. identification within third-party archives.
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

File diff suppressed because it is too large Load Diff

View File

@ -218,7 +218,7 @@ variation] does not impact the recorded file.
#### Wireless #### 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. 1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status). 2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
@ -281,7 +281,7 @@ Dari terminal lain:
scrcpy 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 ```bash
adb kill-server # matikan server adb lokal di 5037 adb kill-server # matikan server adb lokal di 5037
@ -579,7 +579,7 @@ Lihat juga [Masalah #14].
Dalam daftar berikut, <kbd>MOD</kbd> adalah pengubah pintasan. Secara default, ini (kiri) <kbd>Alt</kbd> atau (kiri) <kbd>Super</kbd>. Dalam daftar berikut, <kbd>MOD</kbd> adalah pengubah pintasan. Secara default, ini (kiri) <kbd>Alt</kbd> atau (kiri) <kbd>Super</kbd>.
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 ```bash
# gunakan RCtrl untuk jalan pintas # gunakan RCtrl untuk jalan pintas
@ -672,7 +672,7 @@ Baca [halaman pengembang].
## Lisensi ## Lisensi
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,42 +1,23 @@
_Apri il [README](README.md) originale (in inglese) e sempre aggiornato._ _Apri il [README](README.md) originale e sempre aggiornato._
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> # scrcpy (v1.19)
# scrcpy (v1.23) 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_.
_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_. Funziona su _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
Si concentra su: Si concentra su:
- **leggerezza**: nativo, mostra solo lo schermo del dispositivo - **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
- **prestazioni**: 30~120fps, in funzione del dispositivo - **prestazioni** (30~60fps)
- **qualità**: 1920×1080 o superiore - **qualità** (1920×1080 o superiore)
- **bassa latenza**: [35~70ms][lowlatency] - **bassa latenza** ([35~70ms][lowlatency])
- **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine - **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
- **non invadenza**: nulla rimane installato sul dispositivo - **non invadenza** (nulla viene lasciato 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 [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 ## Requisiti
@ -68,18 +49,12 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si
### Linux ### Linux
Su Debian e Ubuntu: Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
``` ```
apt install scrcpy apt install scrcpy
``` ```
Su Arch Linux:
```
pacman -S scrcpy
```
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link]. È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy [snap-link]: https://snapstats.org/snaps/scrcpy
@ -91,6 +66,10 @@ Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr [COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ [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]. Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
@ -163,7 +142,7 @@ Collega un dispositivo Android ed esegui:
scrcpy scrcpy
``` ```
Scrcpy accetta argomenti da riga di comando, elencati con: Scrcpy accetta argomenti da riga di comando, essi sono listati con:
```bash ```bash
scrcpy --help scrcpy --help
@ -207,14 +186,6 @@ scrcpy --max-fps 15
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti. 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 <kbd>MOD</kbd>+<kbd>i</kbd>.
#### Ritaglio #### Ritaglio
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso. Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
@ -287,7 +258,7 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
#### v4l2loopback #### 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: Il modulo `v4l2loopback` deve essere installato:
@ -350,72 +321,42 @@ scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
e per il V4L2 sink: e per il V4L2 sink:
```bash ```bash
scrcpy --v4l2-buffer=500 # aggiungi 500 ms di buffer per il v4l2 sink scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink
``` ```
### Connessione ### Connessione
#### TCP/IP (wireless) #### 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 ```bash
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`. 3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
5. Scollega il tuo dispositivo. 4. Scollega il tuo dispositivo.
6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP` 5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
con l'indirizzo IP del dispositivo che hai trovato)_. 6. Esegui `scrcpy` come al solito.
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 ```bash
scrcpy --bit-rate 2M --max-size 800 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 [connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi dispositivo #### Multi dispositivo
Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_: Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
```bash ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
@ -429,18 +370,6 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versione breve 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. Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
@ -454,77 +383,37 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [AutoAdb]: https://github.com/rom1v/autoadb
#### Tunnels #### Tunnel SSH
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 ```bash
adb kill-server adb kill-server # termina il server adb locale su 5037
adb -a nodaemon server start ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# tienilo aperto # tieni questo aperto
``` ```
**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.** Da un altro terminale:
Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy:
```bash ```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 scrcpy
``` ```
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`) Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
```bash ```bash
# local 5038 --> remote 5037 adb kill-server # termina il server adb locale su 5037
# local 27183 --> remote 27183 ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# tieni questo aperto # tieni questo aperto
``` ```
Da un altro terminale, esegui scrcpy: Da un altro terminale:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
Come per le connessioni wireless potrebbe essere utile ridurre la qualità: Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
``` ```
@ -662,14 +551,6 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Spegnimento alla chiusura
Per spegnere lo schermo del dispositivo quando si chiude scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Mostrare i tocchi #### Mostrare i tocchi
@ -715,22 +596,20 @@ Qualsiasi scorciatoia <kbd>Ctrl</kbd> viene inoltrata al dispositivo. In partico
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia - <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo) - <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
Questo solitamente funziona come ci si aspetta. Questo solitamente funziona nella maniera più comune.
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio. Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7): Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> invia `COPY` - <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> invia `CUT` - <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo) - <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'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. In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII.
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera. **AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi inviino il testo degli appunti del computer come una sequenza di eventi di pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>). Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`.
#### Pizzica per zoomare (pinch-to-zoom) #### Pizzica per zoomare (pinch-to-zoom)
@ -738,98 +617,16 @@ Per simulare il "pizzica per zoomare": <kbd>Ctrl</kbd>+_click e trascina_.
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo. Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
Concretamente, scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo. 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). #### Preferenze di iniezione del testo
I tasti speciali di cattura, <kbd>Alt</kbd> o <kbd>Super</kbd>, 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: Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato; - _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
- _eventi di testo_, segnalano che del testo è stato inserito. - _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). In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con: Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
@ -839,21 +636,13 @@ scrcpy --prefer-text
(ma questo romperà il normale funzionamento della tastiera nei giochi) (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 [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ripetizione di tasti #### 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: Per prevenire l'inoltro ripetuto degli eventi di pressione:
@ -861,12 +650,9 @@ Per prevenire l'inoltro ripetuto degli eventi di pressione:
scrcpy --no-key-repeat 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 #### 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: In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
```bash ```bash
scrcpy --forward-all-clicks scrcpy --forward-all-clicks
@ -919,7 +705,7 @@ scrcpy --shortcut-mod=rctrl
scrcpy --shortcut-mod=lctrl+lalt,lsuper scrcpy --shortcut-mod=lctrl+lalt,lsuper
``` ```
_<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows [Super]: https://it.wikipedia.org/wiki/Tasto_Windows
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano --> <!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
@ -934,7 +720,7 @@ _<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kb
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_ | Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_ | Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_ | Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd> | Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_ | Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_ | Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd> | Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -945,20 +731,17 @@ _<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kb
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_ | Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_ | Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd> | Copia negli appunti | <kbd>MOD</kbd>+<kbd>c</kbd>
| Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd> | Taglia negli appunti | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizza gli appunti e incolla | <kbd>MOD</kbd>+<kbd>v</kbd> | Sincronizza gli appunti e incolla | <kbd>MOD</kbd>+<kbd>v</kbd>
| Invia il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_ | Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
| 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._ _¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ _²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._ _³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._
_⁵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": Le scorciatoie con pulsanti ripetuti sono eseguite rilasciando e premendo il pulsante una seconda volta. Per esempio, per eseguire "Espandi il pannello delle impostazioni":
@ -1007,7 +790,7 @@ Leggi la [pagina per sviluppatori].
## Licenza (in inglese) ## Licenza (in inglese)
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -1028,14 +811,3 @@ Leggi la [pagina per sviluppatori].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ [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-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)

View File

@ -776,7 +776,7 @@ _⁴Android 7以上のみ._
## ライセンス ## ライセンス
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스 ## 라이선스
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

396
README.md
View File

@ -1,13 +1,11 @@
# scrcpy (v1.24) # scrcpy (v1.20)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_pronounced "**scr**een **c**o**py**"_
[Read in another language](#translations) [Read in another language](#translations)
This application provides display and control of Android devices connected via 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](#wireless)). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _macOS_. It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
@ -19,7 +17,7 @@ It focuses on:
- **quality**: 1920×1080 or above - **quality**: 1920×1080 or above
- **low latency**: [35~70ms][lowlatency] - **low latency**: [35~70ms][lowlatency]
- **low startup time**: ~1 second to display the first image - **low startup time**: ~1 second to display the first image
- **non-intrusiveness**: nothing is left installed on the Android device - **non-intrusiveness**: nothing is left installed on the device
- **user benefits**: no account, no ads, no internet required - **user benefits**: no account, no ads, no internet required
- **freedom**: free and open source software - **freedom**: free and open source software
@ -27,25 +25,24 @@ It focuses on:
Its features include: Its features include:
- [recording](#recording) - [recording](#recording)
- mirroring with [Android device screen off](#turn-screen-off) - mirroring with [device screen off](#turn-screen-off)
- [copy-paste](#copy-paste) in both directions - [copy-paste](#copy-paste) in both directions
- [configurable quality](#capture-configuration) - [configurable quality](#capture-configuration)
- Android device [as a webcam (V4L2)](#v4l2loopback) (Linux-only) - device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid) - [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid) (Linux-only)
- [OTG mode](#otg)
- and more… - and more…
## Requirements ## Requirements
The Android device requires at least API 21 (Android 5.0). The Android device requires at least API 21 (Android 5.0).
Make sure you [enable adb debugging][enable-adb] on your device(s). Make sure you [enabled adb 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/command-line/adb.html#Enabling
On some devices, you also need to enable [an additional option][control] to On some devices, you also need to enable [an additional option][control] to
control it using a keyboard and mouse. control it using keyboard and mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@ -68,18 +65,12 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple])
### Linux ### Linux
On Debian and Ubuntu: On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
``` ```
apt install scrcpy apt install scrcpy
``` ```
On Arch Linux:
```
pacman -S scrcpy
```
A [Snap] package is available: [`scrcpy`][snap-link]. A [Snap] package is available: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy [snap-link]: https://snapstats.org/snaps/scrcpy
@ -91,25 +82,29 @@ For Fedora, a [COPR] package is available: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr [COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ [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]. For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You can also [build the app manually][BUILD] ([simplified You could also [build the app manually][BUILD] ([simplified
process][BUILD_simple]). process][BUILD_simple]).
### Windows ### Windows
For Windows, a prebuilt archive with all the dependencies (including `adb`) is For Windows, for simplicity, a prebuilt archive with all the dependencies
available: (including `adb`) is available:
- [`scrcpy-win64-v1.24.zip`][direct-win64] - [`scrcpy-win64-v1.20.zip`][direct-win64]
<sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub> _(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_
[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.20/scrcpy-win64-v1.20.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -148,7 +143,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet:
brew install android-platform-tools 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 ```bash
sudo port install scrcpy sudo port install scrcpy
@ -162,7 +157,7 @@ You can also [build the app manually][BUILD].
## Run ## Run
Plug an Android device into your computer, and execute: Plug an Android device, and execute:
```bash ```bash
scrcpy scrcpy
@ -180,7 +175,7 @@ scrcpy --help
#### Reduce size #### Reduce size
Sometimes, it is useful to mirror an Android device at a lower resolution to Sometimes, it is useful to mirror an Android device at a lower definition to
increase performance. increase performance.
To limit both the width and height to some value (e.g. 1024): To limit both the width and height to some value (e.g. 1024):
@ -190,8 +185,8 @@ scrcpy --max-size 1024
scrcpy -m 1024 # short version scrcpy -m 1024 # short version
``` ```
The other dimension is computed so that the Android device aspect ratio is The other dimension is computed to that the device aspect ratio is preserved.
preserved. That way, a device in 1920×1080 will be mirrored at 1024×576. That way, a device in 1920×1080 will be mirrored at 1024×576.
#### Change bit-rate #### Change bit-rate
@ -213,20 +208,11 @@ scrcpy --max-fps 15
This is officially supported since Android 10, but may work on earlier versions. 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 <kbd>MOD</kbd>+<kbd>i</kbd>.
#### Crop #### Crop
The device screen may be cropped to mirror only part of the screen. 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 ```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
@ -237,6 +223,7 @@ If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation #### Lock video orientation
To lock the orientation of the mirroring: To lock the orientation of the mirroring:
```bash ```bash
@ -261,7 +248,7 @@ crash. It is possible to select a different encoder:
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 To list the available encoders, you could pass an invalid encoder name, the
error will give the available encoders: error will give the available encoders:
```bash ```bash
@ -325,7 +312,7 @@ v4l2-ctl --list-devices
ls /dev/video* ls /dev/video*
``` ```
To start `scrcpy` using a v4l2 sink: To start scrcpy using a v4l2 sink:
```bash ```bash
scrcpy --v4l2-sink=/dev/videoN scrcpy --v4l2-sink=/dev/videoN
@ -333,7 +320,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version scrcpy --v4l2-sink=/dev/videoN -N # short version
``` ```
(replace `N` with the device ID, check with `ls /dev/video*`) (replace `N` by the device ID, check with `ls /dev/video*`)
Once enabled, you can open your video stream with a v4l2-capable tool: Once enabled, you can open your video stream with a v4l2-capable tool:
@ -349,7 +336,7 @@ For example, you could capture the video within [OBS].
#### Buffering #### Buffering
It is possible to add buffering. This increases latency, but reduces jitter (see It is possible to add buffering. This increases latency but reduces jitter (see
[#2464]). [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464 [#2464]: https://github.com/Genymobile/scrcpy/issues/2464
@ -369,66 +356,25 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
### Connection ### Connection
#### TCP/IP (wireless) #### Wireless
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a _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 device over TCP/IP:
computer.
##### Automatic 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
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, 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. 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: executing this command:
```bash ```bash
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`. 3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
5. Unplug your device. 4. Unplug your device.
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP` 5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
with the device IP address you found)_. 6. Run `scrcpy` as usual.
7. Run `scrcpy` as usual.
Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass It may be useful to decrease the bit-rate and the definition:
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 ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
@ -440,16 +386,13 @@ scrcpy -b2M -m800 # short version
#### Multi-devices #### Multi-devices
If several devices are listed in `adb devices`, you can specify the _serial_: If several devices are listed in `adb devices`, you must specify the _serial_:
```bash ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version 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: If the device is connected over TCP/IP:
```bash ```bash
@ -457,19 +400,6 @@ scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version 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. You can start several instances of _scrcpy_ for several devices.
#### Autostart on device connection #### Autostart on device connection
@ -482,55 +412,19 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [AutoAdb]: https://github.com/rom1v/autoadb
#### Tunnels #### SSH tunnel
To connect to a remote device, it is possible to connect a local `adb` client to 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_ a remote `adb` server (provided they use the same version of the _adb_
protocol). protocol).
##### Remote ADB server First, make sure the ADB server is running on the remote computer:
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
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 an
SSH tunnel.
First, make sure the _adb server_ is running on the remote computer:
```bash ```bash
adb start-server adb start-server
``` ```
Then, establish an SSH tunnel: Then, establish a SSH tunnel:
```bash ```bash
# local 5038 --> remote 5037 # local 5038 --> remote 5037
@ -539,7 +433,7 @@ ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run `scrcpy`: From another terminal, run scrcpy:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038 export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -556,7 +450,7 @@ ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run `scrcpy`: From another terminal, run scrcpy:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038 export ADB_SERVER_SOCKET=tcp:localhost:5038
@ -598,7 +492,7 @@ scrcpy --window-borderless
#### Always on top #### Always on top
To keep the _scrcpy_ window always on top: To keep the scrcpy window always on top:
```bash ```bash
scrcpy --always-on-top scrcpy --always-on-top
@ -623,7 +517,7 @@ The window may be rotated:
scrcpy --rotation 1 scrcpy --rotation 1
``` ```
Possible values: Possibles values are:
- `0`: no rotation - `0`: no rotation
- `1`: 90 degrees counterclockwise - `1`: 90 degrees counterclockwise
- `2`: 180 degrees - `2`: 180 degrees
@ -672,19 +566,19 @@ adb shell dumpsys display # search "mDisplayId=" in the output
``` ```
The secondary display may only be controlled if the device runs at least Android The secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored as read-only). 10 (otherwise it is mirrored in read-only).
#### Stay awake #### Stay awake
To prevent the device from sleeping after a delay when the device is plugged in: To prevent the device to sleep after some delay when the device is plugged in:
```bash ```bash
scrcpy --stay-awake scrcpy --stay-awake
scrcpy -w scrcpy -w
``` ```
The initial state is restored when _scrcpy_ is closed. The initial state is restored when scrcpy is closed.
#### Turn screen off #### Turn screen off
@ -702,10 +596,9 @@ Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>. To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
On Android, the `POWER` button always turns the screen on. For convenience, if On Android, the `POWER` button always turns the screen on. For convenience, if
`POWER` is sent via _scrcpy_ (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), `POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
it will force to turn the screen off after a small delay (on a best effort will force to turn the screen off after a small delay (on a best effort basis).
basis). The physical `POWER` button will still cause the screen to be turned The physical `POWER` button will still cause the screen to be turned on.
on.
It can also be useful to prevent the device from sleeping: It can also be useful to prevent the device from sleeping:
@ -716,22 +609,12 @@ scrcpy -Sw
#### Power off on close #### Power off on close
To turn the device screen off when closing _scrcpy_: To turn the device screen off when closing scrcpy:
```bash ```bash
scrcpy --power-off-on-close 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 #### Show touches
@ -748,13 +631,12 @@ scrcpy --show-touches
scrcpy -t scrcpy -t
``` ```
Note that it only shows _physical_ touches (by a finger on the device). Note that it only shows _physical_ touches (with the finger on the device).
#### Disable screensaver #### Disable screensaver
By default, _scrcpy_ does not prevent the screensaver from running on the By default, scrcpy does not prevent the screensaver to run on the computer.
computer.
To disable it: To disable it:
@ -796,53 +678,47 @@ To copy, cut and paste in such cases (but only supported on Android >= 7):
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device - <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
clipboard synchronization) clipboard synchronization)
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> injects the computer In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
clipboard text as a sequence of key events. This is useful when the component computer clipboard text as a sequence of key events. This is useful when the
does not accept text pasting (for example in _Termux_), but it can break component does not accept text pasting (for example in _Termux_), but it can
non-ASCII content. break non-ASCII content.
**WARNING:** Pasting the computer clipboard to the device (either via **WARNING:** Pasting the computer clipboard to the device (either via
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content <kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
into the Android clipboard. As a consequence, any Android application could read into the device clipboard. As a consequence, any Android application could read
its content. You should avoid pasting sensitive content (like passwords) that its content. You should avoid to paste sensitive content (like passwords) that
way. way.
Some Android devices do not behave as expected when setting the device clipboard Some devices do not behave as expected when setting the device clipboard
programmatically. An option `--legacy-paste` is provided to change the behavior programmatically. An option `--legacy-paste` is provided to change the behavior
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
also inject the computer clipboard text as a sequence of key events (the same also inject the computer clipboard text as a sequence of key events (the same
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>). way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`.
#### Pinch-to-zoom #### Pinch-to-zoom
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_. To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button. More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
Until the left-click button is released, all mouse movements scale and rotate the left-click button is released, all mouse movements scale and rotate the
the content (if supported by the app) relative to the center of the screen. content (if supported by the app) relative to the center of the screen.
Technically, _scrcpy_ generates additional touch events from a "virtual finger" Concretely, scrcpy generates additional touch events from a "virtual finger" at
at a location inverted through the center of the screen. a location inverted through the center of the screen.
#### Physical keyboard simulation (HID) #### Physical keyboard simulation (HID)
By default, _scrcpy_ uses Android key or text injection: it works everywhere, By default, scrcpy uses Android key or text injection: it works everywhere, but
but is limited to ASCII. is limited to ASCII.
Alternatively, `scrcpy` can simulate a physical USB keyboard on Android to On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a
provide a better input experience (using [USB HID over AOAv2][hid-aoav2]): the better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
virtual keyboard is disabled and it works for all characters and IME. keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support [hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected via USB. However, it only works if the device is connected by USB, and is currently only
supported on Linux.
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: To enable this mode:
@ -853,7 +729,7 @@ scrcpy -K # short version
If it fails for some reason (for example because the device is not connected via 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 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 console). This allows to use the same command line options when connected over
USB and TCP/IP. USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently In this mode, raw key events (scancodes) are sent to the device, independently
@ -861,79 +737,12 @@ 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 must be configured on the Android device, in Settings → System → Languages and
input → [Physical keyboard]. 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 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 <kbd>Alt</kbd> or <kbd>Super</kbd>, 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 #### Text injection preference
Two kinds of [events][textevents] are generated when typing text: There are two kinds of [events][textevents] generated when typing text:
- _key events_, signaling that a key is pressed or released; - _key events_, signaling that a key is pressed or released;
- _text events_, signaling that a text has been entered. - _text events_, signaling that a text has been entered.
@ -949,13 +758,7 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games) (but this will break keyboard behavior in games)
On the contrary, you could force to always inject raw key events: This option has no effect on HID keyboard (all key events are sent as
```bash
scrcpy --raw-key-events
```
These options have no effect on HID keyboard (all key events are sent as
scancodes in this mode). scancodes in this mode).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
@ -1051,7 +854,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_ | Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_ | Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_ | Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd> | Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_ | Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_ | Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd> | Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -1062,9 +865,9 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_ | Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_ | Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard | <kbd>MOD</kbd>+<kbd>c</kbd> | Copy to clipboard | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard | <kbd>MOD</kbd>+<kbd>x</kbd> | Cut to clipboard | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronize clipboards and paste | <kbd>MOD</kbd>+<kbd>v</kbd> | Synchronize clipboards and paste | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_ | Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
@ -1074,10 +877,9 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._
_³4th and 5th mouse buttons, if your mouse has them._ _³4th and 5th mouse buttons, if your mouse has them._
_⁴For react-native apps in development, `MENU` triggers development menu._ _⁴Only on Android >= 7._
_⁵Only on Android >= 7._
Shortcuts with repeated keys are executed by releasing and pressing the key a Shortcuts with repeated keys are executted by releasing and pressing the key a
second time. For example, to execute "Expand settings panel": second time. For example, to execute "Expand settings panel":
1. Press and keep pressing <kbd>MOD</kbd>. 1. Press and keep pressing <kbd>MOD</kbd>.
@ -1090,7 +892,7 @@ handled by the active application.
## Custom paths ## 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`: `ADB`:
```bash ```bash
@ -1103,7 +905,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`. To override the icon, configure its path in `SCRCPY_ICON_PATH`.
## Why the name _scrcpy_? ## Why _scrcpy_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet]. A colleague challenged me to find a name as unpronounceable as [gnirehtet].
@ -1120,9 +922,7 @@ See [BUILD].
## Common issues ## Common issues
See the [FAQ]. See the [FAQ](FAQ.md).
[FAQ]: FAQ.md
## Developers ## Developers
@ -1135,7 +935,7 @@ Read the [developers page].
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -1157,29 +957,17 @@ Read the [developers page].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/ [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-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
This README is available in other languages: This README is available in other languages:
- [Deutsch (German, `de`) - v1.22](README.de.md)
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [Italiano (Italiano, `it`) - v1.23](README.it.md) - [Italiano (Italiano, `it`) - v1.19](README.it.md)
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md) - [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.21](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md)

View File

@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
## Licença ## Licença
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,36 +1,24 @@
Solo se garantiza que el archivo [README](README.md) original esté actualizado. Solo se garantiza que el archivo [README](README.md) original esté actualizado.
# scrcpy (v1.21) # scrcpy (v1.17)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> Esta aplicación proporciona imagen y control de un dispositivo Android conectado
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
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_. Compatible con _GNU/Linux_, _Windows_ y _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
Se enfoca en: Sus características principales son:
- **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo
- **rendimiento**: 30~120fps, dependiendo del dispositivo - **ligero** (nativo, solo muestra la imagen del dispositivo)
- **calidad**: 1920×1080 o superior - **desempeño** (30~60fps)
- **baja latencia**: [35~70ms][lowlatency] - **calidad** (1920×1080 o superior)
- **inicio rápido**: ~1 segundo para mostrar la primera imagen - **baja latencia** ([35~70ms][lowlatency])
- **no intrusivo**: no deja nada instalado en el dispositivo - **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
- **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet - **no intrusivo** (no se deja nada instalado en el dispositivo)
- **libertad**: software gratis y de código abierto
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 [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 ## Requisitos
@ -63,7 +51,7 @@ Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
### Linux ### Linux
En Debian y Ubuntu: En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
``` ```
apt install scrcpy apt install scrcpy
@ -137,7 +125,7 @@ Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
brew install android-platform-tools brew install android-platform-tools
``` ```
También está disponible en [MacPorts], que configura el adb automáticamente: También está disponible en [MacPorts], que configura el adb automáticamente:
```bash ```bash
sudo port install scrcpy sudo port install scrcpy
@ -165,7 +153,7 @@ scrcpy --help
## Características ## Características
### Configuración de captura ### Capturar configuración
#### Reducir la definición #### Reducir la definición
@ -220,11 +208,10 @@ Si `--max-size` también está especificado, el cambio de tamaño es aplicado de
Para fijar la rotación de la transmisión: Para fijar la rotación de la transmisión:
```bash ```bash
scrcpy --lock-video-orientation # orientación inicial scrcpy --lock-video-orientation 0 # orientación normal
scrcpy --lock-video-orientation=0 # orientación normal scrcpy --lock-video-orientation 1 # 90° contrarreloj
scrcpy --lock-video-orientation=1 # 90° contrarreloj scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation=2 # 180° scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj
``` ```
Esto afecta la rotación de la grabación. Esto afecta la rotación de la grabación.
@ -246,10 +233,7 @@ Para listar los codificadores disponibles, puedes pasar un nombre de codificador
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Capturas y grabaciones ### Grabación
#### Grabación
Es posible grabar la pantalla mientras se transmite: Es posible grabar la pantalla mientras se transmite:
@ -266,117 +250,17 @@ scrcpy -Nr file.mkv
# interrumpe la grabación con Ctrl+C # 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 "Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
variation]" no impacta el archivo grabado. variation]" no impacta el archivo grabado.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [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 ### Conexión
#### TCP/IP (Inalámbrica) #### Inalámbrica
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP. _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. 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: 2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
@ -418,7 +302,7 @@ scrcpy -s 192.168.0.1:5555 # versión breve
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos. Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
#### Iniciar automáticamente al detectar dispositivo #### Autoiniciar al detectar dispositivo
Puedes utilizar [AutoAdb]: Puedes utilizar [AutoAdb]:
@ -428,82 +312,37 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [AutoAdb]: https://github.com/rom1v/autoadb
#### Túneles #### Túnel SSH
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 ```bash
adb kill-server adb kill-server # cierra el servidor local adb en 5037
adb -a nodaemon server start ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# conserva este servidor abierto # conserva este servidor abierto
``` ```
**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.** Desde otra terminal:
Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra
terminal, corré scrcpy:
```bash ```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 scrcpy
``` ```
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`): Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
```bash ```bash
# local 5038 --> remoto 5037 adb kill-server # cierra el servidor local adb en 5037
# local 27183 --> remoto 27183 ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# conserva este servidor abierto # conserva este servidor abierto
``` ```
Desde otra terminal, corré scrcpy: Desde otra terminal:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad: Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
``` ```
@ -563,7 +402,7 @@ Se puede rotar la ventana:
scrcpy --rotation 1 scrcpy --rotation 1
``` ```
Los posibles valores son: Los valores posibles son:
- `0`: sin rotación - `0`: sin rotación
- `1`: 90 grados contrarreloj - `1`: 90 grados contrarreloj
- `2`: 180 grados - `2`: 180 grados
@ -577,7 +416,7 @@ Nótese que _scrcpy_ maneja 3 diferentes rotaciones:
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación. - `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
### Otras opciones ### Otras opciones menores
#### Solo lectura ("Read-only") #### Solo lectura ("Read-only")
@ -640,12 +479,14 @@ scrcpy -Sw # versión breve
``` ```
#### Apagar al cerrar la aplicación #### Renderizar frames vencidos
Para apagar la pantalla del dispositivo al cerrar scrcpy: Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior.
Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use:
```bash ```bash
scrcpy --power-off-on-close scrcpy --render-expired-frames
``` ```
#### Mostrar clicks #### Mostrar clicks
@ -707,8 +548,6 @@ Además, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> 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 <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>). Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`.
#### Pellizcar para zoom #### Pellizcar para zoom
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_. Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
@ -717,48 +556,6 @@ Más precisamente, mantén <kbd>Ctrl</kbd> 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. 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 #### Preferencias de inyección de texto
@ -776,23 +573,13 @@ scrcpy --prefer-text
(Pero esto romperá el comportamiento del teclado en los juegos) (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 [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Repetir tecla #### Repetir tecla
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede 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.
causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
Para evitar enviar _key events_ repetidos: Para evitar enviar _key events_ repetidos:
@ -800,9 +587,6 @@ Para evitar enviar _key events_ repetidos:
scrcpy --no-key-repeat 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 #### Botón derecho y botón del medio
@ -824,15 +608,14 @@ No hay respuesta visual, un mensaje se escribirá en la consola.
#### Enviar archivos al dispositivo #### Enviar archivos al dispositivo
Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
un archivo (no APK) a la ventana de _scrcpy_.
No hay ninguna respuesta visual, un mensaje se escribirá en la consola. No hay respuesta visual, un mensaje se escribirá en la consola.
El directorio de destino puede ser modificado al iniciar: El directorio de destino puede ser modificado al iniciar:
```bash ```bash
scrcpy --push-target=/sdcard/Movies/ scrcpy --push-target=/sdcard/Download/
``` ```
@ -864,48 +647,36 @@ _<kbd>[Super]</kbd> es generalmente la tecla <kbd>Windows</kbd> o <kbd>Cmd</kbd>
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Acción | Atajo | Acción | Atajo
| ------------------------------------------- |:----------------------------- | ------------------------------------------- |:-----------------------------
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd> | Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_ | Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_ | Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd> | Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click izquierdo¹_ | Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click¹_
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click medio_ | Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click derecho²_ | Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Cuarto botón³_ | Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd> | Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_ | Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_ | Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd> | Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Encendido | _Botón derecho²_ | Encendido | _Botón derecho²_
| Apagar pantalla (manteniendo la transmisión) | <kbd>MOD</kbd>+<kbd>o</kbd> | Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</kbd>+<kbd>o</kbd>
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd> | Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Quinto botón³_ | Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
| Abrir panel de configuración | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doble quinto botón³_ | Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Cerrar paneles | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Copiar al portapapeles | <kbd>MOD</kbd>+<kbd>c</kbd> | Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Cortar al portapapeles | <kbd>MOD</kbd>+<kbd>x</kbd> | Synchronizar portapapeles y pegar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Synchronizar portapapeles y pegar⁵ | <kbd>MOD</kbd>+<kbd>v</kbd> | inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_ | Pellizcar para zoom | <kbd>Ctrl</kbd>+_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._ _¹Doble click en los bordes negros para eliminarlos._
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._ _²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
Cuarto y quinto botón del mouse, si tu mouse los tiene._ Solo en Android >= 7._
_⁴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 <kbd>MOD</kbd>.
2. Después apretá dos veces la tecla <kbd>n</kbd>.
3. Por último, soltá la tecla <kbd>MOD</kbd>.
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa. Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
@ -920,8 +691,6 @@ ADB=/path/to/adb scrcpy
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`. 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_? ## ¿Por qué _scrcpy_?
@ -951,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
## Licencia ## Licencia
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
## Lisans ## Lisans
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,46 +1,28 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._ _Only the original [README](README.md) is guaranteed to be up-to-date._
_只有原版的 [README](README.md)是保证最新的。_ 只有原版的[README](README.md)会保持最新。
Current version is based on [f4c7044] 本文根据[ed130e05]进行翻译。
本文根据[f4c7044]进行翻译。 [ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md
[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md # scrcpy (v1.17)
# scrcpy (v1.22)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_发音为 "**scr**een **c**o**py**"_
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_ 本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
本应用专注于: 专注于:
- **轻量** 原生,仅显示设备屏幕 - **轻量** (原生,仅显示设备屏幕)
- **性能** 30~120fps,取决于设备 - **性能** (30~60fps)
- **质量** 分辨率可达 1920×1080 或更高 - **质量** (分辨率可达 1920×1080 或更高)
- **低延迟** [35~70ms][lowlatency] - **低延迟** ([35~70ms][lowlatency])
- **快速启动** 最快 1 秒内即可显示第一帧 - **快速启动** (最快 1 秒内即可显示第一帧)
- **无侵入性** 不会在设备上遗留任何程序 - **无侵入性** (不会在设备上遗留任何程序)
- **用户利益** 无需帐号,无广告,无需联网
- **自由** 自由和开源软件
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
功能:
- [屏幕录制](#屏幕录制)
- 镜像时[关闭设备屏幕](#关闭设备屏幕)
- 双向[复制粘贴](#复制粘贴)
- [可配置显示质量](#采集设置)
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
- [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux)
- [OTG模式](#otg) (仅限 Linux)
- 更多 ……
## 系统要求 ## 系统要求
@ -59,31 +41,14 @@ _发音为 "**scr**een **c**o**py**"_
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a> <a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### 概要
- Linux: `apt install scrcpy`
- Windows: [下载][direct-win64]
- macOS: `brew install scrcpy`
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
在 Debian 和 Ubuntu 上: 在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上:
``` ```
apt install scrcpy apt install scrcpy
``` ```
在 Arch Linux 上:
```
pacman -S scrcpy
```
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。 我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
[snap-link]: https://snapstats.org/snaps/scrcpy [snap-link]: https://snapstats.org/snaps/scrcpy
@ -95,17 +60,23 @@ pacman -S scrcpy
[COPR]: https://fedoraproject.org/wiki/Category:Copr [COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/ [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]。 对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。 您也可以[自行构建][BUILD] (不必担心,这并不困难)。
### Windows ### Windows
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
- [README](README.md#windows) - [README](README.md#windows)
@ -143,17 +114,13 @@ brew install scrcpy
你还需要在 `PATH` 内有 `adb`。如果还没有: 你还需要在 `PATH` 内有 `adb`。如果还没有:
```bash ```bash
brew install android-platform-tools # Homebrew >= 2.6.0
brew install --cask 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]。 您也可以[自行构建][BUILD]。
@ -173,7 +140,7 @@ scrcpy --help
## 功能介绍 ## 功能介绍
### 采集设置 ### 捕获设置
#### 降低分辨率 #### 降低分辨率
@ -191,7 +158,7 @@ scrcpy -m 1024 # 简写
#### 修改码率 #### 修改码率
默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps) 默认码率是 8Mbps。改变视频码率 (例如改为 2Mbps)
```bash ```bash
scrcpy --bit-rate 2M scrcpy --bit-rate 2M
@ -200,7 +167,7 @@ scrcpy -b 2M # 简写
#### 限制帧率 #### 限制帧率
要限制采集的帧率: 要限制捕获的帧率:
```bash ```bash
scrcpy --max-fps 15 scrcpy --max-fps 15
@ -227,11 +194,10 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
要锁定镜像画面的方向: 要锁定镜像画面的方向:
```bash ```bash
scrcpy --lock-video-orientation # 初始(目前)方向 scrcpy --lock-video-orientation 0 # 自然方向
scrcpy --lock-video-orientation=0 # 自然方向 scrcpy --lock-video-orientation 1 # 逆时针旋转 90°
scrcpy --lock-video-orientation=1 # 逆时针旋转 9 scrcpy --lock-video-orientation 2 # 18
scrcpy --lock-video-orientation=2 # 18 scrcpy --lock-video-orientation 3 # 顺时针旋转 9
scrcpy --lock-video-orientation=3 # 顺时针旋转 90°
``` ```
只影响录制的方向。 只影响录制的方向。
@ -253,9 +219,7 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
scrcpy --encoder _ scrcpy --encoder _
``` ```
### 采集 ### 屏幕录制
#### 屏幕录制
可以在镜像的同时录制视频: 可以在镜像的同时录制视频:
@ -277,117 +241,24 @@ scrcpy -Nr file.mkv
[packet delay variation]: https://en.wikipedia.org/wiki/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 [连接]到设备(设备必须连接与电脑相同的网络)。 _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。 1. 将设备和电脑连接至同一 Wi-Fi。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: 2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
```bash ```bash
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
3. 启用设备的网络 adb 功能`adb tcpip 5555`。 3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
4. 断开设备的 USB 连接。 4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_ 5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_.
6. 正常运行 `scrcpy`。 6. 正常运行 `scrcpy`。
降低比特率和分辨率可能很有用 可能需要降低码率和分辨率
```bash ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
@ -425,80 +296,38 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [AutoAdb]: https://github.com/rom1v/autoadb
#### 隧道 #### SSH 隧道
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同) 要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)
##### 远程ADB服务器
要连接到一个远程ADB服务器让服务器在所有接口上监听
```bash ```bash
adb kill-server adb kill-server # 关闭本地 5037 端口上的 adb 服务端
adb -a nodaemon server start ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持该窗口开启 # 保持该窗口开启
``` ```
**警告所有客户端与ADB服务器的交流都是未加密的。** 在另一个终端:
假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy
```bash ```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 scrcpy
``` ```
若要不使用远程端口转发,可以强制使用正向连接注意 `-L` 而不是 `-R` 若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` `-R` 的区别)
```bash ```bash
# 本地 5038 --> 远程 5037 adb kill-server # 关闭本地 5037 端口上的 adb 服务端
# 本地 27183 <-- 远程 27183 ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持该窗口开启 # 保持该窗口开启
``` ```
在另一个终端运行scrcpy 在另一个终端:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
类似地,对于无线连接,可能需要降低画面质量: 类似无线网络连接,可能需要降低画面质量:
``` ```
scrcpy -b2M -m800 --max-fps 15 scrcpy -b2M -m800 --max-fps 15
@ -511,7 +340,7 @@ scrcpy -b2M -m800 --max-fps 15
窗口的标题默认为设备型号。可以通过如下命令修改: 窗口的标题默认为设备型号。可以通过如下命令修改:
```bash ```bash
scrcpy --window-title "我的设备" scrcpy --window-title 'My device'
``` ```
#### 位置和大小 #### 位置和大小
@ -524,7 +353,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### 无边框 #### 无边框
禁用窗口边框: 关闭边框:
```bash ```bash
scrcpy --window-borderless scrcpy --window-borderless
@ -540,7 +369,7 @@ scrcpy --always-on-top
#### 全屏 #### 全屏
您可以通过如下命令直接全屏启动 scrcpy 您可以通过如下命令直接全屏启动scrcpy
```bash ```bash
scrcpy --fullscreen scrcpy --fullscreen
@ -565,7 +394,7 @@ scrcpy --rotation 1
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。 也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
需要注意的是, _scrcpy_ 有三类旋转方向: 需要注意的是, _scrcpy_ 有三个不同的方向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。 - <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。 - [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。 - `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
@ -575,7 +404,7 @@ scrcpy --rotation 1
#### 只读 #### 只读
禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放) 禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放)
```bash ```bash
scrcpy --no-control scrcpy --no-control
@ -601,14 +430,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
#### 保持常亮 #### 保持常亮
阻止设备在连接时一段时间后休眠: 阻止设备在连接时休眠:
```bash ```bash
scrcpy --stay-awake scrcpy --stay-awake
scrcpy -w scrcpy -w
``` ```
scrcpy 关闭时会恢复设备原来的设置。 程序关闭时会恢复设备原来的设置。
#### 关闭设备屏幕 #### 关闭设备屏幕
@ -622,7 +451,7 @@ scrcpy -S
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。 或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> 要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。 在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
@ -633,17 +462,20 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### 退出时息屏
scrcpy 退出时关闭设备屏幕: #### 渲染过期帧
默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。
强制渲染所有帧 (可能导致延迟变高)
```bash ```bash
scrcpy --power-off-on-close scrcpy --render-expired-frames
``` ```
#### 显示触摸 #### 显示触摸
在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。 在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)
Android 在 _开发者选项_ 中提供了这项功能。 Android 在 _开发者选项_ 中提供了这项功能。
@ -700,91 +532,18 @@ scrcpy --disable-screensaver
一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd> 和 <kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。 一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd> 和 <kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。
要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。
#### 双指缩放 #### 双指缩放
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按下并拖动鼠标_。 模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
按住 <kbd>Ctrl</kbd> 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。 更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
具体来说_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 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
[实体键盘]: 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 设备上)。
特殊的捕获键,<kbd>Alt</kbd> 或 <kbd>Super</kbd>,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。
#### OTG #### 文字注入偏好
可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。 打字的时候,系统会产生两种[事件][textevents]
在这个模式下_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]
- _按键事件_ ,代表一个按键被按下或松开。 - _按键事件_ ,代表一个按键被按下或松开。
- _文本事件_ ,代表一个字符被输入。 - _文本事件_ ,代表一个字符被输入。
@ -796,15 +555,7 @@ scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
scrcpy --prefer-text scrcpy --prefer-text
``` ```
(这会导致键盘在游戏中工作不正常) (这会导致键盘在游戏中工作不正常)
相反,您可以强制始终注入原始按键事件:
```bash
scrcpy --raw-key-events
```
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
@ -812,7 +563,7 @@ scrcpy --raw-key-events
#### 按键重复 #### 按键重复
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。 默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。
避免转发重复按键事件: 避免转发重复按键事件:
@ -820,11 +571,10 @@ scrcpy --raw-key-events
scrcpy --no-key-repeat scrcpy --no-key-repeat
``` ```
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
#### 右键和中键 #### 右键和中键
默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: 默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
```bash ```bash
scrcpy --forward-all-clicks scrcpy --forward-all-clicks
@ -837,27 +587,27 @@ scrcpy --forward-all-clicks
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。 将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
不会有视觉反馈,终端会输出一条日志。 该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。
#### 将文件推送至设备 #### 将文件推送至设备
要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
不会有视觉反馈,终端会输出一条日志。 该操作没有可见的响应,只会在控制台输出日志。
在启动时可以修改目标目录: 在启动时可以修改目标目录:
```bash ```bash
scrcpy --push-target=/sdcard/Movies/ scrcpy --push-target /sdcard/foo/bar/
``` ```
### 音频转发 ### 音频转发
_Scrcpy_ 不支持音频。请使用 [sndcpy] _Scrcpy_ 不支持音频。请使用 [sndcpy].
[issue #14]。 外请阅读 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy [sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 [issue #14]: https://github.com/Genymobile/scrcpy/issues/14
@ -882,47 +632,36 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) [Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| 操作 | 快捷键 | 操作 | 快捷键 |
| --------------------------------- | :------------------------------------------- | --------------------------------- | :------------------------------------------- |
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd> | 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd> |
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ | 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ |
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ | 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ |
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd> | 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd> |
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击左键¹_ | 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击¹_ |
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_ | 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _鼠标中键_ |
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_ | 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _鼠标右键²_ |
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_ | 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> |
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> | 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> |
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ | 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ |
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ | 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ |
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> | 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> |
| 打开屏幕 | _鼠标右键²_ | 打开屏幕 | _鼠标右键²_ |
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> | 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> |
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> | 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> |
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_ | 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> |
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_ | 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd> |
| 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd> | 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd> |
| 剪切到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>x</kbd> | 同步剪贴板并粘贴³ | <kbd>MOD</kbd>+<kbd>v</kbd> |
| 同步剪贴板并粘贴⁵ | <kbd>MOD</kbd>+<kbd>v</kbd> | 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | 打开/关闭FPS显示 (在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> |
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd> | 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_ |
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
| 拖放 APK 文件 | 从电脑安装 APK 文件
| 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device)
_¹双击黑边可以去除黑边。_ _¹双击黑边可以去除黑边_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_ _²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_
鼠标的第4键和第5键。_ 需要安卓版本 Android >= 7。_
_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_
_⁵需要安卓版本 Android >= 7。_
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
1. 按下 <kbd>MOD</kbd> 不放。
2. 双击 <kbd>n</kbd>。
3. 松开 <kbd>MOD</kbd>。
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。 所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
@ -931,20 +670,18 @@ _⁵需要安卓版本 Android >= 7。_
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB` 要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`
```bash ADB=/path/to/adb scrcpy
ADB=/path/to/adb scrcpy
```
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。 要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。
要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。 [useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## 为什么叫 _scrcpy_ ## 为什么叫 _scrcpy_
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。 一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
[`strcpy`] 源于 **str**ing (字符串) `scrcpy` 源于 **scr**een (屏幕) [`strcpy`] 复制一个 **str**ing `scrcpy` 复制一个 **scr**een。
[gnirehtet]: https://github.com/Genymobile/gnirehtet [gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html [`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
@ -952,12 +689,14 @@ ADB=/path/to/adb scrcpy
## 如何构建? ## 如何构建?
请查看 [BUILD]。 请查看[BUILD]。
[BUILD]: BUILD.md
## 常见问题 ## 常见问题
请查看 [FAQ](FAQ.md)。 请查看[FAQ](FAQ.md)。
## 开发者 ## 开发者
@ -970,7 +709,7 @@ ADB=/path/to/adb scrcpy
## 许可协议 ## 许可协议
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -679,7 +679,7 @@ _³只支援 Android 7+。_
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2022 Romain Vimont Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,122 +0,0 @@
_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
--no-power-on
--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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -1 +0,0 @@
@cmd

View File

@ -1,70 +0,0 @@
#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]'
'--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]'
'--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

View File

@ -1,19 +1,16 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb/adb.c', 'src/adb.c',
'src/adb/adb_device.c', 'src/adb_tunnel.c',
'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c',
'src/cli.c', 'src/cli.c',
'src/clock.c', 'src/clock.c',
'src/compat.c', 'src/compat.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
'src/demuxer.c',
'src/device_msg.c', 'src/device_msg.c',
'src/icon.c', 'src/icon.c',
'src/file_pusher.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frame_buffer.c', 'src/frame_buffer.c',
'src/input_manager.c', 'src/input_manager.c',
@ -26,11 +23,9 @@ src = [
'src/scrcpy.c', 'src/scrcpy.c',
'src/screen.c', 'src/screen.c',
'src/server.c', 'src/server.c',
'src/version.c', 'src/stream.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/acksync.c',
'src/util/file.c', 'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c', 'src/util/intr.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
@ -44,76 +39,61 @@ src = [
'src/util/tick.c', 'src/util/tick.c',
] ]
conf = configuration_data()
conf.set('_POSIX_C_SOURCE', '200809L')
conf.set('_XOPEN_SOURCE', '700')
conf.set('_GNU_SOURCE', true)
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
windows = import('windows')
src += [ src += [
'src/sys/win/file.c', 'src/sys/win/file.c',
'src/sys/win/process.c', 'src/sys/win/process.c',
windows.compile_resources('scrcpy-windows.rc'),
] ]
conf.set('_WIN32_WINNT', '0x0600')
conf.set('WINVER', '0x0600')
else else
src += [ src += [
'src/sys/unix/file.c', 'src/sys/unix/file.c',
'src/sys/unix/process.c', 'src/sys/unix/process.c',
] ]
if host_machine.system() == 'darwin'
conf.set('_DARWIN_C_SOURCE', true)
endif
endif endif
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux' v4l2_support = host_machine.system() == 'linux'
if v4l2_support if v4l2_support
src += [ 'src/v4l2_sink.c' ] src += [ 'src/v4l2_sink.c' ]
endif endif
usb_support = get_option('usb') aoa_hid_support = host_machine.system() == 'linux'
if usb_support if aoa_hid_support
src += [ src += [
'src/usb/aoa_hid.c', 'src/aoa_hid.c',
'src/usb/hid_keyboard.c', 'src/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',
] ]
endif endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows' if not get_option('crossbuild_windows')
if not crossbuild_windows
# native build # native build
dependencies = [ dependencies = [
dependency('libavformat', version: '>= 57.33'), dependency('libavformat'),
dependency('libavcodec', version: '>= 57.37'), dependency('libavcodec'),
dependency('libavutil'), dependency('libavutil'),
dependency('sdl2', version: '>= 2.0.5'), dependency('sdl2'),
] ]
if v4l2_support if v4l2_support
dependencies += dependency('libavdevice') dependencies += dependency('libavdevice')
endif endif
if usb_support if aoa_hid_support
dependencies += dependency('libusb-1.0') dependencies += dependency('libusb-1.0')
endif endif
else else
# cross-compile mingw32 build (from Linux to Windows) # cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin' sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib' sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include' sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency( sdl2 = declare_dependency(
dependencies: [ dependencies: [
@ -123,40 +103,22 @@ else
include_directories: include_directories(sdl2_include_dir) include_directories: include_directories(sdl2_include_dir)
) )
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg') prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin' prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include' ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/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( ffmpeg = declare_dependency(
dependencies: [ dependencies: [
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir), cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir), cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir), cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
], ],
include_directories: include_directories(ffmpeg_include_dir) 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
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [ dependencies = [
ffmpeg, ffmpeg,
sdl2, sdl2,
libusb,
cc.find_library('mingw32') cc.find_library('mingw32')
] ]
@ -166,11 +128,7 @@ if host_machine.system() == 'windows'
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
endif endif
check_functions = [ conf = configuration_data()
'strdup',
'asprintf',
'vasprintf',
]
foreach f : check_functions foreach f : check_functions
if cc.has_function(f) if cc.has_function(f)
@ -179,16 +137,13 @@ foreach f : check_functions
endif endif
endforeach 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 # the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version()) conf.set_quoted('SCRCPY_VERSION', meson.project_version())
# the prefix used during configuration (meson --prefix=PREFIX) # the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix')) conf.set_quoted('PREFIX', get_option('prefix'))
# build a "portable" version (with scrcpy-server.apk accessible from the same # build a "portable" version (with scrcpy-server accessible from the same
# directory as the executable) # directory as the executable)
conf.set('PORTABLE', get_option('portable')) conf.set('PORTABLE', get_option('portable'))
@ -211,7 +166,7 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
conf.set('HAVE_V4L2', v4l2_support) conf.set('HAVE_V4L2', v4l2_support)
# enable HID over AOA support (linux only) # enable HID over AOA support (linux only)
conf.set('HAVE_USB', usb_support) conf.set('HAVE_AOA_HID', aoa_hid_support)
configure_file(configuration: conf, output: 'config.h') configure_file(configuration: conf, output: 'config.h')
@ -224,13 +179,9 @@ executable('scrcpy', src,
c_args: []) c_args: [])
install_man('scrcpy.1') install_man('scrcpy.1')
install_data('data/icon.png', install_data('../data/icon.png',
rename: 'scrcpy.png', rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps') 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 ### TESTS
@ -238,15 +189,8 @@ install_data('data/bash-completion/scrcpy',
# do not build tests in release (assertions would not be executed at all) # do not build tests in release (assertions would not be executed at all)
if get_option('buildtype') == 'debug' if get_option('buildtype') == 'debug'
tests = [ 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',
]],
['test_buffer_util', [ ['test_buffer_util', [
'tests/test_buffer_util.c', 'tests/test_buffer_util.c'
]], ]],
['test_cbuf', [ ['test_cbuf', [
'tests/test_cbuf.c', 'tests/test_cbuf.c',
@ -255,7 +199,6 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/options.c',
'src/util/net.c',
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
'src/util/term.c', 'src/util/term.c',
@ -286,9 +229,6 @@ if get_option('buildtype') == 'debug'
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
]], ]],
['test_vector', [
'tests/test_vector.c',
]],
] ]
foreach t : tests foreach t : tests

View File

@ -1 +0,0 @@
/data

View File

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

View File

@ -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-33.0.1
FILENAME=platform-tools_r33.0.1-windows.zip
SHA256SUM=c1f02d42ea24ef4ff2a405ae7370e764ef4546f9b3e4520f5571a00ed5012c42
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"

View File

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

View File

@ -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.0.1
DEP_DIR=ffmpeg-win64-$VERSION
FILENAME=ffmpeg-$VERSION-full_build-shared.7z
SHA256SUM=ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c
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"

View File

@ -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"
DEP_DIR=libusb-1.0.26
FILENAME=libusb-1.0.26-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/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" \
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

View File

@ -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=SDL2-2.0.22
FILENAME=SDL2-devel-2.0.22-mingw.tar.gz
SHA256SUM=0e91e35973366aa1e6f81ee368924d9b4f93f9da4d2f2a89ec80b06eadcf23d1
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/ \

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@ -1,23 +0,0 @@
#include <winuser.h>
0 ICON "data/icon.ico"
1 RT_MANIFEST "scrcpy-windows.manifest"
2 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.24"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View File

@ -43,12 +43,6 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size .B \-\-max\-size
value is computed on the cropped 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 .TP
.BI "\-\-disable-screensaver" .BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running. Disable screensaver while scrcpy is running.
@ -68,12 +62,6 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc
Default is 0 (no buffering). 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 .TP
.BI "\-\-encoder " name .BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder). Use a specific MediaCodec encoder (must be a H.264 encoder).
@ -100,15 +88,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. 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. 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).
Also see \fB\-\-hid\-mouse\fR.
.TP .TP
.B \-\-legacy\-paste .B \-\-legacy\-paste
@ -134,36 +114,6 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited). 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.
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.
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 .TP
.B \-n, \-\-no\-control .B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only). Disable device control (mirror the device in read\-only).
@ -180,24 +130,6 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps .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. 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.
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.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
.TP .TP
.BI "\-p, \-\-port " port[:port] .BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen. Set the TCP port (range) used by the client to listen.
@ -215,20 +147,12 @@ Inject alpha characters and space as text events instead of key events.
This avoids issues when combining multiple keys to enter special characters, This avoids issues when combining multiple keys to enter special characters,
but breaks the expected behavior of alpha keys in games (typically WASD). 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 .TP
.BI "\-\-push\-target " path .BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/Download/". Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP .TP
.BI "\-r, \-\-record " file .BI "\-r, \-\-record " file
Record screen to Record screen to
@ -269,14 +193,6 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
Default is "lalt,lsuper" (left-Alt or left-Super). 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 .TP
.B \-S, \-\-turn\-screen\-off .B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately. Turn the device screen off immediately.
@ -287,18 +203,6 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP
.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 .TP
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.
@ -359,12 +263,6 @@ Set the initial window height.
Default is 0 (automatic). 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 .SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left) In the following list, MOD is the shortcut modifier. By default, it's (left)
@ -479,19 +377,11 @@ Push file to device (see \fB\-\-push\-target\fR)
.TP .TP
.B ADB .B ADB
Path to adb. Specify the path to adb.
.TP
.B ANDROID_SERIAL
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
.TP .TP
.B SCRCPY_SERVER_PATH .B SCRCPY_SERVER_PATH
Path to the server binary. Specify the path to server binary.
.SH AUTHORS .SH AUTHORS
@ -516,7 +406,7 @@ Copyright \(co 2018 Genymobile
Genymobile Genymobile
.UE .UE
Copyright \(co 2018\-2022 Copyright \(co 2018\-2020
.MT rom@rom1v.com .MT rom@rom1v.com
Romain Vimont Romain Vimont
.ME .ME

316
app/src/adb.c Normal file
View File

@ -0,0 +1,316 @@
#include "adb.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/file.h"
#include "util/log.h"
#include "util/process_intr.h"
#include "util/str.h"
static const char *adb_command;
static inline const char *
get_adb_command(void) {
if (!adb_command) {
adb_command = getenv("ADB");
if (!adb_command)
adb_command = "adb";
}
return adb_command;
}
// serialize argv to string "[arg1], [arg2], [arg3]"
static size_t
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
size_t idx = 0;
bool first = true;
while (*argv) {
const char *arg = *argv;
size_t len = strlen(arg);
// count space for "[], ...\0"
if (idx + len + 8 >= bufsize) {
// not enough space, truncate
assert(idx < bufsize - 4);
memcpy(&buf[idx], "...", 3);
idx += 3;
break;
}
if (first) {
first = false;
} else {
buf[idx++] = ',';
buf[idx++] = ' ';
}
buf[idx++] = '[';
memcpy(&buf[idx], arg, len);
idx += len;
buf[idx++] = ']';
argv++;
}
assert(idx < bufsize);
buf[idx] = '\0';
return idx;
}
static void
show_adb_installation_msg() {
#ifndef __WINDOWS__
static const struct {
const char *binary;
const char *command;
} pkg_managers[] = {
{"apt", "apt install adb"},
{"apt-get", "apt-get install adb"},
{"brew", "brew cask install android-platform-tools"},
{"dnf", "dnf install android-tools"},
{"emerge", "emerge dev-util/android-tools"},
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (sc_file_executable_exists(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}
}
#endif
LOGI("You may download and install 'adb' from "
"https://developer.android.com/studio/releases/platform-tools");
}
static void
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)");
return;
}
switch (err) {
case SC_PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf);
break;
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 SC_PROCESS_SUCCESS:
// do nothing
break;
}
free(buf);
}
static sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, unsigned inherit, sc_pipe *pout) {
int i;
sc_pid pid;
const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) {
return SC_PROCESS_NONE;
}
argv[0] = get_adb_command();
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;
enum sc_process_result r =
sc_process_execute_p(argv, &pid, inherit, NULL, pout, NULL);
if (r != SC_PROCESS_SUCCESS) {
show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE;
}
free(argv);
return pid;
}
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned inherit) {
return adb_execute_p(serial, adb_cmd, len, inherit, NULL);
}
static sc_pid
adb_exec_forward(const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) {
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), inherit);
}
static sc_pid
adb_exec_forward_remove(const char *serial, uint16_t local_port,
unsigned inherit) {
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), inherit);
}
static sc_pid
adb_exec_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port, unsigned inherit) {
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), inherit);
}
static sc_pid
adb_exec_reverse_remove(const char *serial, const char *device_socket_name,
unsigned inherit) {
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), inherit);
}
static sc_pid
adb_exec_push(const char *serial, const char *local, const char *remote,
unsigned inherit) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = sc_str_quote(local);
if (!local) {
return SC_PROCESS_NONE;
}
remote = sc_str_quote(remote);
if (!remote) {
free((void *) local);
return SC_PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"push", local, remote};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return pid;
}
static sc_pid
adb_exec_install(const char *serial, const char *local, unsigned inherit) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
local = sc_str_quote(local);
if (!local) {
return SC_PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"install", "-r", local};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
#ifdef __WINDOWS__
free((void *) local);
#endif
return pid;
}
static sc_pid
adb_exec_get_serialno(unsigned inherit, sc_pipe *pout) {
const char *const adb_cmd[] = {"get-serialno"};
return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), inherit, pout);
}
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) {
sc_pid pid =
adb_exec_forward(serial, local_port, device_socket_name, inherit);
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, unsigned inherit) {
sc_pid pid = adb_exec_forward_remove(serial, local_port, inherit);
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,
unsigned inherit) {
sc_pid pid =
adb_exec_reverse(serial, device_socket_name, local_port, inherit);
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, unsigned inherit) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name, inherit);
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, unsigned inherit) {
sc_pid pid = adb_exec_push(serial, local, remote, inherit);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned inherit) {
sc_pid pid = adb_exec_install(serial, local, inherit);
return sc_process_check_success_intr(intr, pid, "adb install", true);
}
char *
adb_get_serialno(struct sc_intr *intr, unsigned inherit) {
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(inherit, &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);
}

48
app/src/adb.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef SC_ADB_H
#define SC_ADB_H
#include "common.h"
#include <stdbool.h>
#include <inttypes.h>
#include "util/intr.h"
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned inherit);
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit);
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned inherit);
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned inherit);
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned inherit);
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned inherit);
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned inherit);
/**
* 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, unsigned inherit);
#endif

View File

@ -1,822 +0,0 @@
#include "adb.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "adb_device.h"
#include "adb_parser.h"
#include "util/file.h"
#include "util/log.h"
#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 *
sc_adb_get_executable(void) {
if (!adb_executable) {
adb_executable = getenv("ADB");
if (!adb_executable)
adb_executable = "adb";
}
return adb_executable;
}
// serialize argv to string "[arg1], [arg2], [arg3]"
static size_t
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
size_t idx = 0;
bool first = true;
while (*argv) {
const char *arg = *argv;
size_t len = strlen(arg);
// count space for "[], ...\0"
if (idx + len + 8 >= bufsize) {
// not enough space, truncate
assert(idx < bufsize - 4);
memcpy(&buf[idx], "...", 3);
idx += 3;
break;
}
if (first) {
first = false;
} else {
buf[idx++] = ',';
buf[idx++] = ' ';
}
buf[idx++] = '[';
memcpy(&buf[idx], arg, len);
idx += len;
buf[idx++] = ']';
argv++;
}
assert(idx < bufsize);
buf[idx] = '\0';
return idx;
}
static void
show_adb_installation_msg() {
#ifndef __WINDOWS__
static const struct {
const char *binary;
const char *command;
} pkg_managers[] = {
{"apt", "apt install adb"},
{"apt-get", "apt-get install adb"},
{"brew", "brew cask install android-platform-tools"},
{"dnf", "dnf install android-tools"},
{"emerge", "emerge dev-util/android-tools"},
{"pacman", "pacman -S android-tools"},
};
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (sc_file_executable_exists(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return;
}
}
#endif
}
static void
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) {
LOG_OOM();
LOGE("Failed to execute");
return;
}
switch (err) {
case SC_PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf);
break;
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 SC_PROCESS_SUCCESS:
// do nothing
break;
}
free(buf);
}
static bool
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) {
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 (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;
}
return true;
}
static bool
process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
unsigned flags) {
if (intr && !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, flags);
if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
// Close separately
sc_process_close(pid);
return ret;
}
static sc_pid
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;
}
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, 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;
}
return pid;
}
sc_pid
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_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) {
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);
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "forward", local, remote);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb forward", flags);
}
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);
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "forward", "--remove", local);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb forward --remove", flags);
}
bool
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);
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "reverse", remote, local);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb reverse", flags);
}
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);
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "reverse", "--remove", remote);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb reverse --remove", flags);
}
bool
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)
local = sc_str_quote(local);
if (!local) {
return SC_PROCESS_NONE;
}
remote = sc_str_quote(remote);
if (!remote) {
free((void *) local);
return SC_PROCESS_NONE;
}
#endif
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "push", local, remote);
sc_pid pid = sc_adb_execute(argv, flags);
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return process_check_success_intr(intr, pid, "adb push", flags);
}
bool
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)
local = sc_str_quote(local);
if (!local) {
return SC_PROCESS_NONE;
}
#endif
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "install", "-r", local);
sc_pid pid = sc_adb_execute(argv, flags);
#ifdef __WINDOWS__
free((void *) local);
#endif
return process_check_success_intr(intr, pid, "adb install", flags);
}
bool
sc_adb_uninstall(struct sc_intr *intr, const char *serial, const char *pkg,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "uninstall", pkg);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb uninstall", flags);
}
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);
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "tcpip", port_string);
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 argv[] = SC_ADB_COMMAND("connect", ip_port);
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, 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) - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb connect", flags);
if (!ok) {
return false;
}
if (r == -1) {
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.
size_t len = strcspn(buf, "\r\n");
buf[len] = '\0';
fprintf(stderr, "%s\n", buf);
}
return ok;
}
bool
sc_adb_disconnect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
assert(ip_port);
const char *const argv[] = SC_ADB_COMMAND("disconnect", ip_port);
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb disconnect", flags);
}
static bool
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;
}
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 < 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 64Kb. "
"Please report an issue.");
return false;
}
#undef BUFSIZE
// It is parsed as a NUL-terminated string
buf[r] = '\0';
// List all devices to the output list directly
ok = sc_adb_parse_devices(buf, out_vec);
free(buf);
return ok;
}
static bool
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 !strcmp(selector->serial, device->serial);
case SC_ADB_DEVICE_SELECT_USB:
return sc_adb_device_get_type(device->serial) ==
SC_ADB_DEVICE_TYPE_USB;
case SC_ADB_DEVICE_SELECT_TCPIP:
// 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;
}
return false;
}
static size_t
sc_adb_devices_select(struct sc_adb_device *devices, size_t len,
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, selector);
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 ? "-->" : " ";
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 : "");
}
}
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: "
"<https://github.com/Genymobile/scrcpy/blob/master/FAQ.md>");
} else {
LOGE("Device could not be connected (state=%s)", state);
}
return false;
}
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_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 (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(vec.data, vec.size, selector, &sel_idx);
if (sel_count == 0) {
// 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;
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;
}
sc_adb_devices_log(SC_LOG_LEVEL_ERROR, vec.data, vec.size);
sc_adb_devices_destroy(&vec);
return false;
}
if (sel_count > 1) {
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;
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, vec.data, vec.size);
LOGE("Select a device via -s (--serial), -d (--select-usb) or -e "
"(--select-tcpip)");
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 = &vec.data[sel_idx];
ok = sc_adb_device_check_state(device, vec.data, vec.size);
if (!ok) {
sc_adb_devices_destroy(&vec);
return false;
}
LOGD("ADB device found:");
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(&vec);
return true;
}
char *
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "getprop", prop);
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, 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) - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "adb getprop", 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);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "ip", "route");
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, 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) - 1);
sc_pipe_close(pout);
bool ok = process_check_success_intr(intr, pid, "ip route", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return NULL;
}
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. "
"Please report an issue.");
return NULL;
}
// It is parsed as a NUL-terminated string
buf[r] = '\0';
return sc_adb_parse_device_ip(buf);
}
char *
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "pm", "list", "package", "-f",
SC_ANDROID_PACKAGE);
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGD("Could not execute \"pm list packages\"");
return NULL;
}
// "pm list packages -f <package>" output should contain only one line, so
// the output should be short
char buf[1024];
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, "pm list packages", flags);
if (!ok) {
return NULL;
}
if (r == -1) {
return NULL;
}
assert((size_t) r < sizeof(buf));
if (r == sizeof(buf) - 1) {
// The implementation assumes that the output of "pm list packages"
// fits in the buffer in a single pass
LOGW("Result of \"pm list package\" does not fit in 1Kb. "
"Please report an issue.");
return NULL;
}
// It is parsed as a NUL-terminated string
buf[r] = '\0';
return sc_adb_parse_installed_apk_path(buf);
}
char *
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
unsigned flags) {
assert(serial);
const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "shell", "dumpsys", "package",
SC_ANDROID_PACKAGE);
sc_pipe pout;
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
if (pid == SC_PROCESS_NONE) {
LOGD("Could not execute \"dumpsys package\"");
return NULL;
}
// "dumpsys package" output can be huge (e.g. 16k), but versionName is at
// the beginning, typically in the first 1024 bytes (64k should be enough
// for the whole output anyway)
#define BUFSIZE 65536
char *buf = malloc(BUFSIZE);
if (!buf) {
return false;
}
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, "dumpsys package", flags);
if (!ok) {
free(buf);
return NULL;
}
if (r == -1) {
free(buf);
return NULL;
}
assert((size_t) r < BUFSIZE);
#undef BUFSIZE
// if r == sizeof(buf), then the output is truncated, but we don't care,
// versionName is at the beginning in practice, and is unlikely to be
// truncated at 64k
// It is parsed as a NUL-terminated string
buf[r] = '\0';
char *version = sc_adb_parse_installed_apk_version(buf);
free(buf);
return version;
}

View File

@ -1,137 +0,0 @@
#ifndef SC_ADB_H
#define SC_ADB_H
#include "common.h"
#include <stdbool.h>
#include <inttypes.h>
#include "adb_device.h"
#include "util/intr.h"
#define SC_ADB_NO_STDOUT (1 << 0)
#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)
#define SC_ANDROID_PACKAGE "com.genymobile.scrcpy"
const char *
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 {
enum sc_adb_device_selector_type type;
const char *serial;
};
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_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);
bool
sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags);
bool
sc_adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned flags);
bool
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags);
bool
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned flags);
bool
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned flags);
bool
sc_adb_uninstall(struct sc_intr *intr, const char *serial, const char *pkg,
unsigned flags);
/**
* Execute `adb tcpip <port>`
*/
bool
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
unsigned flags);
/**
* Execute `adb connect <ip_port>`
*
* `ip_port` may not be NULL.
*/
bool
sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags);
/**
* Execute `adb disconnect [<ip_port>]`
*
* If `ip_port` is NULL, execute `adb disconnect`.
* Otherwise, execute `adb disconnect <ip_port>`.
*/
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 struct sc_adb_device_selector *selector,
unsigned flags, struct sc_adb_device *out_device);
/**
* Execute `adb getprop <prop>`
*/
char *
sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
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 *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
/**
* Return the path of the installed APK for com.genymobile.scrcpy (if any)
*/
char *
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
unsigned flags);
/**
* Return the version of the installed APK for com.genymobile.scrcpy (if any)
*/
char *
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
unsigned flags);
#endif

View File

@ -1,43 +0,0 @@
#include "adb_device.h"
#include <stdlib.h>
#include <string.h>
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(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);
}
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;
}

View File

@ -1,50 +0,0 @@
#ifndef SC_ADB_DEVICE_H
#define SC_ADB_DEVICE_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include "util/vector.h"
struct sc_adb_device {
char *serial;
char *state;
char *model;
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
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(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

View File

@ -1,286 +0,0 @@
#include "adb_parser.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#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;
}
device->selected = false;
return true;
}
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;
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';
struct sc_adb_device device;
bool ok = sc_adb_parse_device(line, &device);
if (!ok) {
continue;
}
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;
}
}
assert(header_found || out_vec->size == 0);
return header_found;
}
static char *
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"
// 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];
size_t dev_name_len = strcspn(dev_name, " \t");
dev_name[dev_name_len] = '\0';
char *ip = &line[idx_ip];
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)) {
LOGD("Device ip lookup: ignoring %s (%s)", ip, dev_name);
return NULL;
}
return strdup(ip);
}
char *
sc_adb_parse_device_ip(char *str) {
size_t idx_line = 0;
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);
if (ip) {
// Found
return ip;
}
idx_line += len;
if (str[idx_line] != '\0') {
// The next line starts after the '\n'
++idx_line;
}
}
return NULL;
}
char *
sc_adb_parse_installed_apk_path(char *str) {
// str is expected to look like:
// "package:/data/app/.../base.apk=com.genymobile.scrcpy"
// ^^^^^^^^^^^^^^^^^^^^^^
// We want to extract the path (which may contain '=', even in practice)
if (strncmp(str, "package:", 8)) {
// Does not start with "package:"
return NULL;
}
char *s = str + 8;
size_t len = strcspn(s, " \r\n");
s[len] = '\0';
char *p = strrchr(s, '=');
if (!p) {
// No '=' found
return NULL;
}
// Truncate at the last '='
*p = '\0';
return strdup(s);
}
char *
sc_adb_parse_installed_apk_version(const char *str) {
// str is the (beginning of the) output of `dumpsys package`
// We want to extract the version string from a line starting with 4 spaces
// then `versionName=` then the version string.
#define VERSION_NAME_PREFIX "\n versionName="
char *s = strstr(str, VERSION_NAME_PREFIX);
if (!s) {
// Not found
return NULL;
}
s+= sizeof(VERSION_NAME_PREFIX) - 1;
size_t len = strspn(s, "0123456789.");
if (!len) {
LOGW("Unexpected version name with no value");
return NULL;
}
char *version = malloc(len + 1);
if (!version) {
return NULL;
}
memcpy(version, s, len);
version[len] = '\0';
return version;
}

View File

@ -1,50 +0,0 @@
#ifndef SC_ADB_PARSER_H
#define SC_ADB_PARSER_H
#include "common.h"
#include <stddef.h>
#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.
*/
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`
*
* The parameter must be a NUL-terminated string.
*
* Warning: this function modifies the buffer for optimization purposes.
*/
char *
sc_adb_parse_device_ip(char *str);
/**
* Parse the package path from the output of
* `adb shell pm list packages -f <package>`
*
* The parameter must be a NUL-terminated string.
*
* Warning: this function modifies the buffer for optimization purposes.
*/
char *
sc_adb_parse_installed_apk_path(char *str);
/**
* Parse the package version from the output of
* `adb shell dumpsys package <package>`
*
* The parameter must be a NUL-terminated string.
*/
char *
sc_adb_parse_installed_apk_version(const char *str);
#endif

View File

@ -20,8 +20,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_port_range port_range) { struct sc_port_range port_range) {
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port, if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port, SC_STDERR)) {
SC_ADB_NO_STDOUT)) {
// the command itself failed, it will fail on any port // the command itself failed, it will fail on any port
return false; return false;
} }
@ -52,8 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
} }
// failure, disable tunnel and try another port // failure, disable tunnel and try another port
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR)) {
SC_ADB_NO_STDOUT)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port); LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
} }
@ -83,8 +81,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME, if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_STDERR)) {
SC_ADB_NO_STDOUT)) {
// success // success
tunnel->local_port = port; tunnel->local_port = port;
tunnel->enabled = true; tunnel->enabled = true;
@ -149,11 +146,9 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
bool ret; bool ret;
if (tunnel->forward) { if (tunnel->forward) {
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port, ret = adb_forward_remove(intr, serial, tunnel->local_port, SC_STDERR);
SC_ADB_NO_STDOUT);
} else { } else {
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME, ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR);
SC_ADB_NO_STDOUT);
assert(tunnel->server_socket != SC_SOCKET_NONE); assert(tunnel->server_socket != SC_SOCKET_NONE);
if (!net_close(tunnel->server_socket)) { if (!net_close(tunnel->server_socket)) {

View File

@ -4,7 +4,6 @@
#include <stdio.h> #include <stdio.h>
#include "aoa_hid.h" #include "aoa_hid.h"
#include "util/log.h"
// See <https://source.android.com/devices/accessories/aoa2#hid-support>. // See <https://source.android.com/devices/accessories/aoa2#hid-support>.
#define ACCESSORY_REGISTER_HID 54 #define ACCESSORY_REGISTER_HID 54
@ -21,7 +20,6 @@ sc_hid_event_log(const struct sc_hid_event *event) {
unsigned buffer_size = event->size * 3 + 1; unsigned buffer_size = event->size * 3 + 1;
char *buffer = malloc(buffer_size); char *buffer = malloc(buffer_size);
if (!buffer) { if (!buffer) {
LOG_OOM();
return; return;
} }
for (unsigned i = 0; i < event->size; ++i) { for (unsigned i = 0; i < event->size; ++i) {
@ -37,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->accessory_id = accessory_id;
hid_event->buffer = buffer; hid_event->buffer = buffer;
hid_event->size = buffer_size; hid_event->size = buffer_size;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID; hid_event->delay = 0;
} }
void void
@ -45,9 +43,82 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer); 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 bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
struct sc_acksync *acksync) {
cbuf_init(&aoa->queue); cbuf_init(&aoa->queue);
if (!sc_mutex_init(&aoa->mutex)) { if (!sc_mutex_init(&aoa->mutex)) {
@ -59,9 +130,31 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
return false; 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; aoa->stopped = false;
aoa->acksync = acksync;
aoa->usb = usb;
return true; return true;
} }
@ -74,6 +167,9 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
sc_hid_event_destroy(&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_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex); sc_mutex_destroy(&aoa->mutex);
} }
@ -90,12 +186,11 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
uint16_t index = report_desc_size; uint16_t index = report_desc_size;
unsigned char *buffer = NULL; unsigned char *buffer = NULL;
uint16_t length = 0; uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb->handle, request_type, int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
request, value, index, buffer, length, value, index, buffer, length,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result)); log_libusb_error((enum libusb_error) result);
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@ -127,12 +222,11 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
// libusb_control_transfer expects a pointer to non-const // libusb_control_transfer expects a pointer to non-const
unsigned char *buffer = (unsigned char *) report_desc; unsigned char *buffer = (unsigned char *) report_desc;
uint16_t length = report_desc_size; uint16_t length = report_desc_size;
int result = libusb_control_transfer(aoa->usb->handle, request_type, int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
request, value, index, buffer, length, value, index, buffer, length,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result)); log_libusb_error((enum libusb_error) result);
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@ -170,12 +264,11 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
uint16_t index = 0; uint16_t index = 0;
unsigned char *buffer = event->buffer; unsigned char *buffer = event->buffer;
uint16_t length = event->size; uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb->handle, request_type, int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
request, value, index, buffer, length, value, index, buffer, length,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result)); log_libusb_error((enum libusb_error) result);
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@ -193,12 +286,11 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
uint16_t index = 0; uint16_t index = 0;
unsigned char *buffer = NULL; unsigned char *buffer = NULL;
uint16_t length = 0; uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb->handle, request_type, int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
request, value, index, buffer, length, value, index, buffer, length,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); log_libusb_error((enum libusb_error) result);
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@ -240,33 +332,23 @@ run_aoa_thread(void *data) {
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
uint64_t ack_to_wait = event.ack_to_wait; assert(event.delay >= 0);
sc_mutex_unlock(&aoa->mutex); if (event.delay) {
// Wait during the specified delay before injecting the HID event
if (ack_to_wait != SC_SEQUENCE_INVALID) { sc_tick deadline = sc_tick_now() + event.delay;
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); bool timed_out = false;
while (!aoa->stopped && !timed_out) {
// If some events have ack_to_wait set, then sc_aoa must have been timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
// initialized with a non NULL acksync deadline);
assert(aoa->acksync); }
if (aoa->stopped) {
// Do not block the loop indefinitely if the ack never comes (it sc_mutex_unlock(&aoa->mutex);
// 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; break;
} }
} }
sc_mutex_unlock(&aoa->mutex);
bool ok = sc_aoa_send_hid_event(aoa, &event); bool ok = sc_aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event); sc_hid_event_destroy(&event);
if (!ok) { if (!ok) {
@ -280,9 +362,9 @@ bool
sc_aoa_start(struct sc_aoa *aoa) { sc_aoa_start(struct sc_aoa *aoa) {
LOGD("Starting AOA thread"); LOGD("Starting AOA thread");
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "scrcpy-aoa", aoa); bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
if (!ok) { if (!ok) {
LOGE("Could not start AOA thread"); LOGC("Could not start AOA thread");
return false; return false;
} }
@ -295,10 +377,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
aoa->stopped = true; aoa->stopped = true;
sc_cond_signal(&aoa->event_cond); sc_cond_signal(&aoa->event_cond);
sc_mutex_unlock(&aoa->mutex); sc_mutex_unlock(&aoa->mutex);
if (aoa->acksync) {
sc_acksync_interrupt(aoa->acksync);
}
} }
void void

View File

@ -6,8 +6,6 @@
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
#include "usb.h"
#include "util/acksync.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h" #include "util/thread.h"
#include "util/tick.h" #include "util/tick.h"
@ -16,7 +14,7 @@ struct sc_hid_event {
uint16_t accessory_id; uint16_t accessory_id;
unsigned char *buffer; unsigned char *buffer;
uint16_t size; uint16_t size;
uint64_t ack_to_wait; sc_tick delay;
}; };
// Takes ownership of buffer // Takes ownership of buffer
@ -30,18 +28,18 @@ 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 CBUF(struct sc_hid_event, 64);
struct sc_aoa { struct sc_aoa {
struct sc_usb *usb; libusb_context *usb_context;
libusb_device *usb_device;
libusb_device_handle *usb_handle;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond event_cond; sc_cond event_cond;
bool stopped; bool stopped;
struct sc_hid_event_queue queue; struct sc_hid_event_queue queue;
struct sc_acksync *acksync;
}; };
bool bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync); sc_aoa_init(struct sc_aoa *aoa, const char *serial);
void void
sc_aoa_destroy(struct sc_aoa *aoa); sc_aoa_destroy(struct sc_aoa *aoa);

View File

@ -9,7 +9,6 @@
#include "options.h" #include "options.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h"
#include "util/str.h" #include "util/str.h"
#include "util/strbuf.h" #include "util/strbuf.h"
#include "util/term.h" #include "util/term.h"
@ -47,18 +46,6 @@
#define OPT_V4L2_SINK 1027 #define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028 #define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029 #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_INSTALL 1040
#define OPT_REINSTALL 1041
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@ -78,16 +65,6 @@ struct sc_shortcut {
const char *text; const char *text;
}; };
struct sc_envvar {
const char *name;
const char *text;
};
struct sc_exit_status {
unsigned value;
const char *text;
};
struct sc_getopt_adapter { struct sc_getopt_adapter {
char *optstring; char *optstring;
struct option *longopts; struct option *longopts;
@ -126,13 +103,7 @@ static const struct sc_option options[] = {
.text = "Crop the device screen on the server.\n" .text = "Crop the device screen on the server.\n"
"The values are expressed in the device natural orientation " "The values are expressed in the device natural orientation "
"(typically, portrait for a phone, landscape for a tablet). " "(typically, portrait for a phone, landscape for a tablet). "
"Any --max-size value is computed on the cropped size.", "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_id = OPT_DISABLE_SCREENSAVER,
@ -157,12 +128,6 @@ static const struct sc_option options[] = {
"This increases latency to compensate for jitter.\n" "This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).", "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_id = OPT_ENCODER_NAME,
.longopt = "encoder", .longopt = "encoder",
@ -194,27 +159,14 @@ static const struct sc_option options[] = {
"It provides a better experience for IME users, and allows to " "It provides a better experience for IME users, and allows to "
"generate non-ASCII characters, contrary to the default " "generate non-ASCII characters, contrary to the default "
"injection method.\n" "injection method.\n"
"It may only work over USB.\n" "It may only work over USB, and is currently only supported "
"The keyboard layout must be configured (once and for all) on " "on Linux.",
"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.",
}, },
{ {
.shortopt = 'h', .shortopt = 'h',
.longopt = "help", .longopt = "help",
.text = "Print this help.", .text = "Print this help.",
}, },
{
.longopt_id = OPT_INSTALL,
.longopt = "install",
.text = "Install the server (via 'adb install') instead of just "
"pushing it (via 'adb push').",
},
{ {
.longopt_id = OPT_LEGACY_PASTE, .longopt_id = OPT_LEGACY_PASTE,
.longopt = "legacy-paste", .longopt = "legacy-paste",
@ -244,17 +196,6 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported " .text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).", "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.\n"
"Also see --hid-keyboard.",
},
{ {
.shortopt = 'm', .shortopt = 'm',
.longopt = "max-size", .longopt = "max-size",
@ -264,30 +205,6 @@ static const struct sc_option options[] = {
"is preserved.\n" "is preserved.\n"
"Default is 0 (unlimited).", "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",
.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."
},
{
.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', .shortopt = 'n',
.longopt = "no-control", .longopt = "no-control",
@ -296,8 +213,11 @@ static const struct sc_option options[] = {
{ {
.shortopt = 'N', .shortopt = 'N',
.longopt = "no-display", .longopt = "no-display",
.text = "Do not display device (only when screen recording or V4L2 " .text = "Do not display device (only when screen recording "
"sink is enabled).", #ifdef HAVE_V4L2
"or V4L2 sink "
#endif
"is enabled).",
}, },
{ {
.longopt_id = OPT_NO_KEY_REPEAT, .longopt_id = OPT_NO_KEY_REPEAT,
@ -311,26 +231,6 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling " "mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.", "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",
.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"
"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.\n"
"See --hid-keyboard and --hid-mouse.",
},
{ {
.shortopt = 'p', .shortopt = 'p',
.longopt = "port", .longopt = "port",
@ -347,18 +247,12 @@ static const struct sc_option options[] = {
{ {
.longopt_id = OPT_PREFER_TEXT, .longopt_id = OPT_PREFER_TEXT,
.longopt = "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" "key events.\n"
"This avoids issues when combining multiple keys to enter a " "This avoids issues when combining multiple keys to enter a "
"special character, but breaks the expected behavior of alpha " "special character, but breaks the expected behavior of alpha "
"keys in games (typically WASD).", "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_id = OPT_PUSH_TARGET,
.longopt = "push-target", .longopt = "push-target",
@ -367,11 +261,6 @@ static const struct sc_option options[] = {
"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/\".", "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', .shortopt = 'r',
.longopt = "record", .longopt = "record",
@ -386,13 +275,6 @@ static const struct sc_option options[] = {
.argdesc = "format", .argdesc = "format",
.text = "Force recording format (either mp4 or mkv).", .text = "Force recording format (either mp4 or mkv).",
}, },
{
.longopt_id = OPT_REINSTALL,
.longopt = "reinstall",
.text = "Reinstall the server (via 'adb install'), even if the correct "
"version is already installed.\n"
"Implies --install.",
},
{ {
.longopt_id = OPT_RENDER_DRIVER, .longopt_id = OPT_RENDER_DRIVER,
.longopt = "render-driver", .longopt = "render-driver",
@ -448,47 +330,14 @@ static const struct sc_option options[] = {
"on exit.\n" "on exit.\n"
"It only shows physical touches (not clicks from scrcpy).", "It only shows physical touches (not clicks from scrcpy).",
}, },
{ #ifdef HAVE_V4L2
.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",
.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.",
},
{ {
.longopt_id = OPT_V4L2_SINK, .longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink", .longopt = "v4l2-sink",
.argdesc = "/dev/videoN", .argdesc = "/dev/videoN",
.text = "Output to v4l2loopback device.\n" .text = "Output to v4l2loopback device.\n"
"It requires to lock the video orientation (see " "It requires to lock the video orientation (see "
"--lock-video-orientation).\n" "--lock-video-orientation).",
"This feature is only available on Linux.",
}, },
{ {
.longopt_id = OPT_V4L2_BUFFER, .longopt_id = OPT_V4L2_BUFFER,
@ -498,9 +347,9 @@ static const struct sc_option options[] = {
"frames. This increases latency to compensate for jitter.\n" "frames. This increases latency to compensate for jitter.\n"
"This option is similar to --display-buffer, but specific to " "This option is similar to --display-buffer, but specific to "
"V4L2 sink.\n" "V4L2 sink.\n"
"Default is 0 (no buffering).\n" "Default is 0 (no buffering).",
"This option is only available on Linux.",
}, },
#endif
{ {
.shortopt = 'V', .shortopt = 'V',
.longopt = "verbosity", .longopt = "verbosity",
@ -598,7 +447,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Click on BACK", .text = "Click on BACK",
}, },
{ {
.shortcuts = { "MOD+s", "4th-click" }, .shortcuts = { "MOD+s" },
.text = "Click on APP_SWITCH", .text = "Click on APP_SWITCH",
}, },
{ {
@ -634,7 +483,7 @@ static const struct sc_shortcut shortcuts[] = {
.text = "Rotate device screen", .text = "Rotate device screen",
}, },
{ {
.shortcuts = { "MOD+n", "5th-click" }, .shortcuts = { "MOD+n" },
.text = "Expand notification panel", .text = "Expand notification panel",
}, },
{ {
@ -676,41 +525,6 @@ static const struct sc_shortcut shortcuts[] = {
}, },
}; };
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=<addr>) is specified",
},
{
.name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon",
},
{
.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 * static char *
sc_getopt_adapter_create_optstring(void) { sc_getopt_adapter_create_optstring(void) {
struct sc_strbuf buf; struct sc_strbuf buf;
@ -749,7 +563,6 @@ sc_getopt_adapter_create_longopts(void) {
struct option *longopts = struct option *longopts =
malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts)); malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts));
if (!longopts) { if (!longopts) {
LOG_OOM();
return NULL; return NULL;
} }
@ -804,7 +617,7 @@ sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) {
} }
return true; return true;
} };
static void static void
sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) { sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) {
@ -902,7 +715,7 @@ print_shortcuts_intro(unsigned cols) {
return; return;
} }
printf("\n%s\n", intro); printf("%s\n", intro);
free(intro); free(intro);
} }
@ -930,42 +743,6 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
free(text); 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("<ERROR>\n");
return;
}
printf("%s\n", text);
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("<ERROR>\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 void
scrcpy_print_usage(const char *arg0) { scrcpy_print_usage(const char *arg0) {
#define SC_TERM_COLS_DEFAULT 80 #define SC_TERM_COLS_DEFAULT 80
@ -993,22 +770,11 @@ scrcpy_print_usage(const char *arg0) {
} }
// Print shortcuts section // Print shortcuts section
printf("\nShortcuts:\n"); printf("\nShortcuts:\n\n");
print_shortcuts_intro(cols); print_shortcuts_intro(cols);
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
print_shortcut(&shortcuts[i], cols); 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);
}
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 static bool
@ -1251,7 +1017,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") // item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error) // returns a bitwise-or of SC_MOD_* constants (or 0 on error)
static unsigned static unsigned
parse_shortcut_mods_item(const char *item, size_t len) { parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0; unsigned mod = 0;
@ -1269,17 +1035,17 @@ parse_shortcut_mods_item(const char *item, size_t len) {
((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) ((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) { if (STREQ("lctrl", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LCTRL; mod |= SC_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) { } else if (STREQ("rctrl", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RCTRL; mod |= SC_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) { } else if (STREQ("lalt", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LALT; mod |= SC_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) { } else if (STREQ("ralt", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RALT; mod |= SC_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) { } else if (STREQ("lsuper", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LSUPER; mod |= SC_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) { } else if (STREQ("rsuper", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RSUPER; mod |= SC_MOD_RSUPER;
} else { } else {
LOGE("Unknown modifier key: %.*s " LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
@ -1361,21 +1127,6 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
return false; 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 static enum sc_record_format
guess_record_format(const char *filename) { guess_record_format(const char *filename) {
size_t len = strlen(filename); size_t len = strlen(filename);
@ -1415,12 +1166,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case 'd':
opts->select_usb = true;
break;
case 'e':
opts->select_tcpip = true;
break;
case 'f': case 'f':
opts->fullscreen = true; opts->fullscreen = true;
break; break;
@ -1436,13 +1181,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true; args->help = true;
break; break;
case 'K': case 'K':
#ifdef HAVE_USB
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
break; break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
return false;
#endif
case OPT_MAX_FPS: case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) { if (!parse_max_fps(optarg, &opts->max_fps)) {
return false; return false;
@ -1453,30 +1193,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case 'M':
#ifdef HAVE_USB
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
return false;
#endif
case OPT_LOCK_VIDEO_ORIENTATION: case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) { &opts->lock_video_orientation)) {
return false; return false;
} }
break; 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': case 'n':
opts->control = false; opts->control = false;
break; break;
@ -1548,18 +1270,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->push_target = optarg; opts->push_target = optarg;
break; break;
case OPT_PREFER_TEXT: case OPT_PREFER_TEXT:
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) { opts->prefer_text = true;
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; break;
case OPT_ROTATION: case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) { if (!parse_rotation(optarg, &opts->rotation)) {
@ -1606,58 +1317,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case OPT_NO_CLIPBOARD_AUTOSYNC:
opts->clipboard_autosync = false;
break;
case OPT_TCPIP:
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
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;
case OPT_INSTALL:
opts->install = true;
break;
case OPT_REINSTALL:
opts->install = true;
opts->reinstall = true;
break;
case OPT_OTG:
#ifdef HAVE_USB
opts->otg = true;
break;
#else
LOGE("OTG mode (--otg) is disabled.");
return false;
#endif
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
case OPT_V4L2_SINK:
opts->v4l2_device = optarg; opts->v4l2_device = optarg;
break; break;
#else
LOGE("V4L2 (--v4l2-sink) is disabled (or unsupported on this "
"platform).");
return false;
#endif
case OPT_V4L2_BUFFER: case OPT_V4L2_BUFFER:
#ifdef HAVE_V4L2
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) { if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false; return false;
} }
break; break;
#else
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
return false;
#endif #endif
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
@ -1665,28 +1333,6 @@ 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;
}
// If a TCP/IP address is provided, then tcpip must be enabled
assert(opts->tcpip || !opts->tcpip_dst);
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=<addr> (with an argument)");
return false;
}
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (!opts->display && !opts->record_filename && !opts->v4l2_device) { if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)" LOGE("-N/--no-display requires either screen recording (-r/--record)"
@ -1694,18 +1340,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
if (opts->v4l2_device) { if (opts->v4l2_device && opts->lock_video_orientation
if (opts->lock_video_orientation == == SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { LOGI("Video orientation is locked for v4l2 sink. "
LOGI("Video orientation is locked for v4l2 sink. " "See --lock-video-orientation.");
"See --lock-video-orientation."); opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
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) { if (opts->v4l2_buffer && !opts->v4l2_device) {
@ -1719,10 +1358,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
#endif #endif
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { int index = optind;
LOGI("Tunnel host/port is set, " if (index < argc) {
"--force-adb-forward automatically enabled."); LOGE("Unexpected additional argument: %s", argv[index]);
opts->force_adb_forward = true; return false;
} }
if (opts->record_format && !opts->record_filename) { if (opts->record_format && !opts->record_filename) {
@ -1740,73 +1379,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
} }
if (!opts->control) { if (!opts->control && opts->turn_screen_off) {
if (opts->turn_screen_off) { LOGE("Could not request to turn screen off if control is disabled");
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;
}
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;
}
}
#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; return false;
} }
# endif
if (opts->otg) { if (!opts->control && opts->stay_awake) {
// OTG mode is compatible with only very few options. LOGE("Could not request to stay awake if control is disabled");
// Only report obvious errors. return false;
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; return true;
} }

View File

@ -1,5 +1,5 @@
#ifndef SC_COMMON_H #ifndef COMMON_H
#define SC_COMMON_H #define COMMON_H
#include "config.h" #include "config.h"
#include "compat.h" #include "compat.h"
@ -7,7 +7,6 @@
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
#define container_of(ptr, type, member) \ #define container_of(ptr, type, member) \
((type *) (((char *) (ptr)) - offsetof(type, member))) ((type *) (((char *) (ptr)) - offsetof(type, member)))

View File

@ -2,12 +2,6 @@
#include "config.h" #include "config.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifndef HAVE_STRDUP #ifndef HAVE_STRDUP
char *strdup(const char *s) { char *strdup(const char *s) {
size_t size = strlen(s) + 1; size_t size = strlen(s) + 1;
@ -18,36 +12,3 @@ char *strdup(const char *s) {
return dup; return dup;
} }
#endif #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

View File

@ -1,19 +1,16 @@
#ifndef SC_COMPAT_H #ifndef COMPAT_H
#define SC_COMPAT_H #define COMPAT_H
#include "config.h" #define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.h>
#ifndef __WIN32
# define PRIu64_ PRIu64
# define SC_PRIsizet "zu"
#else
# define PRIu64_ "I64u" // Windows...
# define SC_PRIsizet "Iu"
#endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(), // Deprecate use of av_register_input_format(), av_register_output_format(),
@ -37,6 +34,15 @@
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL # define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
#endif #endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6) #if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc> // <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS # define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
@ -51,12 +57,4 @@
char *strdup(const char *s); char *strdup(const char *s);
#endif #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 #endif

View File

@ -41,7 +41,7 @@ static const char *const android_motionevent_action_labels[] = {
"pointer-up", "pointer-up",
"hover-move", "hover-move",
"scroll", "scroll",
"hover-enter", "hover-enter"
"hover-exit", "hover-exit",
"btn-press", "btn-press",
"btn-release", "btn-release",
@ -55,25 +55,19 @@ static const char *const screen_power_mode_labels[] = {
"suspend", "suspend",
}; };
static const char *const copy_key_labels[] = {
"none",
"copy",
"cut",
};
static void static void
write_position(uint8_t *buf, const struct sc_position *position) { write_position(uint8_t *buf, const struct sc_position *position) {
sc_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
sc_write32be(&buf[4], position->point.y); buffer_write32be(&buf[4], position->point.y);
sc_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[8], position->screen_size.width);
sc_write16be(&buf[10], position->screen_size.height); buffer_write16be(&buf[10], position->screen_size.height);
} }
// write length (4 bytes) + string (non null-terminated) // write length (2 bytes) + string (non nul-terminated)
static size_t static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) { write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len); size_t len = sc_str_utf8_truncation_index(utf8, max_len);
sc_write32be(buf, len); buffer_write32be(buf, len);
memcpy(&buf[4], utf8, len); memcpy(&buf[4], utf8, len);
return 4 + len; return 4 + len;
} }
@ -89,58 +83,55 @@ to_fixed_point_16(float f) {
} }
size_t size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) { control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buf[0] = msg->type; buf[0] = msg->type;
switch (msg->type) { switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
sc_write32be(&buf[2], msg->inject_keycode.keycode); buffer_write32be(&buf[2], msg->inject_keycode.keycode);
sc_write32be(&buf[6], msg->inject_keycode.repeat); buffer_write32be(&buf[6], msg->inject_keycode.repeat);
sc_write32be(&buf[10], msg->inject_keycode.metastate); buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14; return 14;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: { case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = size_t len =
write_string(msg->inject_text.text, write_string(msg->inject_text.text,
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]); CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len; return 1 + len;
} }
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action; buf[1] = msg->inject_touch_event.action;
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id); buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position); write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure = uint16_t pressure =
to_fixed_point_16(msg->inject_touch_event.pressure); to_fixed_point_16(msg->inject_touch_event.pressure);
sc_write16be(&buf[22], pressure); buffer_write16be(&buf[22], pressure);
sc_write32be(&buf[24], msg->inject_touch_event.buttons); buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28; return 28;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position); write_position(&buf[1], &msg->inject_scroll_event.position);
sc_write32be(&buf[13], buffer_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll); (uint32_t) msg->inject_scroll_event.hscroll);
sc_write32be(&buf[17], buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll); (uint32_t) msg->inject_scroll_event.vscroll);
sc_write32be(&buf[21], msg->inject_scroll_event.buttons); return 21;
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; buf[1] = msg->inject_keycode.action;
return 2; return 2;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = msg->get_clipboard.copy_key; buf[1] = !!msg->set_clipboard.paste;
return 2;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
sc_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text, size_t len = write_string(msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[10]); &buf[2]);
return 10 + len; return 2 + len;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: }
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode; buf[1] = msg->set_screen_power_mode.mode;
return 2; return 2;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data // no additional data
return 1; return 1;
default: default:
@ -150,20 +141,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
} }
void void
sc_control_msg_log(const struct sc_control_msg *msg) { control_msg_log(const struct control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__) #define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) { switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE: case CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx", LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action), KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode, (int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat, msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate); (long) msg->inject_keycode.metastate);
break; break;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text); LOG_CMSG("text \"%s\"", msg->inject_text.text);
break; break;
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: { case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK; & AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id; uint64_t id = msg->inject_touch_event.pointer_id;
@ -179,6 +170,11 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
(long) msg->inject_touch_event.buttons); (long) msg->inject_touch_event.buttons);
} else { } else {
// numeric pointer id // numeric pointer id
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx", PRIi32 " pressure=%g buttons=%06lx",
id, id,
@ -190,43 +186,40 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} }
break; break;
} }
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32 LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32 " buttons=%06lx", " vscroll=%" PRIi32,
msg->inject_scroll_event.position.point.x, msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y, msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll, msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll, msg->inject_scroll_event.vscroll);
(long) msg->inject_scroll_event.buttons);
break; break;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s", LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break; break;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s", LOG_CMSG("clipboard %s \"%s\"",
copy_key_labels[msg->get_clipboard.copy_key]); msg->set_clipboard.paste ? "paste" : "copy",
break;
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); msg->set_clipboard.text);
break; break;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
LOG_CMSG("power mode %s", LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode)); SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break; break;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel"); LOG_CMSG("expand notification panel");
break; break;
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel"); LOG_CMSG("expand settings panel");
break; break;
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels"); LOG_CMSG("collapse panels");
break; break;
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device"); LOG_CMSG("rotate device");
break; break;
default: default:
@ -236,12 +229,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} }
void void
sc_control_msg_destroy(struct sc_control_msg *msg) { control_msg_destroy(struct control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text); free(msg->inject_text.text);
break; break;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text); free(msg->set_clipboard.text);
break; break;
default: default:

View File

@ -1,5 +1,5 @@
#ifndef SC_CONTROLMSG_H #ifndef CONTROLMSG_H
#define SC_CONTROLMSG_H #define CONTROLMSG_H
#include "common.h" #include "common.h"
@ -11,44 +11,38 @@
#include "android/keycodes.h" #include "android/keycodes.h"
#include "coords.h" #include "coords.h"
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 #define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes // type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14) #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
#define POINTER_ID_MOUSE UINT64_C(-1) #define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2) #define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum sc_control_msg_type { enum control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,
SC_CONTROL_MSG_TYPE_INJECT_TEXT, CONTROL_MSG_TYPE_INJECT_TEXT,
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, CONTROL_MSG_TYPE_COLLAPSE_PANELS,
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, CONTROL_MSG_TYPE_ROTATE_DEVICE,
}; };
enum sc_screen_power_mode { enum screen_power_mode {
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305> // see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SC_SCREEN_POWER_MODE_OFF = 0, SCREEN_POWER_MODE_OFF = 0,
SC_SCREEN_POWER_MODE_NORMAL = 2, SCREEN_POWER_MODE_NORMAL = 2,
}; };
enum sc_copy_key { struct control_msg {
SC_COPY_KEY_NONE, enum control_msg_type type;
SC_COPY_KEY_COPY,
SC_COPY_KEY_CUT,
};
struct sc_control_msg {
enum sc_control_msg_type type;
union { union {
struct { struct {
enum android_keyevent_action action; enum android_keyevent_action action;
@ -70,22 +64,17 @@ struct sc_control_msg {
struct sc_position position; struct sc_position position;
int32_t hscroll; int32_t hscroll;
int32_t vscroll; int32_t vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
enum android_keyevent_action action; // action for the BACK key enum android_keyevent_action action; // action for the BACK key
// screen may only be turned on on ACTION_DOWN // screen may only be turned on on ACTION_DOWN
} back_or_screen_on; } back_or_screen_on;
struct { struct {
enum sc_copy_key copy_key;
} get_clipboard;
struct {
uint64_t sequence;
char *text; // owned, to be freed by free() char *text; // owned, to be freed by free()
bool paste; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {
enum sc_screen_power_mode mode; enum screen_power_mode mode;
} set_screen_power_mode; } set_screen_power_mode;
}; };
}; };
@ -93,12 +82,12 @@ struct sc_control_msg {
// buf size must be at least CONTROL_MSG_MAX_SIZE // buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written // return the number of bytes written
size_t size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf); control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
void void
sc_control_msg_log(const struct sc_control_msg *msg); control_msg_log(const struct control_msg *msg);
void void
sc_control_msg_destroy(struct sc_control_msg *msg); control_msg_destroy(struct control_msg *msg);
#endif #endif

View File

@ -5,11 +5,10 @@
#include "util/log.h" #include "util/log.h"
bool bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket, controller_init(struct controller *controller, sc_socket control_socket) {
struct sc_acksync *acksync) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync); bool ok = receiver_init(&controller->receiver, control_socket);
if (!ok) { if (!ok) {
return false; return false;
} }
@ -34,23 +33,23 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
} }
void void
sc_controller_destroy(struct sc_controller *controller) { controller_destroy(struct controller *controller) {
sc_cond_destroy(&controller->msg_cond); sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex); sc_mutex_destroy(&controller->mutex);
struct sc_control_msg msg; struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
sc_control_msg_destroy(&msg); control_msg_destroy(&msg);
} }
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
} }
bool bool
sc_controller_push_msg(struct sc_controller *controller, controller_push_msg(struct controller *controller,
const struct sc_control_msg *msg) { const struct control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_control_msg_log(msg); control_msg_log(msg);
} }
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
@ -64,10 +63,9 @@ sc_controller_push_msg(struct sc_controller *controller,
} }
static bool static bool
process_msg(struct sc_controller *controller, process_msg(struct controller *controller, const struct control_msg *msg) {
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 = control_msg_serialize(msg, serialized_msg);
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
@ -78,7 +76,7 @@ process_msg(struct sc_controller *controller,
static int static int
run_controller(void *data) { run_controller(void *data) {
struct sc_controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
@ -90,14 +88,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex); sc_mutex_unlock(&controller->mutex);
break; break;
} }
struct sc_control_msg msg; struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_mutex_unlock(&controller->mutex); sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
sc_control_msg_destroy(&msg); control_msg_destroy(&msg);
if (!ok) { if (!ok) {
LOGD("Could not write msg to socket"); LOGD("Could not write msg to socket");
break; break;
@ -107,18 +105,18 @@ run_controller(void *data) {
} }
bool bool
sc_controller_start(struct sc_controller *controller) { controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller, bool ok = sc_thread_create(&controller->thread, run_controller,
"scrcpy-ctl", controller); "controller", controller);
if (!ok) { if (!ok) {
LOGE("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return false;
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
sc_controller_stop(controller); controller_stop(controller);
sc_thread_join(&controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
return false; return false;
} }
@ -127,7 +125,7 @@ sc_controller_start(struct sc_controller *controller) {
} }
void void
sc_controller_stop(struct sc_controller *controller) { controller_stop(struct controller *controller) {
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
controller->stopped = true; controller->stopped = true;
sc_cond_signal(&controller->msg_cond); sc_cond_signal(&controller->msg_cond);
@ -135,7 +133,7 @@ sc_controller_stop(struct sc_controller *controller) {
} }
void void
sc_controller_join(struct sc_controller *controller) { controller_join(struct controller *controller) {
sc_thread_join(&controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@ -1,5 +1,5 @@
#ifndef SC_CONTROLLER_H #ifndef CONTROLLER_H
#define SC_CONTROLLER_H #define CONTROLLER_H
#include "common.h" #include "common.h"
@ -7,41 +7,39 @@
#include "control_msg.h" #include "control_msg.h"
#include "receiver.h" #include "receiver.h"
#include "util/acksync.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h" #include "util/thread.h"
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct sc_controller { struct controller {
sc_socket control_socket; sc_socket control_socket;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond msg_cond; sc_cond msg_cond;
bool stopped; bool stopped;
struct sc_control_msg_queue queue; struct control_msg_queue queue;
struct receiver receiver; struct receiver receiver;
}; };
bool bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket, controller_init(struct controller *controller, sc_socket control_socket);
struct sc_acksync *acksync);
void void
sc_controller_destroy(struct sc_controller *controller); controller_destroy(struct controller *controller);
bool bool
sc_controller_start(struct sc_controller *controller); controller_start(struct controller *controller);
void void
sc_controller_stop(struct sc_controller *controller); controller_stop(struct controller *controller);
void void
sc_controller_join(struct sc_controller *controller); controller_join(struct controller *controller);
bool bool
sc_controller_push_msg(struct sc_controller *controller, controller_push_msg(struct controller *controller,
const struct sc_control_msg *msg); const struct control_msg *msg);
#endif #endif

View File

@ -9,10 +9,10 @@
#include "util/log.h" #include "util/log.h"
/** Downcast packet_sink to decoder */ /** Downcast packet_sink to decoder */
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink) #define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
static void static void
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) { decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
while (count) { while (count) {
struct sc_frame_sink *sink = decoder->sinks[--count]; struct sc_frame_sink *sink = decoder->sinks[--count];
sink->ops->close(sink); sink->ops->close(sink);
@ -20,17 +20,17 @@ sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
} }
static inline void static inline void
sc_decoder_close_sinks(struct sc_decoder *decoder) { decoder_close_sinks(struct decoder *decoder) {
sc_decoder_close_first_sinks(decoder, decoder->sink_count); decoder_close_first_sinks(decoder, decoder->sink_count);
} }
static bool static bool
sc_decoder_open_sinks(struct sc_decoder *decoder) { decoder_open_sinks(struct decoder *decoder) {
for (unsigned i = 0; i < decoder->sink_count; ++i) { for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i]; struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->open(sink)) { if (!sink->ops->open(sink)) {
LOGE("Could not open frame sink %d", i); LOGE("Could not open frame sink %d", i);
sc_decoder_close_first_sinks(decoder, i); decoder_close_first_sinks(decoder, i);
return false; return false;
} }
} }
@ -39,15 +39,13 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
} }
static bool static bool
sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) { decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec); decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { if (!decoder->codec_ctx) {
LOG_OOM(); LOGC("Could not allocate decoder context");
return false; return false;
} }
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) { if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec"); LOGE("Could not open codec");
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
@ -56,13 +54,13 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
decoder->frame = av_frame_alloc(); decoder->frame = av_frame_alloc();
if (!decoder->frame) { if (!decoder->frame) {
LOG_OOM(); LOGE("Could not create decoder frame");
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
return false; return false;
} }
if (!sc_decoder_open_sinks(decoder)) { if (!decoder_open_sinks(decoder)) {
LOGE("Could not open decoder sinks"); LOGE("Could not open decoder sinks");
av_frame_free(&decoder->frame); av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
@ -74,15 +72,15 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
} }
static void static void
sc_decoder_close(struct sc_decoder *decoder) { decoder_close(struct decoder *decoder) {
sc_decoder_close_sinks(decoder); decoder_close_sinks(decoder);
av_frame_free(&decoder->frame); av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
} }
static bool static bool
push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) { push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
for (unsigned i = 0; i < decoder->sink_count; ++i) { for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i]; struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->push(sink, frame)) { if (!sink->ops->push(sink, frame)) {
@ -95,7 +93,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
} }
static bool static bool
sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) { decoder_push(struct decoder *decoder, const AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE; bool is_config = packet->pts == AV_NOPTS_VALUE;
if (is_config) { if (is_config) {
// nothing to do // nothing to do
@ -124,40 +122,39 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
} }
static bool static bool
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct sc_decoder *decoder = DOWNCAST(sink); struct decoder *decoder = DOWNCAST(sink);
return sc_decoder_open(decoder, codec); return decoder_open(decoder, codec);
} }
static void static void
sc_decoder_packet_sink_close(struct sc_packet_sink *sink) { decoder_packet_sink_close(struct sc_packet_sink *sink) {
struct sc_decoder *decoder = DOWNCAST(sink); struct decoder *decoder = DOWNCAST(sink);
sc_decoder_close(decoder); decoder_close(decoder);
} }
static bool static bool
sc_decoder_packet_sink_push(struct sc_packet_sink *sink, decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
const AVPacket *packet) { struct decoder *decoder = DOWNCAST(sink);
struct sc_decoder *decoder = DOWNCAST(sink); return decoder_push(decoder, packet);
return sc_decoder_push(decoder, packet);
} }
void void
sc_decoder_init(struct sc_decoder *decoder) { decoder_init(struct decoder *decoder) {
decoder->sink_count = 0; decoder->sink_count = 0;
static const struct sc_packet_sink_ops ops = { static const struct sc_packet_sink_ops ops = {
.open = sc_decoder_packet_sink_open, .open = decoder_packet_sink_open,
.close = sc_decoder_packet_sink_close, .close = decoder_packet_sink_close,
.push = sc_decoder_packet_sink_push, .push = decoder_packet_sink_push,
}; };
decoder->packet_sink.ops = &ops; decoder->packet_sink.ops = &ops;
} }
void void
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) { decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
assert(decoder->sink_count < SC_DECODER_MAX_SINKS); assert(decoder->sink_count < DECODER_MAX_SINKS);
assert(sink); assert(sink);
assert(sink->ops); assert(sink->ops);
decoder->sinks[decoder->sink_count++] = sink; decoder->sinks[decoder->sink_count++] = sink;

View File

@ -1,20 +1,19 @@
#ifndef SC_DECODER_H #ifndef DECODER_H
#define SC_DECODER_H #define DECODER_H
#include "common.h" #include "common.h"
#include "trait/packet_sink.h" #include "trait/packet_sink.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#define SC_DECODER_MAX_SINKS 2 #define DECODER_MAX_SINKS 2
struct sc_decoder { struct decoder {
struct sc_packet_sink packet_sink; // packet sink trait struct sc_packet_sink packet_sink; // packet sink trait
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS]; struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
unsigned sink_count; unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
@ -22,9 +21,9 @@ struct sc_decoder {
}; };
void void
sc_decoder_init(struct sc_decoder *decoder); decoder_init(struct decoder *decoder);
void void
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink); decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
#endif #endif

View File

@ -1,280 +0,0 @@
#include "demuxer.h"
#include <assert.h>
#include <libavutil/time.h>
#include <unistd.h>
#include "decoder.h"
#include "events.h"
#include "recorder.h"
#include "util/buffer_util.h"
#include "util/log.h"
#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)
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
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 "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> 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
// CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<------------------------------------------------------------------->
// || PTS
// | `- config packet
// `-- key frame
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;
}
uint64_t pts_flags = sc_read64be(header);
uint32_t len = sc_read32be(&header[8]);
assert(len);
if (av_new_packet(packet, len)) {
LOG_OOM();
return false;
}
r = net_recv_all(demuxer->socket, packet->data, len);
if (r < 0 || ((uint32_t) r) < len) {
av_packet_unref(packet);
return false;
}
if (pts_flags & SC_PACKET_FLAG_CONFIG) {
packet->pts = AV_NOPTS_VALUE;
} else {
packet->pts = pts_flags & SC_PACKET_PTS_MASK;
}
if (pts_flags & SC_PACKET_FLAG_KEY_FRAME) {
packet->flags |= AV_PKT_FLAG_KEY;
}
packet->dts = packet->pts;
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)) {
LOGE("Could not send config packet to sink %d", i);
return false;
}
}
return true;
}
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) {
size_t offset;
if (demuxer->pending) {
offset = demuxer->pending->size;
if (av_grow_packet(demuxer->pending, packet->size)) {
LOG_OOM();
return false;
}
} else {
offset = 0;
demuxer->pending = av_packet_alloc();
if (!demuxer->pending) {
LOG_OOM();
return false;
}
if (av_new_packet(demuxer->pending, packet->size)) {
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;
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;
}
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)) {
LOGE("Could not open packet sink %d", i);
sc_demuxer_close_first_sinks(demuxer, i);
return false;
}
}
return true;
}
static int
run_demuxer(void *data) {
struct sc_demuxer *demuxer = data;
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
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;
}
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!
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
goto finally_close_parser;
}
for (;;) {
bool ok = sc_demuxer_recv_packet(demuxer, packet);
if (!ok) {
// end of stream
break;
}
ok = sc_demuxer_push_packet(demuxer, packet);
av_packet_unref(packet);
if (!ok) {
// cannot process packet (error already logged)
break;
}
}
LOGD("End of frames");
if (demuxer->pending) {
av_packet_free(&demuxer->pending);
}
av_packet_free(&packet);
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);
return 0;
}
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_eos);
demuxer->cbs = cbs;
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("Starting demuxer thread");
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
demuxer);
if (!ok) {
LOGE("Could not start demuxer thread");
return false;
}
return true;
}
void
sc_demuxer_join(struct sc_demuxer *demuxer) {
sc_thread_join(&demuxer->thread, NULL);
}

View File

@ -1,51 +0,0 @@
#ifndef SC_DEMUXER_H
#define SC_DEMUXER_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#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

View File

@ -1,6 +1,5 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -18,13 +17,13 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->type = buf[0]; msg->type = buf[0];
switch (msg->type) { switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: { case DEVICE_MSG_TYPE_CLIPBOARD: {
size_t clipboard_len = sc_read32be(&buf[1]); size_t clipboard_len = buffer_read32be(&buf[1]);
if (clipboard_len > len - 5) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = malloc(clipboard_len + 1); char *text = malloc(clipboard_len + 1);
if (!text) { if (!text) {
LOG_OOM(); LOGW("Could not allocate text for clipboard");
return -1; return -1;
} }
if (clipboard_len) { if (clipboard_len) {
@ -35,11 +34,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
msg->clipboard.text = text; msg->clipboard.text = text;
return 5 + clipboard_len; return 5 + clipboard_len;
} }
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
uint64_t sequence = sc_read64be(&buf[1]);
msg->ack_clipboard.sequence = sequence;
return 9;
}
default: default:
LOGW("Unknown device message type: %d", (int) msg->type); LOGW("Unknown device message type: %d", (int) msg->type);
return -1; // error, we cannot recover return -1; // error, we cannot recover

View File

@ -1,5 +1,5 @@
#ifndef SC_DEVICEMSG_H #ifndef DEVICEMSG_H
#define SC_DEVICEMSG_H #define DEVICEMSG_H
#include "common.h" #include "common.h"
@ -13,7 +13,6 @@
enum device_msg_type { enum device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_CLIPBOARD,
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
}; };
struct device_msg { struct device_msg {
@ -22,9 +21,6 @@ struct device_msg {
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by free()
} clipboard; } clipboard;
struct {
uint64_t sequence;
} ack_clipboard;
}; };
}; };

View File

@ -2,4 +2,3 @@
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2) #define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) #define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)

180
app/src/file_handler.c Normal file
View File

@ -0,0 +1,180 @@
#include "file_handler.h"
#include <assert.h>
#include <string.h>
#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) {
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;
}
// 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, SC_STDOUT | SC_STDERR);
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,
SC_STDOUT | SC_STDERR);
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,
"file_handler", 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);
}

60
app/src/file_handler.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include "common.h"
#include <stdbool.h>
#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

View File

@ -1,178 +0,0 @@
#include "file_pusher.h"
#include <assert.h>
#include <string.h>
#include "adb/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 = sc_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 = sc_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) {
LOGE("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);
}

View File

@ -1,58 +0,0 @@
#ifndef SC_FILE_PUSHER_H
#define SC_FILE_PUSHER_H
#include "common.h"
#include <stdbool.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

View File

@ -4,10 +4,10 @@
#include "util/log.h" #include "util/log.h"
#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1) #define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
bool bool
sc_fps_counter_init(struct sc_fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex); bool ok = sc_mutex_init(&counter->mutex);
if (!ok) { if (!ok) {
return false; return false;
@ -27,26 +27,26 @@ sc_fps_counter_init(struct sc_fps_counter *counter) {
} }
void void
sc_fps_counter_destroy(struct sc_fps_counter *counter) { fps_counter_destroy(struct fps_counter *counter) {
sc_cond_destroy(&counter->state_cond); sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex); sc_mutex_destroy(&counter->mutex);
} }
static inline bool static inline bool
is_started(struct sc_fps_counter *counter) { is_started(struct fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire); return atomic_load_explicit(&counter->started, memory_order_acquire);
} }
static inline void static inline void
set_started(struct sc_fps_counter *counter, bool started) { set_started(struct fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release); atomic_store_explicit(&counter->started, started, memory_order_release);
} }
// must be called with mutex locked // must be called with mutex locked
static void static void
display_fps(struct sc_fps_counter *counter) { display_fps(struct fps_counter *counter) {
unsigned rendered_per_second = unsigned rendered_per_second =
counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL; counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL;
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second, LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped); counter->nr_skipped);
@ -57,7 +57,7 @@ display_fps(struct sc_fps_counter *counter) {
// must be called with mutex locked // must be called with mutex locked
static void static void
check_interval_expired(struct sc_fps_counter *counter, sc_tick now) { check_interval_expired(struct fps_counter *counter, uint32_t now) {
if (now < counter->next_timestamp) { if (now < counter->next_timestamp) {
return; return;
} }
@ -67,13 +67,13 @@ check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
// add a multiple of the interval // add a multiple of the interval
uint32_t elapsed_slices = uint32_t elapsed_slices =
(now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1; (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices; counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
} }
static int static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct sc_fps_counter *counter = data; struct fps_counter *counter = data;
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
@ -94,9 +94,9 @@ run_fps_counter(void *data) {
} }
bool bool
sc_fps_counter_start(struct sc_fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL; counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@ -108,7 +108,7 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
// same thread, no need to lock // same thread, no need to lock
if (!counter->thread_started) { if (!counter->thread_started) {
bool ok = sc_thread_create(&counter->thread, run_fps_counter, bool ok = sc_thread_create(&counter->thread, run_fps_counter,
"scrcpy-fps", counter); "fps counter", counter);
if (!ok) { if (!ok) {
LOGE("Could not start FPS counter thread"); LOGE("Could not start FPS counter thread");
return false; return false;
@ -117,24 +117,22 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
counter->thread_started = true; counter->thread_started = true;
} }
LOGI("FPS counter started");
return true; return true;
} }
void void
sc_fps_counter_stop(struct sc_fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
sc_cond_signal(&counter->state_cond); sc_cond_signal(&counter->state_cond);
LOGI("FPS counter stopped");
} }
bool bool
sc_fps_counter_is_started(struct sc_fps_counter *counter) { fps_counter_is_started(struct fps_counter *counter) {
return is_started(counter); return is_started(counter);
} }
void void
sc_fps_counter_interrupt(struct sc_fps_counter *counter) { fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread_started) { if (!counter->thread_started) {
return; return;
} }
@ -147,7 +145,7 @@ sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
} }
void void
sc_fps_counter_join(struct sc_fps_counter *counter) { fps_counter_join(struct fps_counter *counter) {
if (counter->thread_started) { if (counter->thread_started) {
// interrupted must be set by the thread calling join(), so no need to // interrupted must be set by the thread calling join(), so no need to
// lock for the assertion // lock for the assertion
@ -158,7 +156,7 @@ sc_fps_counter_join(struct sc_fps_counter *counter) {
} }
void void
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) { fps_counter_add_rendered_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!is_started(counter)) {
return; return;
} }
@ -171,7 +169,7 @@ sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
} }
void void
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) { fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!is_started(counter)) {
return; return;
} }

View File

@ -1,5 +1,5 @@
#ifndef SC_FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define SC_FPSCOUNTER_H #define FPSCOUNTER_H
#include "common.h" #include "common.h"
@ -9,7 +9,7 @@
#include "util/thread.h" #include "util/thread.h"
struct sc_fps_counter { struct fps_counter {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond state_cond; sc_cond state_cond;
@ -28,32 +28,32 @@ struct sc_fps_counter {
}; };
bool bool
sc_fps_counter_init(struct sc_fps_counter *counter); fps_counter_init(struct fps_counter *counter);
void void
sc_fps_counter_destroy(struct sc_fps_counter *counter); fps_counter_destroy(struct fps_counter *counter);
bool bool
sc_fps_counter_start(struct sc_fps_counter *counter); fps_counter_start(struct fps_counter *counter);
void void
sc_fps_counter_stop(struct sc_fps_counter *counter); fps_counter_stop(struct fps_counter *counter);
bool bool
sc_fps_counter_is_started(struct sc_fps_counter *counter); fps_counter_is_started(struct fps_counter *counter);
// request to stop the thread (on quit) // request to stop the thread (on quit)
// must be called before sc_fps_counter_join() // must be called before fps_counter_join()
void void
sc_fps_counter_interrupt(struct sc_fps_counter *counter); fps_counter_interrupt(struct fps_counter *counter);
void void
sc_fps_counter_join(struct sc_fps_counter *counter); fps_counter_join(struct fps_counter *counter);
void void
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter); fps_counter_add_rendered_frame(struct fps_counter *counter);
void void
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter); fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif #endif

View File

@ -10,13 +10,11 @@ bool
sc_frame_buffer_init(struct sc_frame_buffer *fb) { sc_frame_buffer_init(struct sc_frame_buffer *fb) {
fb->pending_frame = av_frame_alloc(); fb->pending_frame = av_frame_alloc();
if (!fb->pending_frame) { if (!fb->pending_frame) {
LOG_OOM();
return false; return false;
} }
fb->tmp_frame = av_frame_alloc(); fb->tmp_frame = av_frame_alloc();
if (!fb->tmp_frame) { if (!fb->tmp_frame) {
LOG_OOM();
av_frame_free(&fb->pending_frame); av_frame_free(&fb->pending_frame);
return false; return false;
} }
@ -50,7 +48,9 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
bool bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame, sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) { bool *previous_frame_skipped) {
sc_mutex_lock(&fb->mutex);
// Use a temporary frame to preserve pending_frame in case of error. // 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. // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame); int r = av_frame_ref(fb->tmp_frame, frame);
@ -59,8 +59,6 @@ sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
return false; return false;
} }
sc_mutex_lock(&fb->mutex);
// Now that av_frame_ref() succeeded, we can replace the previous // Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame // pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame); swap_frames(&fb->pending_frame, &fb->tmp_frame);

View File

@ -1,8 +1,8 @@
#include "hid_keyboard.h" #include "hid_keyboard.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to hid_keyboard */ /** Downcast key processor to hid_keyboard */
@ -126,105 +126,31 @@ static const unsigned char keyboard_report_desc[] = {
0xC0 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 static unsigned char
sdl_keymod_to_hid_modifiers(uint16_t mod) { sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
unsigned char modifiers = HID_MODIFIER_NONE; unsigned char modifiers = HID_MODIFIER_NONE;
if (mod & SC_MOD_LCTRL) { if (mod & KMOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL; modifiers |= HID_MODIFIER_LEFT_CONTROL;
} }
if (mod & SC_MOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT; modifiers |= HID_MODIFIER_LEFT_SHIFT;
} }
if (mod & SC_MOD_LALT) { if (mod & KMOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT; modifiers |= HID_MODIFIER_LEFT_ALT;
} }
if (mod & SC_MOD_LGUI) { if (mod & KMOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI; modifiers |= HID_MODIFIER_LEFT_GUI;
} }
if (mod & SC_MOD_RCTRL) { if (mod & KMOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL; modifiers |= HID_MODIFIER_RIGHT_CONTROL;
} }
if (mod & SC_MOD_RSHIFT) { if (mod & KMOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT; modifiers |= HID_MODIFIER_RIGHT_SHIFT;
} }
if (mod & SC_MOD_RALT) { if (mod & KMOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT; modifiers |= HID_MODIFIER_RIGHT_ALT;
} }
if (mod & SC_MOD_RGUI) { if (mod & KMOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI; modifiers |= HID_MODIFIER_RIGHT_GUI;
} }
return modifiers; return modifiers;
@ -234,7 +160,6 @@ static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE); unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) { if (!buffer) {
LOG_OOM();
return false; return false;
} }
@ -248,15 +173,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
} }
static inline bool static inline bool
scancode_is_modifier(enum sc_scancode scancode) { scancode_is_modifier(SDL_Scancode scancode) {
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI; return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
} }
static bool static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb, convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event, struct sc_hid_event *hid_event,
const struct sc_key_event *event) { const SDL_KeyboardEvent *event) {
enum sc_scancode scancode = event->scancode; SDL_Scancode scancode = event->keysym.scancode;
assert(scancode >= 0); assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot // SDL also generates events when only modifiers are pressed, we cannot
@ -272,11 +197,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
return false; return false;
} }
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state); unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
if (scancode < SC_HID_KEYBOARD_KEYS) { if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false // Pressed is true and released is false
kb->keys[scancode] = (event->action == SC_ACTION_DOWN); kb->keys[scancode] = (event->type == SDL_KEYDOWN);
LOGV("keys[%02x] = %s", scancode, LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false"); kb->keys[scancode] ? "true" : "false");
} }
@ -291,7 +216,7 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
// USB HID protocol says that if keys exceeds report count, a // USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported // phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) { if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Phantom state: // Pantom state:
// - Modifiers // - Modifiers
// - Reserved // - Reserved
// - ErrorRollOver * HID_MAX_KEYS // - ErrorRollOver * HID_MAX_KEYS
@ -306,17 +231,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end: end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x", LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode, event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
event->scancode, modifiers); event->keysym.scancode, modifiers);
return true; return true;
} }
static bool static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
bool capslock = mods_state & SC_MOD_CAPS; bool capslock = sdl_mod & KMOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM; bool numlock = sdl_mod & KMOD_NUM;
if (!capslock && !numlock) { if (!capslock && !numlock) {
// Nothing to do // Nothing to do
return true; return true;
@ -328,6 +253,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
return false; return false;
} }
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0; unsigned i = 0;
if (capslock) { if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
@ -340,7 +267,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event); sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (mod lock state)"); LOGW("Could request HID event");
return false; return false;
} }
@ -351,8 +278,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event, const SDL_KeyboardEvent *event) {
uint64_t ack_to_wait) {
if (event->repeat) { if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so // In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here. // just ignore key repeat here.
@ -367,26 +293,39 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
if (!kb->mod_lock_synchronized) { if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize // Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state // keyboard state
if (push_mod_lock_state(kb, event->mods_state)) { if (push_mod_lock_state(kb, event->keysym.mod)) {
kb->mod_lock_synchronized = true; kb->mod_lock_synchronized = true;
} }
} }
if (ack_to_wait) { 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 // Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait until clipboard synchronization is acknowledged // requested. Wait a bit so that the clipboard is set before
// by the server, otherwise it could paste the old clipboard // injecting Ctrl+v via HID, otherwise it would paste the old
// content. // clipboard content.
hid_event.ack_to_wait = ack_to_wait; hid_event.delay = SC_TICK_FROM_MS(5);
} }
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event); sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (key)"); 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 bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa; kb->aoa = aoa;
@ -406,15 +345,9 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
static const struct sc_key_processor_ops ops = { static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key, .process_key = sc_key_processor_process_key,
// Never forward text input via HID (all the keys are injected .process_text = sc_key_processor_process_text,
// 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; kb->key_processor.ops = &ops;
return true; return true;

View File

@ -2,7 +2,6 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h> #include <libavutil/pixfmt.h>
@ -32,7 +31,7 @@ get_icon_path(void) {
char *icon_path = strdup(icon_path_env); char *icon_path = strdup(icon_path_env);
#endif #endif
if (!icon_path) { if (!icon_path) {
LOG_OOM(); LOGE("Could not allocate memory");
return NULL; return NULL;
} }
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path); LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
@ -43,7 +42,7 @@ get_icon_path(void) {
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH); LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH); char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) { if (!icon_path) {
LOG_OOM(); LOGE("Could not allocate memory");
return NULL; return NULL;
} }
#else #else
@ -64,7 +63,7 @@ decode_image(const char *path) {
AVFormatContext *ctx = avformat_alloc_context(); AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) { if (!ctx) {
LOG_OOM(); LOGE("Could not allocate image decoder context");
return NULL; return NULL;
} }
@ -86,7 +85,7 @@ decode_image(const char *path) {
AVCodecParameters *params = ctx->streams[stream]->codecpar; AVCodecParameters *params = ctx->streams[stream]->codecpar;
const AVCodec *codec = avcodec_find_decoder(params->codec_id); AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) { if (!codec) {
LOGE("Could not find image decoder"); LOGE("Could not find image decoder");
goto close_input; goto close_input;
@ -94,7 +93,7 @@ decode_image(const char *path) {
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) { if (!codec_ctx) {
LOG_OOM(); LOGE("Could not allocate codec context");
goto close_input; goto close_input;
} }
@ -110,13 +109,13 @@ decode_image(const char *path) {
AVFrame *frame = av_frame_alloc(); AVFrame *frame = av_frame_alloc();
if (!frame) { if (!frame) {
LOG_OOM(); LOGE("Could not allocate frame");
goto close_codec; goto close_codec;
} }
AVPacket *packet = av_packet_alloc(); AVPacket *packet = av_packet_alloc();
if (!packet) { if (!packet) {
LOG_OOM(); LOGE("Could not allocate packet");
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto close_codec;
} }

View File

@ -1,5 +1,5 @@
#ifndef SC_ICON_H #ifndef ICON_H
#define SC_ICON_H #define ICON_H
#include "common.h" #include "common.h"

View File

@ -1,452 +0,0 @@
#ifndef SC_INPUT_EVENTS_H
#define SC_INPUT_EVENTS_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_events.h>
#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,
};
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;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
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
};
struct sc_touch_event {
struct sc_position position;
enum sc_touch_action action;
uint64_t pointer_id;
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

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#ifndef SC_INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define SC_INPUTMANAGER_H #define INPUTMANAGER_H
#include "common.h" #include "common.h"
@ -8,23 +8,22 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "controller.h" #include "controller.h"
#include "file_pusher.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "options.h" #include "options.h"
#include "screen.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
#include "trait/mouse_processor.h" #include "trait/mouse_processor.h"
struct sc_input_manager { struct input_manager {
struct sc_controller *controller; struct controller *controller;
struct sc_file_pusher *fp; struct screen *screen;
struct sc_screen *screen;
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool clipboard_autosync;
struct { struct {
unsigned data[SC_MAX_SHORTCUT_MODS]; unsigned data[SC_MAX_SHORTCUT_MODS];
@ -39,28 +38,15 @@ struct sc_input_manager {
unsigned key_repeat; unsigned key_repeat;
SDL_Keycode last_keycode; SDL_Keycode last_keycode;
uint16_t last_mod; uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
};
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;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
}; };
void void
sc_input_manager_init(struct sc_input_manager *im, input_manager_init(struct input_manager *im, struct controller *controller,
const struct sc_input_manager_params *params); struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options);
void bool
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event); input_manager_handle_event(struct input_manager *im, SDL_Event *event);
#endif #endif

View File

@ -1,191 +1,108 @@
#include "keyboard_inject.h" #include "keyboard_inject.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */ /** 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)
static enum android_keyevent_action #define MAP(FROM, TO) case FROM: *to = TO; return true
convert_keycode_action(enum sc_action action) { #define FAIL default: return false
if (action == SC_ACTION_DOWN) { static bool
return AKEY_EVENT_ACTION_DOWN; 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;
} }
assert(action == SC_ACTION_UP);
return AKEY_EVENT_ACTION_UP;
} }
static bool static bool
convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod, convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
enum sc_key_inject_mode key_inject_mode) { bool prefer_text) {
// Navigation keys and ENTER. switch (from) {
// Used in all modes. MAP(SDLK_RETURN, AKEYCODE_ENTER);
static const struct sc_intmap_entry special_keys[] = { MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
{SC_KEYCODE_RETURN, AKEYCODE_ENTER}, MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
{SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER}, MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
{SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE}, MAP(SDLK_TAB, AKEYCODE_TAB);
{SC_KEYCODE_BACKSPACE, AKEYCODE_DEL}, MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
{SC_KEYCODE_TAB, AKEYCODE_TAB}, MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
{SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP}, MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
{SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL}, MAP(SDLK_END, AKEYCODE_MOVE_END);
{SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME}, MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
{SC_KEYCODE_END, AKEYCODE_MOVE_END}, MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
{SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN}, MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
{SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT}, MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
{SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT}, MAP(SDLK_UP, AKEYCODE_DPAD_UP);
{SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN}, MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
{SC_KEYCODE_UP, AKEYCODE_DPAD_UP}, MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
{SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT}, MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT}, MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_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[] = {
{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[] = {
{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[] = {
{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 =
SC_INTMAP_FIND_ENTRY(special_keys, from);
if (entry) {
*to = entry->value;
return true;
} }
if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) { if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// Handle Numpad events when Num Lock is disabled // Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead // If SHIFT is pressed, a text event will be sent instead
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from); switch(from) {
if (entry) { MAP(SDLK_KP_0, AKEYCODE_INSERT);
*to = entry->value; MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
return true; 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 (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && if (prefer_text && !(mod & KMOD_CTRL)) {
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
// do not forward alpha and space key events (unless Ctrl is pressed) // do not forward alpha and space key events (unless Ctrl is pressed)
return false; return false;
} }
if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false; return false;
} }
// if ALT and META are not pressed, also handle letters and space // if ALT and META are not pressed, also handle letters and space
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from); switch (from) {
if (entry) { MAP(SDLK_a, AKEYCODE_A);
*to = entry->value; MAP(SDLK_b, AKEYCODE_B);
return true; 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;
} }
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;
} }
static enum android_metastate static enum android_metastate
@ -208,69 +125,70 @@ autocomplete_metastate(enum android_metastate metastate) {
} }
static enum android_metastate static enum android_metastate
convert_meta_state(uint16_t mod) { convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0; enum android_metastate metastate = 0;
if (mod & SC_MOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON; metastate |= AMETA_SHIFT_LEFT_ON;
} }
if (mod & SC_MOD_RSHIFT) { if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON; metastate |= AMETA_SHIFT_RIGHT_ON;
} }
if (mod & SC_MOD_LCTRL) { if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON; metastate |= AMETA_CTRL_LEFT_ON;
} }
if (mod & SC_MOD_RCTRL) { if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON; metastate |= AMETA_CTRL_RIGHT_ON;
} }
if (mod & SC_MOD_LALT) { if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON; metastate |= AMETA_ALT_LEFT_ON;
} }
if (mod & SC_MOD_RALT) { if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON; metastate |= AMETA_ALT_RIGHT_ON;
} }
if (mod & SC_MOD_LGUI) { // Windows key if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON; metastate |= AMETA_META_LEFT_ON;
} }
if (mod & SC_MOD_RGUI) { // Windows key if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON; metastate |= AMETA_META_RIGHT_ON;
} }
if (mod & SC_MOD_NUM) { if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON; metastate |= AMETA_NUM_LOCK_ON;
} }
if (mod & SC_MOD_CAPS) { if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON; metastate |= AMETA_CAPS_LOCK_ON;
} }
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields // fill the dependent fields
return autocomplete_metastate(metastate); return autocomplete_metastate(metastate);
} }
static bool static bool
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg, convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) { bool prefer_text, uint32_t repeat) {
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE; to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode, if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
event->mods_state, key_inject_mode)) {
return false; return false;
} }
msg->inject_keycode.action = convert_keycode_action(event->action); uint16_t mod = from->keysym.mod;
msg->inject_keycode.repeat = repeat; if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
msg->inject_keycode.metastate = convert_meta_state(event->mods_state); prefer_text)) {
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true; return true;
} }
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event, const SDL_KeyboardEvent *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
// is set before injecting Ctrl+v.
(void) ack_to_wait;
struct sc_keyboard_inject *ki = DOWNCAST(kp); struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (event->repeat) { if (event->repeat) {
@ -282,9 +200,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
ki->repeat = 0; ki->repeat = 0;
} }
struct sc_control_msg msg; struct control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) { if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
if (!sc_controller_push_msg(ki->controller, &msg)) { if (!controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }
} }
@ -292,31 +210,26 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
static void static void
sc_key_processor_process_text(struct sc_key_processor *kp, sc_key_processor_process_text(struct sc_key_processor *kp,
const struct sc_text_event *event) { const SDL_TextInputEvent *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp); struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) { if (!ki->prefer_text) {
// Never inject text events
return;
}
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0'); assert(event->text[1] == '\0');
// Letters and space are handled as raw key events // letters and space are handled as raw key event
return; return;
} }
} }
struct sc_control_msg msg; struct control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text); msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) { if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!sc_controller_push_msg(ki->controller, &msg)) { if (!controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text); free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
@ -324,12 +237,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
void void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki, sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller, struct controller *controller,
enum sc_key_inject_mode key_inject_mode, const struct scrcpy_options *options) {
bool forward_key_repeat) {
ki->controller = controller; ki->controller = controller;
ki->key_inject_mode = key_inject_mode; ki->prefer_text = options->prefer_text;
ki->forward_key_repeat = forward_key_repeat; ki->forward_key_repeat = options->forward_key_repeat;
ki->repeat = 0; ki->repeat = 0;
@ -338,7 +250,5 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
.process_text = sc_key_processor_process_text, .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; ki->key_processor.ops = &ops;
} }

View File

@ -12,20 +12,19 @@
struct sc_keyboard_inject { struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait struct sc_key_processor key_processor; // key processor trait
struct sc_controller *controller; struct controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual // SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count. // number of repetitions. This variable keeps track of the count.
unsigned repeat; unsigned repeat;
enum sc_key_inject_mode key_inject_mode; bool prefer_text;
bool forward_key_repeat; bool forward_key_repeat;
}; };
void void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki, sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller, struct controller *controller,
enum sc_key_inject_mode key_inject_mode, const struct scrcpy_options *options);
bool forward_key_repeat);
#endif #endif

View File

@ -13,9 +13,30 @@
#include "cli.h" #include "cli.h"
#include "options.h" #include "options.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h" #include "util/log.h"
#include "version.h"
static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\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);
#ifdef HAVE_V4L2
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
}
int int
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
@ -40,19 +61,19 @@ main(int argc, char *argv[]) {
#endif #endif
if (!scrcpy_parse_args(&args, argc, argv)) { if (!scrcpy_parse_args(&args, argc, argv)) {
return SCRCPY_EXIT_FAILURE; return 1;
} }
sc_set_log_level(args.opts.log_level); sc_set_log_level(args.opts.log_level);
if (args.help) { if (args.help) {
scrcpy_print_usage(argv[0]); scrcpy_print_usage(argv[0]);
return SCRCPY_EXIT_SUCCESS; return 0;
} }
if (args.version) { if (args.version) {
scrcpy_print_version(); print_version();
return SCRCPY_EXIT_SUCCESS; return 0;
} }
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
@ -66,17 +87,12 @@ main(int argc, char *argv[]) {
#endif #endif
if (avformat_network_init()) { if (avformat_network_init()) {
return SCRCPY_EXIT_FAILURE; return 1;
} }
#ifdef HAVE_USB int res = scrcpy(&args.opts) ? 0 : 1;
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
#else
enum scrcpy_exit_code ret = scrcpy(&args.opts);
#endif
avformat_network_deinit(); // ignore failure avformat_network_deinit(); // ignore failure
return ret; return res;
} }

View File

@ -1,12 +1,11 @@
#include "mouse_inject.h" #include "mouse_inject.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */ /** Downcast mouse processor to sc_mouse_inject */
@ -15,147 +14,198 @@
static enum android_motionevent_buttons static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) { convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0; enum android_motionevent_buttons buttons = 0;
if (state & SC_MOUSE_BUTTON_LEFT) { if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY; buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
} }
if (state & SC_MOUSE_BUTTON_RIGHT) { if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY; buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
} }
if (state & SC_MOUSE_BUTTON_MIDDLE) { if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY; buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
} }
if (state & SC_MOUSE_BUTTON_X1) { if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK; buttons |= AMOTION_EVENT_BUTTON_BACK;
} }
if (state & SC_MOUSE_BUTTON_X2) { if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD; buttons |= AMOTION_EVENT_BUTTON_FORWARD;
} }
return buttons; return buttons;
} }
static enum android_motionevent_action #define MAP(FROM, TO) case FROM: *to = TO; return true
convert_mouse_action(enum sc_action action) { #define FAIL default: return false
if (action == SC_ACTION_DOWN) { static bool
return AMOTION_EVENT_ACTION_DOWN; 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;
} }
assert(action == SC_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
} }
static enum android_motionevent_action static bool
convert_touch_action(enum sc_touch_action action) { convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (action) { switch (from) {
case SC_TOUCH_ACTION_MOVE: MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
return AMOTION_EVENT_ACTION_MOVE; MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
case SC_TOUCH_ACTION_DOWN: MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
return AMOTION_EVENT_ACTION_DOWN; FAIL;
default:
assert(action == SC_TOUCH_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
} }
} }
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 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;
return true;
}
static void static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) { const SDL_MouseMotionEvent *event) {
if (!event->buttons_state) { struct sc_mouse_inject *mi = DOWNCAST(mp);
// Do not send motion events when no click is pressed
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
return; return;
} }
struct sc_mouse_inject *mi = DOWNCAST(mp); if (!controller_push_msg(mi->controller, &msg)) {
struct sc_control_msg msg = {
.type = SC_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 (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'"); LOGW("Could not request 'inject mouse motion event'");
} }
} }
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_control_msg msg = {
.type = SC_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 (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
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_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
.vscroll = event->vscroll,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
static void static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) { const SDL_TouchFingerEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp); struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = { struct control_msg msg;
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, if (convert_touch(event, mi->screen, &msg)) {
.inject_touch_event = { if (!controller_push_msg(mi->controller, &msg)) {
.action = convert_touch_action(event->action), LOGW("Could not request 'inject touch event'");
.pointer_id = event->pointer_id, }
.position = event->position, }
.pressure = event->pressure, }
.buttons = 0,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) { static void
LOGW("Could not request 'inject touch event'"); 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 void
sc_mouse_inject_init(struct sc_mouse_inject *mi, sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct sc_controller *controller) { struct screen *screen) {
mi->controller = controller; mi->controller = controller;
mi->screen = screen;
static const struct sc_mouse_processor_ops ops = { static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion, .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,
.process_touch = sc_mouse_processor_process_touch, .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; mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
} }

View File

@ -12,11 +12,12 @@
struct sc_mouse_inject { struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller; struct controller *controller;
struct screen *screen;
}; };
void void
sc_mouse_inject_init(struct sc_mouse_inject *mi, sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct sc_controller *controller); struct screen *screen);
#endif #endif

View File

@ -28,7 +28,7 @@ sc_opengl_init(struct sc_opengl *gl) {
sizeof(OPENGL_ES_PREFIX) - 1); sizeof(OPENGL_ES_PREFIX) - 1);
if (gl->is_opengles) { if (gl->is_opengles) {
/* skip the prefix */ /* skip the prefix */
version += sizeof(OPENGL_ES_PREFIX) - 1; version += sizeof(PREFIX) - 1;
} }
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor); int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);

View File

@ -19,10 +19,8 @@ const struct scrcpy_options scrcpy_options_default = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST,
}, },
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = { .shortcut_mods = {
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER}, .data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2, .count = 2,
}, },
.max_size = 0, .max_size = 0,
@ -37,16 +35,13 @@ const struct scrcpy_options scrcpy_options_default = {
.display_id = 0, .display_id = 0,
.display_buffer = 0, .display_buffer = 0,
.v4l2_buffer = 0, .v4l2_buffer = 0,
#ifdef HAVE_USB
.otg = false,
#endif
.show_touches = false, .show_touches = false,
.fullscreen = false, .fullscreen = false,
.always_on_top = false, .always_on_top = false,
.control = true, .control = true,
.display = true, .display = true,
.turn_screen_off = false, .turn_screen_off = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .prefer_text = false,
.window_borderless = false, .window_borderless = false,
.mipmaps = true, .mipmaps = true,
.stay_awake = false, .stay_awake = false,
@ -56,15 +51,4 @@ const struct scrcpy_options scrcpy_options_default = {
.forward_all_clicks = false, .forward_all_clicks = false,
.legacy_paste = false, .legacy_paste = false,
.power_off_on_close = false, .power_off_on_close = false,
.clipboard_autosync = true,
.downsize_on_error = true,
.tcpip = false,
.tcpip_dst = NULL,
.select_tcpip = false,
.select_usb = false,
.cleanup = true,
.start_fps_counter = false,
.power_on = true,
.install = false,
.reinstall = false,
}; };

View File

@ -38,34 +38,15 @@ enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_HID, 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.
// 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,
// Inject everything as key events.
SC_KEY_INJECT_MODE_RAW,
};
#define SC_MAX_SHORTCUT_MODS 8 #define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod { enum sc_shortcut_mod {
SC_SHORTCUT_MOD_LCTRL = 1 << 0, SC_MOD_LCTRL = 1 << 0,
SC_SHORTCUT_MOD_RCTRL = 1 << 1, SC_MOD_RCTRL = 1 << 1,
SC_SHORTCUT_MOD_LALT = 1 << 2, SC_MOD_LALT = 1 << 2,
SC_SHORTCUT_MOD_RALT = 1 << 3, SC_MOD_RALT = 1 << 3,
SC_SHORTCUT_MOD_LSUPER = 1 << 4, SC_MOD_LSUPER = 1 << 4,
SC_SHORTCUT_MOD_RSUPER = 1 << 5, SC_MOD_RSUPER = 1 << 5,
}; };
struct sc_shortcut_mods { struct sc_shortcut_mods {
@ -95,10 +76,7 @@ struct scrcpy_options {
enum sc_log_level log_level; enum sc_log_level log_level;
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods; struct sc_shortcut_mods shortcut_mods;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
@ -112,16 +90,13 @@ struct scrcpy_options {
uint32_t display_id; uint32_t display_id;
sc_tick display_buffer; sc_tick display_buffer;
sc_tick v4l2_buffer; sc_tick v4l2_buffer;
#ifdef HAVE_USB
bool otg;
#endif
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
bool control; bool control;
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
enum sc_key_inject_mode key_inject_mode; bool prefer_text;
bool window_borderless; bool window_borderless;
bool mipmaps; bool mipmaps;
bool stay_awake; bool stay_awake;
@ -131,17 +106,6 @@ struct scrcpy_options {
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
bool cleanup;
bool start_fps_counter;
bool power_on;
bool install;
bool reinstall;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View File

@ -7,16 +7,12 @@
#include "util/log.h" #include "util/log.h"
bool 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); bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) { if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
receiver->acksync = acksync;
return true; return true;
} }
@ -26,7 +22,7 @@ receiver_destroy(struct receiver *receiver) {
} }
static void static void
process_msg(struct receiver *receiver, struct device_msg *msg) { process_msg(struct device_msg *msg) {
switch (msg->type) { switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: { case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText(); char *current = SDL_GetClipboardText();
@ -41,17 +37,11 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
SDL_SetClipboardText(msg->clipboard.text); SDL_SetClipboardText(msg->clipboard.text);
break; break;
} }
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
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 static ssize_t
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) { process_msgs(const unsigned char *buf, size_t len) {
size_t head = 0; size_t head = 0;
for (;;) { for (;;) {
struct device_msg msg; struct device_msg msg;
@ -63,7 +53,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
return head; return head;
} }
process_msg(receiver, &msg); process_msg(&msg);
device_msg_destroy(&msg); device_msg_destroy(&msg);
head += r; head += r;
@ -91,7 +81,7 @@ run_receiver(void *data) {
} }
head += r; head += r;
ssize_t consumed = process_msgs(receiver, buf, head); ssize_t consumed = process_msgs(buf, head);
if (consumed == -1) { if (consumed == -1) {
// an error occurred // an error occurred
break; break;
@ -111,10 +101,10 @@ bool
receiver_start(struct receiver *receiver) { receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread"); LOGD("Starting receiver thread");
bool ok = sc_thread_create(&receiver->thread, run_receiver, bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
"scrcpy-receiver", receiver); receiver);
if (!ok) { if (!ok) {
LOGE("Could not start receiver thread"); LOGC("Could not start receiver thread");
return false; return false;
} }

View File

@ -1,11 +1,10 @@
#ifndef SC_RECEIVER_H #ifndef RECEIVER_H
#define SC_RECEIVER_H #define RECEIVER_H
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "util/acksync.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h" #include "util/thread.h"
@ -15,13 +14,10 @@ struct receiver {
sc_socket control_socket; sc_socket control_socket;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
struct sc_acksync *acksync;
}; };
bool bool
receiver_init(struct receiver *receiver, sc_socket control_socket, receiver_init(struct receiver *receiver, sc_socket control_socket);
struct sc_acksync *acksync);
void void
receiver_destroy(struct receiver *receiver); receiver_destroy(struct receiver *receiver);

View File

@ -9,7 +9,7 @@
#include "util/str.h" #include "util/str.h"
/** Downcast packet_sink to recorder */ /** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink) #define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -30,17 +30,15 @@ find_muxer(const char *name) {
return oformat; return oformat;
} }
static struct sc_record_packet * static struct record_packet *
sc_record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct sc_record_packet *rec = malloc(sizeof(*rec)); struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) { if (!rec) {
LOG_OOM();
return NULL; return NULL;
} }
rec->packet = av_packet_alloc(); rec->packet = av_packet_alloc();
if (!rec->packet) { if (!rec->packet) {
LOG_OOM();
free(rec); free(rec);
return NULL; return NULL;
} }
@ -54,22 +52,22 @@ sc_record_packet_new(const AVPacket *packet) {
} }
static void static void
sc_record_packet_delete(struct sc_record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_free(&rec->packet); av_packet_free(&rec->packet);
free(rec); free(rec);
} }
static void static void
sc_recorder_queue_clear(struct sc_recorder_queue *queue) { recorder_queue_clear(struct recorder_queue *queue) {
while (!sc_queue_is_empty(queue)) { while (!sc_queue_is_empty(queue)) {
struct sc_record_packet *rec; struct record_packet *rec;
sc_queue_take(queue, next, &rec); sc_queue_take(queue, next, &rec);
sc_record_packet_delete(rec); record_packet_delete(rec);
} }
} }
static const char * static const char *
sc_recorder_get_format_name(enum sc_record_format format) { recorder_get_format_name(enum sc_record_format format) {
switch (format) { switch (format) {
case SC_RECORD_FORMAT_MP4: return "mp4"; case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska"; case SC_RECORD_FORMAT_MKV: return "matroska";
@ -78,12 +76,12 @@ sc_recorder_get_format_name(enum sc_record_format format) {
} }
static bool static bool
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) { recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t)); uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) { if (!extradata) {
LOG_OOM(); LOGC("Could not allocate extradata");
return false; return false;
} }
@ -103,19 +101,19 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
} }
static void static void
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) { recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
} }
static bool static bool
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) { recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) { if (packet->pts != AV_NOPTS_VALUE) {
LOGE("The first packet is not a config packet"); LOGE("The first packet is not a config packet");
return false; return false;
} }
bool ok = sc_recorder_write_header(recorder, packet); bool ok = recorder_write_header(recorder, packet);
if (!ok) { if (!ok) {
return false; return false;
} }
@ -128,13 +126,13 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
return true; return true;
} }
sc_recorder_rescale_packet(recorder, packet); recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0; return av_write_frame(recorder->ctx, packet) >= 0;
} }
static int static int
run_recorder(void *data) { run_recorder(void *data) {
struct sc_recorder *recorder = data; struct recorder *recorder = data;
for (;;) { for (;;) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
@ -148,29 +146,29 @@ run_recorder(void *data) {
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct sc_record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
// assign an arbitrary duration to the last packet // assign an arbitrary duration to the last packet
last->packet->duration = 100000; last->packet->duration = 100000;
bool ok = sc_recorder_write(recorder, last->packet); bool ok = recorder_write(recorder, last->packet);
if (!ok) { if (!ok) {
// failing to write the last frame is not very serious, no // failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file // future frame may depend on it, so the resulting file
// will still be valid // will still be valid
LOGW("Could not record last packet"); LOGW("Could not record last packet");
} }
sc_record_packet_delete(last); record_packet_delete(last);
} }
break; break;
} }
struct sc_record_packet *rec; struct record_packet *rec;
sc_queue_take(&recorder->queue, next, &rec); sc_queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock // recorder->previous is only written from this thread, no need to lock
struct sc_record_packet *previous = recorder->previous; struct record_packet *previous = recorder->previous;
recorder->previous = rec; recorder->previous = rec;
if (!previous) { if (!previous) {
@ -186,15 +184,15 @@ run_recorder(void *data) {
rec->packet->pts - previous->packet->pts; rec->packet->pts - previous->packet->pts;
} }
bool ok = sc_recorder_write(recorder, previous->packet); bool ok = recorder_write(recorder, previous->packet);
sc_record_packet_delete(previous); record_packet_delete(previous);
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->failed = true; recorder->failed = true;
// discard pending packets // discard pending packets
sc_recorder_queue_clear(&recorder->queue); recorder_queue_clear(&recorder->queue);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
break; break;
} }
@ -216,7 +214,7 @@ run_recorder(void *data) {
if (recorder->failed) { if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename); LOGE("Recording failed to %s", recorder->filename);
} else { } else {
const char *format_name = sc_recorder_get_format_name(recorder->format); const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, LOGI("Recording complete to %s file: %s", format_name,
recorder->filename); recorder->filename);
} }
@ -227,14 +225,16 @@ run_recorder(void *data) {
} }
static bool static bool
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) { recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
bool ok = sc_mutex_init(&recorder->mutex); bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex");
return false; return false;
} }
ok = sc_cond_init(&recorder->queue_cond); ok = sc_cond_init(&recorder->queue_cond);
if (!ok) { if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy; goto error_mutex_destroy;
} }
@ -244,7 +244,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
recorder->header_written = false; recorder->header_written = false;
recorder->previous = NULL; recorder->previous = NULL;
const char *format_name = sc_recorder_get_format_name(recorder->format); const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name); assert(format_name);
const AVOutputFormat *format = find_muxer(format_name); const AVOutputFormat *format = find_muxer(format_name);
if (!format) { if (!format) {
@ -254,7 +254,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
recorder->ctx = avformat_alloc_context(); recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) { if (!recorder->ctx) {
LOG_OOM(); LOGE("Could not allocate output context");
goto error_cond_destroy; goto error_cond_destroy;
} }
@ -287,10 +287,10 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
} }
LOGD("Starting recorder thread"); LOGD("Starting recorder thread");
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder", ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder); recorder);
if (!ok) { if (!ok) {
LOGE("Could not start recorder thread"); LOGC("Could not start recorder thread");
goto error_avio_close; goto error_avio_close;
} }
@ -311,7 +311,7 @@ error_mutex_destroy:
} }
static void static void
sc_recorder_close(struct sc_recorder *recorder) { recorder_close(struct recorder *recorder) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
@ -326,7 +326,7 @@ sc_recorder_close(struct sc_recorder *recorder) {
} }
static bool static bool
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
@ -336,9 +336,9 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
return false; return false;
} }
struct sc_record_packet *rec = sc_record_packet_new(packet); struct record_packet *rec = record_packet_new(packet);
if (!rec) { if (!rec) {
LOG_OOM(); LOGC("Could not allocate record packet");
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
@ -351,33 +351,31 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
} }
static bool static bool
sc_recorder_packet_sink_open(struct sc_packet_sink *sink, recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
const AVCodec *codec) { struct recorder *recorder = DOWNCAST(sink);
struct sc_recorder *recorder = DOWNCAST(sink); return recorder_open(recorder, codec);
return sc_recorder_open(recorder, codec);
} }
static void static void
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) { recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct sc_recorder *recorder = DOWNCAST(sink); struct recorder *recorder = DOWNCAST(sink);
sc_recorder_close(recorder); recorder_close(recorder);
} }
static bool static bool
sc_recorder_packet_sink_push(struct sc_packet_sink *sink, recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
const AVPacket *packet) { struct recorder *recorder = DOWNCAST(sink);
struct sc_recorder *recorder = DOWNCAST(sink); return recorder_push(recorder, packet);
return sc_recorder_push(recorder, packet);
} }
bool bool
sc_recorder_init(struct sc_recorder *recorder, recorder_init(struct recorder *recorder,
const char *filename, const char *filename,
enum sc_record_format format, enum sc_record_format format,
struct sc_size declared_frame_size) { struct sc_size declared_frame_size) {
recorder->filename = strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOG_OOM(); LOGE("Could not strdup filename");
return false; return false;
} }
@ -385,9 +383,9 @@ sc_recorder_init(struct sc_recorder *recorder,
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
static const struct sc_packet_sink_ops ops = { static const struct sc_packet_sink_ops ops = {
.open = sc_recorder_packet_sink_open, .open = recorder_packet_sink_open,
.close = sc_recorder_packet_sink_close, .close = recorder_packet_sink_close,
.push = sc_recorder_packet_sink_push, .push = recorder_packet_sink_push,
}; };
recorder->packet_sink.ops = &ops; recorder->packet_sink.ops = &ops;
@ -396,6 +394,6 @@ sc_recorder_init(struct sc_recorder *recorder,
} }
void void
sc_recorder_destroy(struct sc_recorder *recorder) { recorder_destroy(struct recorder *recorder) {
free(recorder->filename); free(recorder->filename);
} }

View File

@ -1,5 +1,5 @@
#ifndef SC_RECORDER_H #ifndef RECORDER_H
#define SC_RECORDER_H #define RECORDER_H
#include "common.h" #include "common.h"
@ -12,14 +12,14 @@
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h" #include "util/thread.h"
struct sc_record_packet { struct record_packet {
AVPacket *packet; AVPacket *packet;
struct sc_record_packet *next; struct record_packet *next;
}; };
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet); struct recorder_queue SC_QUEUE(struct record_packet);
struct sc_recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait struct sc_packet_sink packet_sink; // packet sink trait
char *filename; char *filename;
@ -33,21 +33,20 @@ struct sc_recorder {
sc_cond queue_cond; sc_cond queue_cond;
bool stopped; // set on recorder_close() bool stopped; // set on recorder_close()
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct sc_recorder_queue queue; struct recorder_queue queue;
// we can write a packet only once we received the next one so that we can // we can write a packet only once we received the next one so that we can
// set its duration (next_pts - current_pts) // set its duration (next_pts - current_pts)
// "previous" is only accessed from the recorder thread, so it does not // "previous" is only accessed from the recorder thread, so it does not
// need to be protected by the mutex // need to be protected by the mutex
struct sc_record_packet *previous; struct record_packet *previous;
}; };
bool bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder_init(struct recorder *recorder, const char *filename,
enum sc_record_format format, enum sc_record_format format, struct sc_size declared_frame_size);
struct sc_size declared_frame_size);
void void
sc_recorder_destroy(struct sc_recorder *recorder); recorder_destroy(struct recorder *recorder);
#endif #endif

View File

@ -15,21 +15,18 @@
#include "controller.h" #include "controller.h"
#include "decoder.h" #include "decoder.h"
#include "demuxer.h"
#include "events.h" #include "events.h"
#include "file_pusher.h" #include "file_handler.h"
#include "input_manager.h"
#ifdef HAVE_AOA_HID
# include "hid_keyboard.h"
#endif
#include "keyboard_inject.h" #include "keyboard_inject.h"
#include "mouse_inject.h" #include "mouse_inject.h"
#include "recorder.h" #include "recorder.h"
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#ifdef HAVE_USB #include "stream.h"
# 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" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@ -38,33 +35,26 @@
struct scrcpy { struct scrcpy {
struct sc_server server; struct sc_server server;
struct sc_screen screen; struct screen screen;
struct sc_demuxer demuxer; struct stream stream;
struct sc_decoder decoder; struct decoder decoder;
struct sc_recorder recorder; struct recorder recorder;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink; struct sc_v4l2_sink v4l2_sink;
#endif #endif
struct sc_controller controller; struct controller controller;
struct sc_file_pusher file_pusher; struct file_handler file_handler;
#ifdef HAVE_USB #ifdef HAVE_AOA_HID
struct sc_usb usb;
struct sc_aoa aoa; struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif #endif
union { union {
struct sc_keyboard_inject keyboard_inject; struct sc_keyboard_inject keyboard_inject;
#ifdef HAVE_USB #ifdef HAVE_AOA_HID
struct sc_hid_keyboard keyboard_hid; struct sc_hid_keyboard keyboard_hid;
#endif #endif
}; };
union { struct sc_mouse_inject mouse_inject;
struct sc_mouse_inject mouse_inject; struct input_manager input_manager;
#ifdef HAVE_USB
struct sc_hid_mouse mouse_hid;
#endif
};
}; };
static inline void static inline void
@ -101,10 +91,12 @@ sdl_set_hints(const char *render_driver) {
LOGW("Could not enable linear filtering"); LOGW("Could not enable linear filtering");
} }
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click // Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) { if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough"); LOGW("Could not enable mouse focus clickthrough");
} }
#endif
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS #ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events // Disable synthetic mouse events from touch events
@ -143,47 +135,101 @@ sdl_configure(bool display, bool disable_screensaver) {
} }
if (disable_screensaver) { if (disable_screensaver) {
LOGD("Screensaver disabled");
SDL_DisableScreenSaver(); SDL_DisableScreenSaver();
} else { } else {
LOGD("Screensaver enabled");
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
} }
} }
static enum scrcpy_exit_code static bool
event_loop(struct scrcpy *s) { 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,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
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;
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;
}
file_handler_action_t action;
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&s->file_handler, action, file);
goto end;
}
}
bool consumed = screen_handle_event(&s->screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&s->input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
switch (event.type) { enum event_result result = handle_event(s, options, &event);
case EVENT_STREAM_STOPPED: switch (result) {
case EVENT_RESULT_STOPPED_BY_USER:
return true;
case EVENT_RESULT_STOPPED_BY_EOS:
LOGW("Device disconnected"); LOGW("Device disconnected");
return SCRCPY_EXIT_DISCONNECTED; return false;
case SDL_QUIT: case EVENT_RESULT_CONTINUE:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
default:
sc_screen_handle_event(&s->screen, &event);
break; break;
} }
} }
return SCRCPY_EXIT_FAILURE; return false;
} }
// Return true on success, false on error
static bool static bool
await_for_server(bool *connected) { await_for_server(void) {
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
switch (event.type) { switch (event.type) {
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
*connected = false; return false;
return true;
case EVENT_SERVER_CONNECTION_FAILED: case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed"); LOGE("Server connection failed");
return false; return false;
case EVENT_SERVER_CONNECTED: case EVENT_SERVER_CONNECTED:
LOGD("Server connected"); LOGD("Server connected");
*connected = true;
return true; return true;
default: default:
break; break;
@ -222,7 +268,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
size_t fmt_len = strlen(fmt); size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10); char *local_fmt = malloc(fmt_len + 10);
if (!local_fmt) { if (!local_fmt) {
LOG_OOM(); LOGC("Could not allocate string");
return; return;
} }
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0' memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
@ -232,8 +278,8 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
} }
static void static void
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) { stream_on_eos(struct stream *stream, void *userdata) {
(void) demuxer; (void) stream;
(void) userdata; (void) userdata;
PUSH_EVENT(EVENT_STREAM_STOPPED); PUSH_EVENT(EVENT_STREAM_STOPPED);
@ -265,48 +311,40 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
// event // event
} }
enum scrcpy_exit_code bool
scrcpy(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy; struct scrcpy *s = &scrcpy;
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError()); LOGC("Could not initialize SDL: %s", SDL_GetError());
return SCRCPY_EXIT_FAILURE; return false;
} }
atexit(SDL_Quit); atexit(SDL_Quit);
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE; bool ret = false;
bool server_started = false; bool server_started = false;
bool file_pusher_initialized = false; bool file_handler_initialized = false;
bool recorder_initialized = false; bool recorder_initialized = false;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
bool v4l2_sink_initialized = false; bool v4l2_sink_initialized = false;
#endif #endif
bool demuxer_started = false; bool stream_started = false;
#ifdef HAVE_USB #ifdef HAVE_AOA_HID
bool aoa_hid_initialized = false; bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
#endif #endif
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false; bool screen_initialized = false;
struct sc_acksync *acksync = NULL;
struct sc_server_params params = { struct sc_server_params params = {
.req_serial = options->serial, .serial = options->serial,
.select_usb = options->select_usb,
.select_tcpip = options->select_tcpip,
.log_level = options->log_level, .log_level = options->log_level,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size, .max_size = options->max_size,
.bit_rate = options->bit_rate, .bit_rate = options->bit_rate,
.max_fps = options->max_fps, .max_fps = options->max_fps,
@ -319,14 +357,6 @@ scrcpy(struct scrcpy_options *options) {
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close, .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,
.cleanup = options->cleanup,
.power_on = options->power_on,
.install = options->install,
.reinstall = options->reinstall,
}; };
static const struct sc_server_callbacks cbs = { static const struct sc_server_callbacks cbs = {
@ -335,7 +365,7 @@ scrcpy(struct scrcpy_options *options) {
.on_disconnected = sc_server_on_disconnected, .on_disconnected = sc_server_on_disconnected,
}; };
if (!sc_server_init(&s->server, &params, &cbs, NULL)) { if (!sc_server_init(&s->server, &params, &cbs, NULL)) {
return SCRCPY_EXIT_FAILURE; return false;
} }
if (!sc_server_start(&s->server)) { if (!sc_server_start(&s->server)) {
@ -350,57 +380,47 @@ scrcpy(struct scrcpy_options *options) {
// Initialize SDL video in addition if display is enabled // Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) { if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGE("Could not initialize SDL: %s", SDL_GetError()); LOGC("Could not initialize SDL: %s", SDL_GetError());
goto end; goto end;
} }
sdl_configure(options->display, options->disable_screensaver); sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling // Await for server without blocking Ctrl+C handling
bool connected; if (!await_for_server()) {
if (!await_for_server(&connected)) {
goto end;
}
if (!connected) {
// This is not an error, user requested to quit
ret = SCRCPY_EXIT_SUCCESS;
goto end; goto end;
} }
// It is necessarily initialized here, since the device is connected // It is necessarily initialized here, since the device is connected
struct sc_server_info *info = &s->server.info; struct sc_server_info *info = &s->server.info;
const char *serial = s->server.serial; const char *serial = s->server.params.serial;
assert(serial); assert(serial);
struct sc_file_pusher *fp = NULL;
if (options->display && options->control) { if (options->display && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial, if (!file_handler_init(&s->file_handler, serial,
options->push_target)) { options->push_target)) {
goto end; goto end;
} }
fp = &s->file_pusher; file_handler_initialized = true;
file_pusher_initialized = true;
} }
struct sc_decoder *dec = NULL; struct decoder *dec = NULL;
bool needs_decoder = options->display; bool needs_decoder = options->display;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
needs_decoder |= !!options->v4l2_device; needs_decoder |= !!options->v4l2_device;
#endif #endif
if (needs_decoder) { if (needs_decoder) {
sc_decoder_init(&s->decoder); decoder_init(&s->decoder);
dec = &s->decoder; dec = &s->decoder;
} }
struct sc_recorder *rec = NULL; struct recorder *rec = NULL;
if (options->record_filename) { if (options->record_filename) {
if (!sc_recorder_init(&s->recorder, if (!recorder_init(&s->recorder,
options->record_filename, options->record_filename,
options->record_format, options->record_format,
info->frame_size)) { info->frame_size)) {
goto end; goto end;
} }
rec = &s->recorder; rec = &s->recorder;
@ -409,187 +429,46 @@ scrcpy(struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
static const struct sc_demuxer_callbacks demuxer_cbs = { static const struct stream_callbacks stream_cbs = {
.on_eos = sc_demuxer_on_eos, .on_eos = stream_on_eos,
}; };
sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL); stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
if (dec) { if (dec) {
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink); stream_add_sink(&s->stream, &dec->packet_sink);
} }
if (rec) { if (rec) {
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink); 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;
if (options->control) { if (options->control) {
#ifdef HAVE_USB if (!controller_init(&s->controller, s->server.control_socket)) {
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 ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
}
ok = sc_usb_init(&s->usb);
if (!ok) {
LOGE("Failed to initialize USB");
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
assert(serial);
struct sc_usb_device usb_device;
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
if (!ok) {
sc_usb_destroy(&s->usb);
goto aoa_hid_end;
}
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, NULL, NULL);
sc_usb_device_destroy(&usb_device);
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;
}
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;
}
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 (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);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
acksync = &s->acksync;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_initialized) {
if (hid_keyboard_initialized) {
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) {
LOGE("Fallback to default keyboard injection method "
"(-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
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_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 (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end; goto end;
} }
controller_initialized = true; controller_initialized = true;
if (!sc_controller_start(&s->controller)) { if (!controller_start(&s->controller)) {
goto end; goto end;
} }
controller_started = true; controller_started = true;
controller = &s->controller;
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct sc_control_msg msg; struct control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF; msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) { if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
} }
} }
} }
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->display) { if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info->device_name; options->window_title ? options->window_title : info->device_name;
struct sc_screen_params screen_params = { struct screen_params screen_params = {
.controller = controller,
.fp = fp,
.kp = kp,
.mp = mp,
.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, .window_title = window_title,
.frame_size = info->frame_size, .frame_size = info->frame_size,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
@ -601,16 +480,15 @@ aoa_hid_end:
.rotation = options->rotation, .rotation = options->rotation,
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
.fullscreen = options->fullscreen, .fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.buffering_time = options->display_buffer, .buffering_time = options->display_buffer,
}; };
if (!sc_screen_init(&s->screen, &screen_params)) { if (!screen_init(&s->screen, &screen_params)) {
goto end; goto end;
} }
screen_initialized = true; screen_initialized = true;
sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink); decoder_add_sink(&s->decoder, &s->screen.frame_sink);
} }
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@ -620,52 +498,101 @@ aoa_hid_end:
goto end; goto end;
} }
sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink); decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
v4l2_sink_initialized = true; v4l2_sink_initialized = true;
} }
#endif #endif
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
// start the demuxer // start the stream
if (!sc_demuxer_start(&s->demuxer)) { if (!stream_start(&s->stream)) {
goto end; goto end;
} }
demuxer_started = true; stream_started = true;
ret = event_loop(s); struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
#ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
bool ok = sc_aoa_init(&s->aoa, serial);
if (!ok) {
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;
}
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
options);
ret = event_loop(s, options);
LOGD("quit..."); LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may // 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 stream thread is joined (it may take time)
sc_screen_hide_window(&s->screen); screen_hide_window(&s->screen);
end: end:
// The demuxer is not stopped explicitly, because it will stop by itself on // The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream // end-of-stream
#ifdef HAVE_USB #ifdef HAVE_AOA_HID
if (aoa_hid_initialized) { if (aoa_hid_initialized) {
if (hid_keyboard_initialized) { sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_hid_keyboard_destroy(&s->keyboard_hid);
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
}
sc_aoa_stop(&s->aoa); sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);
}
if (acksync) {
sc_acksync_destroy(acksync);
} }
#endif #endif
if (controller_started) { if (controller_started) {
sc_controller_stop(&s->controller); controller_stop(&s->controller);
} }
if (file_pusher_initialized) { if (file_handler_initialized) {
sc_file_pusher_stop(&s->file_pusher); file_handler_stop(&s->file_handler);
} }
if (screen_initialized) { if (screen_initialized) {
sc_screen_interrupt(&s->screen); screen_interrupt(&s->screen);
} }
if (server_started) { if (server_started) {
@ -673,10 +600,10 @@ end:
sc_server_stop(&s->server); sc_server_stop(&s->server);
} }
// now that the sockets are shutdown, the demuxer and controller are // now that the sockets are shutdown, the stream and controller are
// interrupted, we can join them // interrupted, we can join them
if (demuxer_started) { if (stream_started) {
sc_demuxer_join(&s->demuxer); stream_join(&s->stream);
} }
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@ -685,37 +612,34 @@ end:
} }
#endif #endif
#ifdef HAVE_USB #ifdef HAVE_AOA_HID
if (aoa_hid_initialized) { if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa); sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa); sc_aoa_destroy(&s->aoa);
sc_usb_join(&s->usb);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
} }
#endif #endif
// Destroy the screen only after the demuxer is guaranteed to be finished, // Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction // because otherwise the screen could receive new frames after destruction
if (screen_initialized) { if (screen_initialized) {
sc_screen_join(&s->screen); screen_join(&s->screen);
sc_screen_destroy(&s->screen); screen_destroy(&s->screen);
} }
if (controller_started) { if (controller_started) {
sc_controller_join(&s->controller); controller_join(&s->controller);
} }
if (controller_initialized) { if (controller_initialized) {
sc_controller_destroy(&s->controller); controller_destroy(&s->controller);
} }
if (recorder_initialized) { if (recorder_initialized) {
sc_recorder_destroy(&s->recorder); recorder_destroy(&s->recorder);
} }
if (file_pusher_initialized) { if (file_handler_initialized) {
sc_file_pusher_join(&s->file_pusher); file_handler_join(&s->file_handler);
sc_file_pusher_destroy(&s->file_pusher); file_handler_destroy(&s->file_handler);
} }
sc_server_destroy(&s->server); sc_server_destroy(&s->server);

View File

@ -6,18 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "options.h" #include "options.h"
enum scrcpy_exit_code { bool
// 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); scrcpy(struct scrcpy_options *options);
#endif #endif

View File

@ -12,7 +12,7 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink) #define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct sc_size static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) { 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 // get the window size in a struct sc_size
static struct sc_size static struct sc_size
get_window_size(const struct sc_screen *screen) { get_window_size(const struct screen *screen) {
int width; int width;
int height; int height;
SDL_GetWindowSize(screen->window, &width, &height); SDL_GetWindowSize(screen->window, &width, &height);
@ -41,7 +41,7 @@ get_window_size(const struct sc_screen *screen) {
} }
static struct sc_point static struct sc_point
get_window_position(const struct sc_screen *screen) { get_window_position(const struct screen *screen) {
int x; int x;
int y; int y;
SDL_GetWindowPosition(screen->window, &x, &y); SDL_GetWindowPosition(screen->window, &x, &y);
@ -54,7 +54,7 @@ get_window_position(const struct sc_screen *screen) {
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void
set_window_size(struct sc_screen *screen, struct sc_size new_size) { set_window_size(struct screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height); SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -64,7 +64,12 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
static bool static bool
get_preferred_display_bounds(struct sc_size *bounds) { get_preferred_display_bounds(struct sc_size *bounds) {
SDL_Rect rect; SDL_Rect rect;
if (SDL_GetDisplayUsableBounds(0, &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)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError()); LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false; return false;
} }
@ -90,8 +95,7 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct sc_size static struct 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) { if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
@ -100,10 +104,10 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size,
struct sc_size window_size; struct sc_size window_size;
struct sc_size display_size; struct sc_size display_size;
if (!within_display_bounds || if (!get_preferred_display_bounds(&display_size)) {
!get_preferred_display_bounds(&display_size)) { // could not get display bounds, do not constraint the size
// do not constraint the size window_size.width = current_size.width;
window_size = current_size; window_size.height = current_size.height;
} else { } else {
window_size.width = MIN(current_size.width, display_size.width); window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height); window_size.height = MIN(current_size.height, display_size.height);
@ -136,7 +140,7 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
uint16_t req_height) { uint16_t req_height) {
struct sc_size window_size; struct sc_size window_size;
if (!req_width && !req_height) { if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size, true); window_size = get_optimal_size(content_size, content_size);
} else { } else {
if (req_width) { if (req_width) {
window_size.width = req_width; window_size.width = req_width;
@ -156,55 +160,8 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size; 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 static void
sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) { screen_update_content_rect(struct screen *screen) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
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());
}
}
static inline bool
sc_screen_get_mouse_capture(struct sc_screen *screen) {
(void) screen;
return SDL_GetRelativeMouseMode();
}
static inline void
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
sc_screen_update_content_rect(struct sc_screen *screen) {
int dw; int dw;
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@ -241,7 +198,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
} }
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct sc_screen *screen) { create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size; struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
@ -272,9 +229,9 @@ create_texture(struct sc_screen *screen) {
// Set the update_content_rect flag if the window or content size may have // Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed // changed, so that the content rectangle is recomputed
static void static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) { screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) { if (update_content_rect) {
sc_screen_update_content_rect(screen); screen_update_content_rect(screen);
} }
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
@ -318,20 +275,20 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178> // <https://stackoverflow.com/a/40693139/1987178>
static int static int
event_watcher(void *data, SDL_Event *event) { event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data; struct screen *screen = data;
if (event->type == SDL_WINDOWEVENT if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) { && event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in // In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround. // that specific case. Anyway, it's just a workaround.
sc_screen_render(screen, true); screen_render(screen, true);
} }
return 0; return 0;
} }
#endif #endif
static bool static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink) { screen_frame_sink_open(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
(void) screen; (void) screen;
#ifndef NDEBUG #ifndef NDEBUG
screen->open = true; screen->open = true;
@ -342,8 +299,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
} }
static void static void
sc_screen_frame_sink_close(struct sc_frame_sink *sink) { screen_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
(void) screen; (void) screen;
#ifndef NDEBUG #ifndef NDEBUG
screen->open = false; screen->open = false;
@ -353,8 +310,8 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
} }
static bool static bool
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame); return sc_video_buffer_push(&screen->vb, frame);
} }
@ -362,7 +319,7 @@ static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) { void *userdata) {
(void) vb; (void) vb;
struct sc_screen *screen = userdata; struct screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have // event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent) // been consumed if the event was not sent)
@ -370,7 +327,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
bool need_new_event; bool need_new_event;
if (previous_skipped) { if (previous_skipped) {
sc_fps_counter_add_skipped_frame(&screen->fps_counter); fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed // this new frame instead, unless the previous event failed
need_new_event = screen->event_failed; need_new_event = screen->event_failed;
@ -395,21 +352,12 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
} }
bool bool
sc_screen_init(struct sc_screen *screen, screen_init(struct screen *screen, const struct screen_params *params) {
const struct sc_screen_params *params) {
screen->resize_pending = false; screen->resize_pending = false;
screen->has_frame = false; screen->has_frame = false;
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
screen->event_failed = false; screen->event_failed = 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;
screen->req.start_fps_counter = params->start_fps_counter;
static const struct sc_video_buffer_callbacks cbs = { static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame, .on_new_frame = sc_video_buffer_on_new_frame,
@ -418,15 +366,18 @@ sc_screen_init(struct sc_screen *screen,
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs, bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen); screen);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&screen->vb); ok = sc_video_buffer_start(&screen->vb);
if (!ok) { if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer; goto error_destroy_video_buffer;
} }
if (!sc_fps_counter_init(&screen->fps_counter)) { if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer; goto error_stop_and_join_video_buffer;
} }
@ -439,28 +390,40 @@ sc_screen_init(struct sc_screen *screen,
get_rotated_size(screen->frame_size, screen->rotation); get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size; screen->content_size = content_size;
struct sc_size window_size =
get_initial_optimal_size(content_size,params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE | SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI; | SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) { if (params->always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
} }
if (params->window_borderless) { if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
// The window will be positioned and sized on first video frame int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
screen->window = ? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); 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);
if (!screen->window) { if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError()); LOGC("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter; goto error_destroy_fps_counter;
} }
screen->renderer = SDL_CreateRenderer(screen->window, -1, screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED); SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window; goto error_destroy_window;
} }
@ -509,38 +472,35 @@ sc_screen_init(struct sc_screen *screen,
params->frame_size.height); params->frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGE("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
goto error_destroy_renderer; goto error_destroy_renderer;
} }
screen->frame = av_frame_alloc(); screen->frame = av_frame_alloc();
if (!screen->frame) { if (!screen->frame) {
LOG_OOM(); LOGC("Could not create screen frame");
goto error_destroy_texture; goto error_destroy_texture;
} }
struct sc_input_manager_params im_params = { // Reset the window size to trigger a SIZE_CHANGED event, to workaround
.controller = params->controller, // HiDPI issues with some SDL renderers when several displays having
.fp = params->fp, // different HiDPI scaling are connected
.screen = screen, SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
.kp = params->kp,
.mp = params->mp,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
sc_input_manager_init(&screen->im, &im_params); screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen); SDL_AddEventWatch(event_watcher, screen);
#endif #endif
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
.open = sc_screen_frame_sink_open, .open = screen_frame_sink_open,
.close = sc_screen_frame_sink_close, .close = screen_frame_sink_close,
.push = sc_screen_frame_sink_push, .push = screen_frame_sink_push,
}; };
screen->frame_sink.ops = &ops; screen->frame_sink.ops = &ops;
@ -558,7 +518,7 @@ error_destroy_renderer:
error_destroy_window: error_destroy_window:
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
error_destroy_fps_counter: error_destroy_fps_counter:
sc_fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer: error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb); sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb); sc_video_buffer_join(&screen->vb);
@ -569,49 +529,29 @@ error_destroy_video_buffer:
} }
static void static void
sc_screen_show_initial_window(struct sc_screen *screen) { screen_show_window(struct 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);
}
if (screen->req.start_fps_counter) {
sc_fps_counter_start(&screen->fps_counter);
}
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void void
sc_screen_hide_window(struct sc_screen *screen) { screen_hide_window(struct screen *screen) {
SDL_HideWindow(screen->window); SDL_HideWindow(screen->window);
} }
void void
sc_screen_interrupt(struct sc_screen *screen) { screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb); sc_video_buffer_stop(&screen->vb);
sc_fps_counter_interrupt(&screen->fps_counter); fps_counter_interrupt(&screen->fps_counter);
} }
void void
sc_screen_join(struct sc_screen *screen) { screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb); sc_video_buffer_join(&screen->vb);
sc_fps_counter_join(&screen->fps_counter); fps_counter_join(&screen->fps_counter);
} }
void void
sc_screen_destroy(struct sc_screen *screen) { screen_destroy(struct screen *screen) {
#ifndef NDEBUG #ifndef NDEBUG
assert(!screen->open); assert(!screen->open);
#endif #endif
@ -619,12 +559,12 @@ sc_screen_destroy(struct sc_screen *screen) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
sc_fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb); sc_video_buffer_destroy(&screen->vb);
} }
static void static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size, resize_for_content(struct screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) { struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen); struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = { struct sc_size target_size = {
@ -633,12 +573,12 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
.height = (uint32_t) window_size.height * new_content_size.height .height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height, / old_content_size.height,
}; };
target_size = get_optimal_size(target_size, new_content_size, true); target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
} }
static void static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) { set_content_size(struct screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) { if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size); resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) { } else if (!screen->resize_pending) {
@ -652,7 +592,7 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
} }
static void static void
apply_pending_resize(struct sc_screen *screen) { apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
if (screen->resize_pending) { if (screen->resize_pending) {
@ -663,7 +603,7 @@ apply_pending_resize(struct sc_screen *screen) {
} }
void void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) { screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4); assert(rotation < 4);
if (rotation == screen->rotation) { if (rotation == screen->rotation) {
return; return;
@ -677,12 +617,12 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
screen->rotation = rotation; screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation); LOGI("Display rotation set to %u", rotation);
sc_screen_render(screen, true); screen_render(screen, true);
} }
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool static bool
prepare_for_frame(struct sc_screen *screen, struct sc_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 if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@ -694,13 +634,13 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
get_rotated_size(new_frame_size, screen->rotation); get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size); set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen); screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGE("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
return false; return false;
} }
} }
@ -710,7 +650,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture // write the frame into the texture
static void static void
update_texture(struct sc_screen *screen, const AVFrame *frame) { update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL, SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
@ -724,12 +664,12 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
} }
static bool static bool
sc_screen_update_frame(struct sc_screen *screen) { screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame); av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame); sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame; AVFrame *frame = screen->frame;
sc_fps_counter_add_rendered_frame(&screen->fps_counter); fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height}; struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
@ -737,23 +677,12 @@ sc_screen_update_frame(struct sc_screen *screen) {
} }
update_texture(screen, frame); update_texture(screen, frame);
if (!screen->has_frame) { screen_render(screen, false);
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_set_mouse_capture(screen, true);
}
}
sc_screen_render(screen, false);
return true; return true;
} }
void void
sc_screen_switch_fullscreen(struct sc_screen *screen) { screen_switch_fullscreen(struct screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@ -766,11 +695,11 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
sc_screen_render(screen, true); screen_render(screen, true);
} }
void void
sc_screen_resize_to_fit(struct sc_screen *screen) { screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen || screen->maximized) { if (screen->fullscreen || screen->maximized) {
return; return;
} }
@ -779,7 +708,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
struct sc_size window_size = get_window_size(screen); struct sc_size window_size = get_window_size(screen);
struct sc_size optimal_size = struct sc_size optimal_size =
get_optimal_size(window_size, screen->content_size, false); get_optimal_size(window_size, screen->content_size);
// Center the window related to the device screen // Center the window related to the device screen
assert(optimal_size.width <= window_size.width); assert(optimal_size.width <= window_size.width);
@ -794,7 +723,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
} }
void void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) { screen_resize_to_pixel_perfect(struct screen *screen) {
if (screen->fullscreen) { if (screen->fullscreen) {
return; return;
} }
@ -810,34 +739,31 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
content_size.height); content_size.height);
} }
static inline bool bool
sc_screen_is_mouse_capture_key(SDL_Keycode key) { screen_handle_event(struct screen *screen, SDL_Event *event) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
bool relative_mode = sc_screen_is_relative_mode(screen);
switch (event->type) { switch (event->type) {
case EVENT_NEW_FRAME: { case EVENT_NEW_FRAME:
bool ok = sc_screen_update_frame(screen); if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
if (!ok) { if (!ok) {
LOGW("Frame update failed\n"); LOGW("Frame update failed\n");
} }
return; return true;
}
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
if (!screen->has_frame) { if (!screen->has_frame) {
// Do nothing // Do nothing
return; return true;
} }
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
sc_screen_render(screen, true); screen_render(screen, true);
break; break;
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
sc_screen_render(screen, true); screen_render(screen, true);
break; break;
case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true; screen->maximized = true;
@ -853,79 +779,18 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
} }
screen->maximized = false; screen->maximized = false;
apply_pending_resize(screen); apply_pending_resize(screen);
sc_screen_render(screen, true); screen_render(screen, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (relative_mode) {
sc_screen_set_mouse_capture(screen, false);
}
break; break;
} }
return; return true;
case SDL_KEYDOWN:
if (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;
} 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;
}
}
break;
case SDL_KEYUP:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
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_toggle_mouse_capture(screen);
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return;
}
break;
case SDL_MOUSEBUTTONUP:
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
sc_screen_set_mouse_capture(screen, true);
return;
}
break;
} }
sc_input_manager_handle_event(&screen->im, event); return false;
} }
struct sc_point struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
unsigned rotation = screen->rotation; unsigned rotation = screen->rotation;
assert(rotation < 4); assert(rotation < 4);
@ -961,14 +826,14 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
} }
struct sc_point struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
sc_screen_hidpi_scale_coords(screen, &x, &y); screen_hidpi_scale_coords(screen, &x, &y);
return sc_screen_convert_drawable_to_frame_coords(screen, x, y); return screen_convert_drawable_to_frame_coords(screen, x, y);
} }
void void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) { screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account // take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh; int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh); SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@ -7,36 +7,21 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h" #include "coords.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h" #include "opengl.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
#include "video_buffer.h" #include "video_buffer.h"
struct sc_screen { struct screen {
struct sc_frame_sink frame_sink; // frame sink trait struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG #ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior bool open; // track the open/close state to assert correct behavior
#endif #endif
struct sc_input_manager im;
struct sc_video_buffer vb; struct sc_video_buffer vb;
struct sc_fps_counter fps_counter; 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;
bool start_fps_counter;
} req;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; SDL_Renderer *renderer;
@ -61,32 +46,18 @@ struct sc_screen {
bool event_failed; // in case SDL_PushEvent() returned an error 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;
AVFrame *frame; AVFrame *frame;
}; };
struct sc_screen_params { struct screen_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
const char *window_title; const char *window_title;
struct sc_size frame_size; struct sc_size frame_size;
bool always_on_top; bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED int16_t window_x;
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED int16_t window_y;
uint16_t window_width; uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless; bool window_borderless;
@ -94,72 +65,71 @@ struct sc_screen_params {
bool mipmaps; bool mipmaps;
bool fullscreen; bool fullscreen;
bool start_fps_counter;
sc_tick buffering_time; sc_tick buffering_time;
}; };
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool bool
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params); screen_init(struct screen *screen, const struct screen_params *params);
// request to interrupt any inner thread // request to interrupt any inner thread
// must be called before screen_join() // must be called before screen_join()
void void
sc_screen_interrupt(struct sc_screen *screen); screen_interrupt(struct screen *screen);
// join any inner thread // join any inner thread
void void
sc_screen_join(struct sc_screen *screen); screen_join(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void
sc_screen_destroy(struct sc_screen *screen); screen_destroy(struct screen *screen);
// hide the window // hide the window
// //
// It is used to hide the window immediately on closing without waiting for // It is used to hide the window immediately on closing without waiting for
// screen_destroy() // screen_destroy()
void void
sc_screen_hide_window(struct sc_screen *screen); screen_hide_window(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void
sc_screen_switch_fullscreen(struct sc_screen *screen); screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders) // resize window to optimal size (remove black borders)
void void
sc_screen_resize_to_fit(struct sc_screen *screen); screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen); screen_resize_to_pixel_perfect(struct screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise) // set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation); screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events // react to SDL events
void bool
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event); screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates // convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// Convert coordinates from window to drawable. // Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in // Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ // drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise. // otherwise.
void void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y); screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
#endif #endif

View File

@ -7,18 +7,17 @@
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb/adb.h" #include "adb.h"
#include "util/file.h" #include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/net_intr.h" #include "util/net_intr.h"
#include "util/process_intr.h" #include "util/process_intr.h"
#include "util/str.h" #include "util/str.h"
#define SC_SERVER_FILENAME "scrcpy-server.apk" #define SC_SERVER_FILENAME "scrcpy-server"
#define SC_SERVER_PACKAGE "com.genymobile.scrcpy"
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.apk" #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static char * static char *
get_server_path(void) { get_server_path(void) {
@ -35,7 +34,7 @@ get_server_path(void) {
char *server_path = strdup(server_path_env); char *server_path = strdup(server_path_env);
#endif #endif
if (!server_path) { if (!server_path) {
LOG_OOM(); LOGE("Could not allocate memory");
return NULL; return NULL;
} }
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path); LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
@ -46,7 +45,7 @@ get_server_path(void) {
LOGD("Using server: " SC_SERVER_PATH_DEFAULT); LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
char *server_path = strdup(SC_SERVER_PATH_DEFAULT); char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
if (!server_path) { if (!server_path) {
LOG_OOM(); LOGE("Could not allocate memory");
return NULL; return NULL;
} }
#else #else
@ -66,11 +65,10 @@ get_server_path(void) {
static void static void
sc_server_params_destroy(struct sc_server_params *params) { sc_server_params_destroy(struct sc_server_params *params) {
// The server stores a copy of the params provided by the user // The server stores a copy of the params provided by the user
free((char *) params->req_serial); free((char *) params->serial);
free((char *) params->crop); free((char *) params->crop);
free((char *) params->codec_options); free((char *) params->codec_options);
free((char *) params->encoder_name); free((char *) params->encoder_name);
free((char *) params->tcpip_dst);
} }
static bool static bool
@ -90,11 +88,10 @@ sc_server_params_copy(struct sc_server_params *dst,
} \ } \
} }
COPY(req_serial); COPY(serial);
COPY(crop); COPY(crop);
COPY(codec_options); COPY(codec_options);
COPY(encoder_name); COPY(encoder_name);
COPY(tcpip_dst);
#undef COPY #undef COPY
return true; return true;
@ -105,10 +102,7 @@ error:
} }
static bool static bool
push_server(struct sc_intr *intr, const char *serial, bool install, push_server(struct sc_intr *intr, const char *serial) {
bool reinstall) {
assert(install || !reinstall); // reinstall implies install
char *server_path = get_server_path(); char *server_path = get_server_path();
if (!server_path) { if (!server_path) {
return false; return false;
@ -118,28 +112,8 @@ push_server(struct sc_intr *intr, const char *serial, bool install,
free(server_path); free(server_path);
return false; return false;
} }
bool ok; bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH,
SC_STDERR);
if (install) {
char *version = sc_adb_get_installed_apk_version(intr, serial, 0);
bool same_version = version && !strcmp(version, SCRCPY_VERSION);
free(version);
if (!reinstall && same_version) {
LOGI("Server " SCRCPY_VERSION " already installed");
ok = true;
} else {
LOGI("Installing server " SCRCPY_VERSION);
// If a server with a different signature is installed, or if a
// newer server is already installed, we must uninstall it first.
ok = sc_adb_uninstall(intr, serial, SC_SERVER_PACKAGE,
SC_ADB_SILENT);
(void) ok; // expected to fail if it is not installed
ok = sc_adb_install(intr, serial, server_path, 0);
}
} else {
ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
}
free(server_path); free(server_path);
return ok; return ok;
} }
@ -163,79 +137,28 @@ 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 char *
get_classpath_cmd(struct sc_intr *intr, const char *serial, bool install) {
if (!install) {
// In push mode, the path is known statically
char *cp = strdup("CLASSPATH=" SC_DEVICE_SERVER_PATH);
if (!cp) {
LOG_OOM();
}
return cp;
}
char *apk_path = sc_adb_get_installed_apk_path(intr, serial, 0);
if (!apk_path) {
LOGE("Could not get device apk path");
return NULL;
}
#define PREFIX_SIZE (sizeof("CLASSPATH=") - 1)
size_t len = strlen(apk_path);
char *cp = malloc(PREFIX_SIZE + len + 1);
if (!cp) {
LOG_OOM();
free(apk_path);
return NULL;
}
memcpy(cp, "CLASSPATH=", PREFIX_SIZE);
memcpy(cp + PREFIX_SIZE, apk_path, len + 1);
free(apk_path);
return cp;
}
static sc_pid static sc_pid
execute_server(struct sc_server *server, execute_server(struct sc_server *server,
const struct sc_server_params *params) { const struct sc_server_params *params) {
sc_pid pid = SC_PROCESS_NONE; const char *serial = server->params.serial;
const char *serial = server->serial;
assert(serial);
char *classpath = get_classpath_cmd(&server->intr, serial, params->install);
if (!classpath) {
return SC_PROCESS_NONE;
}
LOGD("Using %s", classpath);
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;
cmd[count++] = "app_process";
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",
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005" # define SERVER_DEBUGGER_PORT "5005"
cmd[count++] =
# ifdef SERVER_DEBUGGER_METHOD_NEW # ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */ /* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y," "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
@ -244,85 +167,27 @@ execute_server(struct sc_server *server,
/* Android 8 and below */ /* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=" "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif # endif
SERVER_DEBUGGER_PORT; SERVER_DEBUGGER_PORT,
#endif #endif
cmd[count++] = "/"; // unused "/", // unused
cmd[count++] = "com.genymobile.scrcpy.Server"; "com.genymobile.scrcpy.Server",
cmd[count++] = SCRCPY_VERSION; SCRCPY_VERSION,
log_level_to_server_string(params->log_level),
unsigned dyn_idx = count; // from there, the strings are allocated max_size_string,
#define ADD_PARAM(fmt, ...) { \ bit_rate_string,
char *p = (char *) &cmd[count]; \ max_fps_string,
if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \ lock_video_orientation_string,
goto end; \ server->tunnel.forward ? "true" : "false",
} \ params->crop ? params->crop : "-",
cmd[count++] = p; \ "true", // always send frame meta (packet boundaries + timestamp)
} params->control ? "true" : "false",
display_id_string,
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); params->show_touches ? "true" : "false",
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-",
if (params->max_size) { params->encoder_name ? params->encoder_name : "-",
ADD_PARAM("max_size=%" PRIu16, params->max_size); params->power_off_on_close ? "true" : "false",
} };
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=true");
}
if (params->crop) {
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
// By default, control is true
ADD_PARAM("control=false");
}
if (params->display_id) {
ADD_PARAM("display_id=%" PRIu32, params->display_id);
}
if (params->show_touches) {
ADD_PARAM("show_touches=true");
}
if (params->stay_awake) {
ADD_PARAM("stay_awake=true");
}
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=true");
}
if (!params->clipboard_autosync) {
// 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");
}
if (!params->cleanup) {
// By default, cleanup is true
ADD_PARAM("cleanup=false");
}
if (!params->power_on) {
// By default, power_on is true
ADD_PARAM("power_on=false");
}
// TODO ADD_PARAM("install=…");
// The server must not rm in /data/local/tmp if installed
#undef ADD_PARAM
cmd[count++] = NULL;
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger waiting for a client on device port "
SERVER_DEBUGGER_PORT "..."); SERVER_DEBUGGER_PORT "...");
@ -335,22 +200,12 @@ execute_server(struct sc_server *server,
// Then click on "Debug" // Then click on "Debug"
#endif #endif
// Inherit both stdout and stderr (all server logs are printed to stdout) // Inherit both stdout and stderr (all server logs are printed to stdout)
pid = sc_adb_execute(cmd, 0); return adb_execute(serial, cmd, ARRAY_LEN(cmd), SC_STDOUT | SC_STDERR);
end:
free(classpath);
for (unsigned i = dyn_idx; i < count; ++i) {
free((char *) cmd[i]);
}
return pid;
} }
static bool static bool
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
uint32_t tunnel_host, uint16_t tunnel_port) { bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
if (!ok) { if (!ok) {
return false; return false;
} }
@ -367,13 +222,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
} }
static sc_socket static sc_socket
connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay, connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
uint32_t host, uint16_t port) { uint16_t port = server->tunnel.local_port;
do { do {
LOGD("Remaining connection attempts: %u", attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = net_socket(); sc_socket socket = net_socket();
if (socket != SC_SOCKET_NONE) { if (socket != SC_SOCKET_NONE) {
bool ok = connect_and_read_byte(&server->intr, socket, host, port); bool ok = connect_and_read_byte(&server->intr, socket, port);
if (ok) { if (ok) {
// it worked! // it worked!
return socket; return socket;
@ -388,14 +243,22 @@ connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
} }
if (attempts) { if (attempts) {
sc_mutex_lock(&server->mutex);
sc_tick deadline = sc_tick_now() + delay; sc_tick deadline = sc_tick_now() + delay;
bool ok = sc_server_sleep(server, deadline); bool timed_out = false;
if (!ok) { 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"); LOGI("Connection attempt stopped");
break; break;
} }
} }
} while (--attempts); } while (--attempts > 0);
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
@ -404,18 +267,20 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata) { const struct sc_server_callbacks *cbs, void *cbs_userdata) {
bool ok = sc_server_params_copy(&server->params, params); bool ok = sc_server_params_copy(&server->params, params);
if (!ok) { if (!ok) {
LOG_OOM(); LOGE("Could not copy server params");
return false; return false;
} }
ok = sc_mutex_init(&server->mutex); ok = sc_mutex_init(&server->mutex);
if (!ok) { if (!ok) {
LOGE("Could not create server mutex");
sc_server_params_destroy(&server->params); sc_server_params_destroy(&server->params);
return false; return false;
} }
ok = sc_cond_init(&server->cond_stopped); ok = sc_cond_init(&server->cond_stopped);
if (!ok) { if (!ok) {
LOGE("Could not create server cond_stopped");
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params); sc_server_params_destroy(&server->params);
return false; return false;
@ -423,13 +288,13 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
ok = sc_intr_init(&server->intr); ok = sc_intr_init(&server->intr);
if (!ok) { if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&server->cond_stopped); sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params); sc_server_params_destroy(&server->params);
return false; return false;
} }
server->serial = NULL;
server->stopped = false; server->stopped = false;
server->video_socket = SC_SOCKET_NONE; server->video_socket = SC_SOCKET_NONE;
@ -474,10 +339,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
assert(tunnel->enabled); assert(tunnel->enabled);
const char *serial = server->serial; const char *serial = server->params.serial;
assert(serial);
bool control = server->params.control;
sc_socket video_socket = SC_SOCKET_NONE; sc_socket video_socket = SC_SOCKET_NONE;
sc_socket control_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE;
@ -487,44 +349,27 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
goto fail; goto fail;
} }
if (control) { control_socket = net_accept_intr(&server->intr, tunnel->server_socket);
control_socket = if (control_socket == SC_SOCKET_NONE) {
net_accept_intr(&server->intr, tunnel->server_socket); goto fail;
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
} }
} else { } else {
uint32_t tunnel_host = server->params.tunnel_host; uint32_t attempts = 100;
if (!tunnel_host) {
tunnel_host = IPV4_LOCALHOST;
}
uint16_t tunnel_port = server->params.tunnel_port;
if (!tunnel_port) {
tunnel_port = tunnel->local_port;
}
unsigned attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100); sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay, tunnel_host, video_socket = connect_to_server(server, attempts, delay);
tunnel_port);
if (video_socket == SC_SOCKET_NONE) { if (video_socket == SC_SOCKET_NONE) {
goto fail; goto fail;
} }
if (control) { // we know that the device is listening, we don't need several attempts
// we know that the device is listening, we don't need several control_socket = net_socket();
// attempts if (control_socket == SC_SOCKET_NONE) {
control_socket = net_socket(); goto fail;
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, if (!ok) {
tunnel_host, tunnel_port); goto fail;
if (!ok) {
goto fail;
}
} }
} }
@ -538,7 +383,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
} }
assert(video_socket != SC_SOCKET_NONE); assert(video_socket != SC_SOCKET_NONE);
assert(!control || control_socket != SC_SOCKET_NONE); assert(control_socket != SC_SOCKET_NONE);
server->video_socket = video_socket; server->video_socket = video_socket;
server->control_socket = control_socket; server->control_socket = control_socket;
@ -558,10 +403,8 @@ fail:
} }
} }
if (tunnel->enabled) { // Always leave this function with tunnel disabled
// Always leave this function with tunnel disabled sc_adb_tunnel_close(tunnel, &server->intr, serial);
sc_adb_tunnel_close(tunnel, &server->intr, serial);
}
return false; return false;
} }
@ -582,254 +425,43 @@ sc_server_on_terminated(void *userdata) {
} }
static bool static bool
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) { sc_server_fill_serial(struct sc_server *server) {
struct sc_intr *intr = &server->intr; // Retrieve the actual device immediately if not provided, so that all
// future adb commands are executed for this specific device, even if other
char *current_port = // devices are connected afterwards (without "more than one
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT); // device/emulator" error)
if (!current_port) { if (!server->params.serial) {
return false; // The serial is owned by sc_server_params, and will be freed on destroy
} server->params.serial = adb_get_serialno(&server->intr, SC_STDERR);
if (!server->params.serial) {
// Is the device is listening on TCP on port 5555? LOGE("Could not get device serial");
bool enabled = !strcmp("5555", current_port);
free(current_port);
return enabled;
}
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");
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; return false;
} }
if (is_tcpip_mode_enabled(server, serial)) {
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 char *
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");
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);
if (!tcp_mode) {
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;
}
unsigned attempts = 40;
sc_tick delay = SC_TICK_FROM_MS(250);
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay);
if (!ok) {
goto error;
}
}
return ip_port;
error:
free(ip_port);
return NULL;
}
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
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;
}
LOGI("Connected to %s", ip_port);
return true; return true;
} }
static bool
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;
}
server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port);
}
static bool
sc_server_configure_tcpip_unknown_address(struct sc_server *server,
const char *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);
return true;
}
char *ip_port = sc_server_switch_to_tcpip(server, serial);
if (!ip_port) {
return false;
}
server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port);
}
static int static int
run_server(void *data) { run_server(void *data) {
struct sc_server *server = data; struct sc_server *server = data;
if (!sc_server_fill_serial(server)) {
goto error_connection_failed;
}
const struct sc_server_params *params = &server->params; const struct sc_server_params *params = &server->params;
// Execute "adb start-server" before "adb devices" so that daemon starting LOGD("Device serial: %s", params->serial);
// output/errors is correctly printed in the console ("adb devices" output
// is parsed, so it is not output) bool ok = push_server(&server->intr, params->serial);
bool ok = sc_adb_start_server(&server->intr, 0);
if (!ok) { if (!ok) {
LOGE("Could not start adb daemon");
goto error_connection_failed; goto error_connection_failed;
} }
// params->tcpip_dst implies params->tcpip LOGI("Server pushed");
assert(!params->tcpip_dst || params->tcpip);
// If tcpip_dst parameter is given, then it must connect to this address. ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
// 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;
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 {
// 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);
if (!ok) {
goto error_connection_failed;
}
if (params->tcpip) {
assert(!params->tcpip_dst);
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 {
// "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);
if (!ok) {
goto error_connection_failed;
}
}
const char *serial = server->serial;
assert(serial);
LOGD("Device serial: %s", serial);
ok = push_server(&server->intr, serial, params->install, params->reinstall);
if (!ok) {
LOGE("Failed to push server");
goto error_connection_failed;
}
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
params->port_range, params->force_adb_forward); params->port_range, params->force_adb_forward);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
@ -838,7 +470,7 @@ run_server(void *data) {
// server will connect to our server socket // server will connect to our server socket
sc_pid pid = execute_server(server, params); sc_pid pid = execute_server(server, params);
if (pid == SC_PROCESS_NONE) { if (pid == SC_PROCESS_NONE) {
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial); sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
goto error_connection_failed; goto error_connection_failed;
} }
@ -850,7 +482,7 @@ run_server(void *data) {
if (!ok) { if (!ok) {
sc_process_terminate(pid); sc_process_terminate(pid);
sc_process_wait(pid, true); // ignore exit code 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, params->serial);
goto error_connection_failed; goto error_connection_failed;
} }
@ -874,15 +506,6 @@ run_server(void *data) {
} }
sc_mutex_unlock(&server->mutex); 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->control_socket != SC_SOCKET_NONE) {
// There is no control_socket if --no-control is set
net_interrupt(server->control_socket);
}
// Give some delay for the server to terminate properly // Give some delay for the server to terminate properly
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
@ -913,8 +536,7 @@ error_connection_failed:
bool bool
sc_server_start(struct sc_server *server) { sc_server_start(struct sc_server *server) {
bool ok = bool ok = sc_thread_create(&server->thread, run_server, "server", server);
sc_thread_create(&server->thread, run_server, "scrcpy-server", server);
if (!ok) { if (!ok) {
LOGE("Could not create server thread"); LOGE("Could not create server thread");
return false; return false;
@ -936,14 +558,6 @@ sc_server_stop(struct sc_server *server) {
void void
sc_server_destroy(struct sc_server *server) { 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);
}
free(server->serial);
sc_server_params_destroy(&server->params); sc_server_params_destroy(&server->params);
sc_intr_destroy(&server->intr); sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped); sc_cond_destroy(&server->cond_stopped);

View File

@ -7,7 +7,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "adb/adb_tunnel.h" #include "adb.h"
#include "adb_tunnel.h"
#include "coords.h" #include "coords.h"
#include "options.h" #include "options.h"
#include "util/intr.h" #include "util/intr.h"
@ -22,14 +23,12 @@ struct sc_server_info {
}; };
struct sc_server_params { struct sc_server_params {
const char *req_serial; const char *serial;
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
const char *codec_options; const char *codec_options;
const char *encoder_name; const char *encoder_name;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
@ -40,22 +39,11 @@ struct sc_server_params {
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
bool cleanup;
bool power_on;
bool install;
bool reinstall;
}; };
struct sc_server { struct sc_server {
// The internal allocated strings are copies owned by the server // The internal allocated strings are copies owned by the server
struct sc_server_params params; struct sc_server_params params;
char *serial;
sc_thread thread; sc_thread thread;
struct sc_server_info info; // initialized once connected struct sc_server_info info; // initialized once connected

298
app/src/stream.c Normal file
View File

@ -0,0 +1,298 @@
#include "stream.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <unistd.h>
#include "decoder.h"
#include "events.h"
#include "recorder.h"
#include "util/buffer_util.h"
#include "util/log.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static bool
stream_recv_packet(struct stream *stream, 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 "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r < HEADER_SIZE) {
return false;
}
uint64_t pts = 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)) {
LOGE("Could not allocate packet");
return false;
}
r = net_recv_all(stream->socket, packet->data, len);
if (r < 0 || ((uint32_t) r) < len) {
av_packet_unref(packet);
return false;
}
packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE;
return true;
}
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];
if (!sink->ops->push(sink, packet)) {
LOGE("Could not send config packet to sink %d", i);
return false;
}
}
return true;
}
static bool
stream_parse(struct stream *stream, 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,
&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 (stream->parser->key_frame == 1) {
packet->flags |= AV_PKT_FLAG_KEY;
}
packet->dts = packet->pts;
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) {
LOGE("Could not process packet");
return false;
}
return true;
}
static bool
stream_push_packet(struct stream *stream, 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) {
size_t offset;
if (stream->pending) {
offset = stream->pending->size;
if (av_grow_packet(stream->pending, packet->size)) {
LOGE("Could not grow packet");
return false;
}
} else {
offset = 0;
stream->pending = av_packet_alloc();
if (!stream->pending) {
LOGE("Could not allocate packet");
return false;
}
if (av_new_packet(stream->pending, packet->size)) {
LOGE("Could not create packet");
av_packet_free(&stream->pending);
return false;
}
}
memcpy(stream->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;
}
}
if (is_config) {
// config packet
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) {
return false;
}
} else {
// data packet
bool ok = stream_parse(stream, packet);
if (stream->pending) {
// the pending packet must be discarded (consumed or error)
av_packet_free(&stream->pending);
}
if (!ok) {
return false;
}
}
return true;
}
static void
stream_close_first_sinks(struct stream *stream, unsigned count) {
while (count) {
struct sc_packet_sink *sink = stream->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
stream_close_sinks(struct stream *stream) {
stream_close_first_sinks(stream, stream->sink_count);
}
static bool
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
stream_close_first_sinks(stream, i);
return false;
}
}
return true;
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
stream->codec_ctx = avcodec_alloc_context3(codec);
if (!stream->codec_ctx) {
LOGC("Could not allocate codec context");
goto end;
}
if (!stream_open_sinks(stream, codec)) {
LOGE("Could not open stream sinks");
goto finally_free_codec_ctx;
}
stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->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;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
goto finally_close_parser;
}
for (;;) {
bool ok = stream_recv_packet(stream, packet);
if (!ok) {
// end of stream
break;
}
ok = stream_push_packet(stream, packet);
av_packet_unref(packet);
if (!ok) {
// cannot process packet (error already logged)
break;
}
}
LOGD("End of frames");
if (stream->pending) {
av_packet_free(&stream->pending);
}
av_packet_free(&packet);
finally_close_parser:
av_parser_close(stream->parser);
finally_close_sinks:
stream_close_sinks(stream);
finally_free_codec_ctx:
avcodec_free_context(&stream->codec_ctx);
end:
stream->cbs->on_eos(stream, stream->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;
assert(cbs && cbs->on_eos);
stream->cbs = cbs;
stream->cbs_userdata = cbs_userdata;
}
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
assert(stream->sink_count < STREAM_MAX_SINKS);
assert(sink);
assert(sink->ops);
stream->sinks[stream->sink_count++] = sink;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
if (!ok) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_join(struct stream *stream) {
sc_thread_join(&stream->thread, NULL);
}

50
app/src/stream.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef STREAM_H
#define STREAM_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavformat/avformat.h>
#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

View File

@ -2,13 +2,10 @@
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h"
bool bool
sc_file_executable_exists(const char *file) { sc_file_executable_exists(const char *file) {
char *path = getenv("PATH"); char *path = getenv("PATH");
@ -26,10 +23,7 @@ sc_file_executable_exists(const char *file) {
size_t dir_len = strlen(dir); size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2); char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath) if (!fullpath)
{
LOG_OOM();
continue; continue;
}
memcpy(fullpath, dir, dir_len); memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/'; fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1); memcpy(fullpath + dir_len + 1, file, file_len + 1);

View File

@ -11,10 +11,15 @@
#include "util/log.h" #include "util/log.h"
enum sc_process_result enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags, sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
int *pin, int *pout, int *perr) { int *pin, int *pout, int *perr) {
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT); bool inherit_stdout = inherit & SC_STDOUT;
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); bool inherit_stderr = inherit & SC_STDERR;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT.
assert(!pout || !inherit_stdout);
// If perr is defined, then inherit MUST NOT contain SC_STDERR.
assert(!perr || !inherit_stderr);
int in[2]; int in[2];
int out[2]; int out[2];
@ -93,7 +98,8 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
} }
close(in[1]); close(in[1]);
} }
// Do not close stdin in the child process, this makes adb fail on Linux // Do not close stdin in the child process, this makes adb fail on
// Linux
if (pout) { if (pout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
@ -119,13 +125,6 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
close(internal[0]); close(internal[0]);
enum sc_process_result err; 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) { if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv); execvp(argv[0], (char *const *) argv);
perror("exec"); perror("exec");
@ -176,7 +175,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
bool bool
sc_process_terminate(pid_t pid) { sc_process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGE("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
abort(); abort();
} }

View File

@ -26,7 +26,7 @@ bool
sc_file_is_regular(const char *path) { sc_file_is_regular(const char *path) {
wchar_t *wide_path = sc_str_to_wchars(path); wchar_t *wide_path = sc_str_to_wchars(path);
if (!wide_path) { if (!wide_path) {
LOG_OOM(); LOGC("Could not allocate wide char string");
return false; return false;
} }

View File

@ -1,3 +1,6 @@
// <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>
#define _WIN32_WINNT 0x0600 // For extended process API
#include "util/process.h" #include "util/process.h"
#include <processthreadsapi.h> #include <processthreadsapi.h>
@ -24,13 +27,20 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
} }
enum sc_process_result enum sc_process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit,
HANDLE *pin, HANDLE *pout, HANDLE *perr) { HANDLE *pin, HANDLE *pout, HANDLE *perr) {
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT); bool inherit_stdout = inherit & SC_STDOUT;
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); bool inherit_stderr = inherit & SC_STDERR;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT.
assert(!pout || !inherit_stdout);
// If perr is defined, then inherit MUST NOT contain SC_STDERR.
assert(!perr || !inherit_stderr);
// Add 1 per non-NULL pointer // 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; enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
@ -79,29 +89,25 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
si.StartupInfo.cb = sizeof(si); si.StartupInfo.cb = sizeof(si);
HANDLE handles[3]; 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; LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
// Must be set even if handle_count == 0, so that stdin, stdout and stderr
// are NOT inherited in that case
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (handle_count) { if (handle_count) {
unsigned i = 0; unsigned i = 0;
if (pin) { if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle; si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput; handles[i++] = si.StartupInfo.hStdInput;
} }
if (pout) { if (pout || inherit_stdout) {
assert(!inherit_stdout); si.StartupInfo.hStdOutput = pout ? stdout_write_handle
si.StartupInfo.hStdOutput = stdout_write_handle; : GetStdHandle(STD_OUTPUT_HANDLE);
handles[i++] = si.StartupInfo.hStdOutput; handles[i++] = si.StartupInfo.hStdOutput;
} }
if (perr) { if (perr || inherit_stderr) {
assert(!inherit_stderr); si.StartupInfo.hStdError = perr ? stderr_write_handle
si.StartupInfo.hStdError = stderr_write_handle; : GetStdHandle(STD_ERROR_HANDLE);
handles[i++] = si.StartupInfo.hStdError; handles[i++] = si.StartupInfo.hStdError;
} }
@ -116,7 +122,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
lpAttributeList = malloc(size); lpAttributeList = malloc(size);
if (!lpAttributeList) { if (!lpAttributeList) {
LOG_OOM();
goto error_close_stderr; goto error_close_stderr;
} }
@ -139,33 +144,23 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
char *cmd = malloc(CMD_MAX_LEN); char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
LOG_OOM();
goto error_free_attribute_list; goto error_free_attribute_list;
} }
wchar_t *wide = sc_str_to_wchars(cmd); wchar_t *wide = sc_str_to_wchars(cmd);
free(cmd); free(cmd);
if (!wide) { if (!wide) {
LOG_OOM(); LOGC("Could not allocate wide char string");
goto error_free_attribute_list; goto error_free_attribute_list;
} }
BOOL bInheritHandles = handle_count > 0 || inherit_stdout || inherit_stderr; BOOL bInheritHandles = handle_count > 0;
DWORD dwCreationFlags = 0; DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 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, BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide); free(wide);
if (!ok) { if (!ok) {
int err = GetLastError(); if (GetLastError() == ERROR_FILE_NOT_FOUND) {
LOGE("CreateProcessW() error %d", err);
if (err == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY; ret = SC_PROCESS_ERROR_MISSING_BINARY;
} }
goto error_free_attribute_list; goto error_free_attribute_list;

View File

@ -6,7 +6,7 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include "input_events.h" #include <SDL2/SDL_events.h>
/** /**
* Key processor trait. * Key processor trait.
@ -14,42 +14,16 @@
* Component able to process and inject keys should implement this trait. * Component able to process and inject keys should implement this trait.
*/ */
struct sc_key_processor { 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; const struct sc_key_processor_ops *ops;
}; };
struct sc_key_processor_ops { struct sc_key_processor_ops {
/**
* 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 void
(*process_key)(struct sc_key_processor *kp, (*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event);
const struct sc_key_event *event, uint64_t ack_to_wait);
/**
* Process an input text
*
* This function is optional.
*/
void void
(*process_text)(struct sc_key_processor *kp, (*process_text)(struct sc_key_processor *kp,
const struct sc_text_event *event); const SDL_TextInputEvent *event);
}; };
#endif #endif

View File

@ -6,7 +6,7 @@
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include "input_events.h" #include <SDL2/SDL_events.h>
/** /**
* Mouse processor trait. * Mouse processor trait.
@ -16,51 +16,24 @@
*/ */
struct sc_mouse_processor { struct sc_mouse_processor {
const struct sc_mouse_processor_ops *ops; 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 { struct sc_mouse_processor_ops {
/**
* Process a mouse motion event
*
* This function is mandatory.
*/
void void
(*process_mouse_motion)(struct sc_mouse_processor *mp, (*process_mouse_motion)(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event); const SDL_MouseMotionEvent *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 void
(*process_touch)(struct sc_mouse_processor *mp, (*process_touch)(struct sc_mouse_processor *mp,
const struct sc_touch_event *event); 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 #endif

View File

@ -1,267 +0,0 @@
#include "hid_mouse.h"
#include <assert.h>
#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:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §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 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;
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 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;
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 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");
}
}

View File

@ -1,23 +0,0 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#include "common.h"
#include <stdbool.h>
#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

View File

@ -1,217 +0,0 @@
#include "scrcpy_otg.h"
#include <SDL2/SDL.h>
#include "adb/adb.h"
#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 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 SCRCPY_EXIT_DISCONNECTED;
case SDL_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
default:
sc_screen_otg_handle_event(&s->screen_otg, &event);
break;
}
}
return SCRCPY_EXIT_FAILURE;
}
enum scrcpy_exit_code
scrcpy_otg(struct scrcpy_options *options) {
static struct scrcpy_otg scrcpy_otg;
struct scrcpy_otg *s = &scrcpy_otg;
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());
return false;
}
atexit(SDL_Quit);
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
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;
#ifdef _WIN32
// On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773>
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,
};
bool ok = sc_usb_init(&s->usb);
if (!ok) {
return SCRCPY_EXIT_FAILURE;
}
struct sc_usb_device usb_device;
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
if (!ok) {
goto end;
}
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;
}
usb_connected = true;
ok = sc_aoa_init(&s->aoa, &s->usb, NULL);
if (!ok) {
goto end;
}
aoa_initialized = true;
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;
}
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;
}
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_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
};
ok = sc_screen_otg_init(&s->screen_otg, &params);
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;
}

Some files were not shown because too many files have changed in this diff Show More