Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13eb9b7a6a |
33
BUILD.md
33
BUILD.md
@@ -46,7 +46,7 @@ sudo ninja -Cbuild-auto uninstall
|
||||
### `master`
|
||||
|
||||
The `master` branch concerns the latest release, and is the home page of the
|
||||
project on GitHub.
|
||||
project on Github.
|
||||
|
||||
|
||||
### `dev`
|
||||
@@ -161,8 +161,7 @@ install the required packages:
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-x86_64-SDL2 \
|
||||
mingw-w64-x86_64-ffmpeg \
|
||||
mingw-w64-x86_64-libusb
|
||||
mingw-w64-x86_64-ffmpeg
|
||||
|
||||
# client build dependencies
|
||||
pacman -S mingw-w64-x86_64-make \
|
||||
@@ -176,8 +175,7 @@ For a 32 bits version, replace `x86_64` by `i686`:
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-i686-SDL2 \
|
||||
mingw-w64-i686-ffmpeg \
|
||||
mingw-w64-i686-libusb
|
||||
mingw-w64-i686-ffmpeg
|
||||
|
||||
# client build dependencies
|
||||
pacman -S mingw-w64-i686-make \
|
||||
@@ -201,7 +199,7 @@ Install the packages with [Homebrew]:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
brew install sdl2 ffmpeg libusb
|
||||
brew install sdl2 ffmpeg
|
||||
|
||||
# client build dependencies
|
||||
brew install pkg-config meson
|
||||
@@ -260,7 +258,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
|
||||
Then, build:
|
||||
|
||||
```bash
|
||||
meson x --buildtype=release --strip -Db_lto=true
|
||||
meson x --buildtype release --strip -Db_lto=true
|
||||
ninja -Cx # DO NOT RUN AS ROOT
|
||||
```
|
||||
|
||||
@@ -272,16 +270,16 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.24`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub>
|
||||
- [`scrcpy-server-v1.20`][direct-scrcpy-server]
|
||||
_(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
|
||||
configuration:
|
||||
|
||||
```bash
|
||||
meson x --buildtype=release --strip -Db_lto=true \
|
||||
meson x --buildtype release --strip -Db_lto=true \
|
||||
-Dprebuilt_server=/path/to/scrcpy-server
|
||||
ninja -Cx # DO NOT RUN AS ROOT
|
||||
```
|
||||
@@ -305,16 +303,13 @@ After a successful build, you can install _scrcpy_ on the system:
|
||||
sudo ninja -Cx install # without sudo on Windows
|
||||
```
|
||||
|
||||
This installs several files:
|
||||
This installs three files:
|
||||
|
||||
- `/usr/local/bin/scrcpy` (main app)
|
||||
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
|
||||
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
|
||||
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
|
||||
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
|
||||
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)
|
||||
- `/usr/local/bin/scrcpy`
|
||||
- `/usr/local/share/scrcpy/scrcpy-server`
|
||||
- `/usr/local/share/man/man1/scrcpy.1`
|
||||
|
||||
You can then [run](README.md#run) `scrcpy`.
|
||||
You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
### Uninstall
|
||||
|
||||
|
||||
98
FAQ.md
98
FAQ.md
@@ -4,16 +4,23 @@
|
||||
|
||||
Here are the common reported problems and their status.
|
||||
|
||||
If you encounter any error, the first step is to upgrade to the latest version.
|
||||
|
||||
|
||||
## `adb` issues
|
||||
|
||||
`scrcpy` execute `adb` commands to initialize the connection with the device. If
|
||||
`adb` fails, then scrcpy will not work.
|
||||
|
||||
In that case, it will print this error:
|
||||
|
||||
> ERROR: "adb push" returned with value 1
|
||||
|
||||
This is typically not a bug in _scrcpy_, but a problem in your environment.
|
||||
|
||||
To find out the cause, execute:
|
||||
|
||||
```bash
|
||||
adb devices
|
||||
```
|
||||
|
||||
### `adb` not found
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
### Device unauthorized
|
||||
|
||||
Check [stackoverflow][device-unauthorized].
|
||||
|
||||
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
||||
|
||||
|
||||
### Device not detected
|
||||
|
||||
> ERROR: Could not find any ADB device
|
||||
> adb: error: failed to get feature set: no devices/emulators found
|
||||
|
||||
Check that you correctly enabled [adb debugging][enable-adb].
|
||||
|
||||
Your device must be detected by `adb`:
|
||||
|
||||
```
|
||||
adb devices
|
||||
```
|
||||
|
||||
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
|
||||
If your device is not detected, you may need some [drivers] (on Windows).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
|
||||
|
||||
|
||||
### Device unauthorized
|
||||
|
||||
> ERROR: Device is unauthorized:
|
||||
> ERROR: --> (usb) 0123456789abcdef unauthorized
|
||||
> ERROR: A popup should open on the device to request authorization.
|
||||
|
||||
When connecting, a popup should open on the device. You must authorize USB
|
||||
debugging.
|
||||
|
||||
If it does not open, check [stackoverflow][device-unauthorized].
|
||||
|
||||
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
||||
|
||||
|
||||
### Several devices connected
|
||||
|
||||
If several devices are connected, you will encounter this error:
|
||||
|
||||
ERROR: Multiple (2) ADB devices:
|
||||
ERROR: --> (usb) 0123456789abcdef device Nexus_5
|
||||
ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
|
||||
ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
|
||||
> adb: error: failed to get feature set: more than one device/emulator
|
||||
|
||||
In that case, you can either provide the identifier of the device you want to
|
||||
mirror:
|
||||
the identifier of the device you want to mirror must be provided:
|
||||
|
||||
```bash
|
||||
scrcpy -s 0123456789abcdef
|
||||
scrcpy -s 01234567890abcdef
|
||||
```
|
||||
|
||||
Or request the single USB (or TCP/IP) device:
|
||||
|
||||
```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:
|
||||
Note that if your device is connected over TCP/IP, you'll get this message:
|
||||
|
||||
> adb: error: more than one device/emulator
|
||||
> 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
|
||||
|
||||
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 >
|
||||
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
||||
|
||||
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||
|
||||
Also, to improve downscaling quality, trilinear filtering is enabled
|
||||
automatically if the renderer is OpenGL and if it supports mipmapping.
|
||||
|
||||
On Windows, you might want to force OpenGL to enable mipmapping:
|
||||
|
||||
```
|
||||
scrcpy --render-driver=opengl
|
||||
```
|
||||
|
||||
|
||||
### Issue with Wayland
|
||||
|
||||
@@ -239,9 +219,6 @@ scrcpy -m 1024
|
||||
scrcpy -m 800
|
||||
```
|
||||
|
||||
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
|
||||
before failing. This behavior can be disabled with `--no-downsize-on-error`.
|
||||
|
||||
You could also try another [encoder](README.md#encoder).
|
||||
|
||||
|
||||
@@ -268,15 +245,8 @@ Caused by: java.lang.IllegalArgumentException: displayToken must not be null
|
||||
|
||||
## Command line on Windows
|
||||
|
||||
Since v1.22, a "shortcut" has been added to directly open a terminal in the
|
||||
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
|
||||
command. For example:
|
||||
|
||||
```
|
||||
scrcpy --record file.mkv
|
||||
```
|
||||
|
||||
You could also open a terminal and go to the scrcpy folder manually:
|
||||
Some Windows users are not familiar with the command line. Here is how to open a
|
||||
terminal and run `scrcpy` with arguments:
|
||||
|
||||
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.
|
||||
@@ -315,4 +285,4 @@ This FAQ is available in other languages:
|
||||
|
||||
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
|
||||
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md)
|
||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
|
||||
|
||||
@@ -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)是保证最新的。_
|
||||
|
||||
Current version is based on [28054cd]
|
||||
|
||||
本文根据[28054cd]进行翻译。
|
||||
|
||||
[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md
|
||||
[d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md
|
||||
|
||||
# 常见问题
|
||||
|
||||
@@ -14,11 +9,11 @@ Current version is based on [28054cd]
|
||||
|
||||
## `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,而是你的环境的问题。
|
||||
|
||||
@@ -38,37 +33,28 @@ adb devices
|
||||
|
||||
### 设备未授权
|
||||
|
||||
|
||||
> 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].
|
||||
参见这里 [stackoverflow][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].
|
||||
|
||||
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver].
|
||||
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
|
||||
|
||||
|
||||
### 已连接多个设备
|
||||
|
||||
如果连接了多个设备,您将遇到以下错误:
|
||||
|
||||
> error: more than one device/emulator
|
||||
> adb: error: failed to get feature set: more than one device/emulator
|
||||
|
||||
必须提供要镜像的设备的标识符:
|
||||
|
||||
@@ -104,19 +90,19 @@ scrcpy
|
||||
### 设备断开连接
|
||||
|
||||
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
|
||||
|
||||
请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。
|
||||
请尝试使用另一条USB线或者电脑上的另一个USB接口。
|
||||
请参看 [#281] 和 [#283]。
|
||||
|
||||
[#281]: https://github.com/Genymobile/scrcpy/issues/281
|
||||
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
||||
|
||||
|
||||
## 控制相关问题
|
||||
|
||||
### 鼠标和键盘不起作用
|
||||
|
||||
|
||||
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
|
||||
|
||||
在开发者选项中,打开:
|
||||
|
||||
> **USB调试 (安全设置)**
|
||||
@@ -129,12 +115,10 @@ scrcpy
|
||||
|
||||
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。
|
||||
|
||||
自 Linux 上的 scrcpy v1.20 之后,可以模拟[物理键盘][hid] (HID)。
|
||||
|
||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||
[hid]: README.md#physical-keyboard-simulation-hid
|
||||
|
||||
|
||||
## 客户端相关问题
|
||||
@@ -145,6 +129,7 @@ scrcpy
|
||||
|
||||
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
||||
|
||||
|
||||
为了提升降尺度的质量,如果渲染器是OpenGL并且支持mip映射,就会自动开启三线性过滤。
|
||||
|
||||
在Windows上,你可能希望强制使用OpenGL:
|
||||
@@ -192,7 +177,6 @@ scrcpy
|
||||
## 崩溃
|
||||
|
||||
### 异常
|
||||
|
||||
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
|
||||
|
||||
> ```
|
||||
@@ -220,40 +204,12 @@ scrcpy -m 1024
|
||||
scrcpy -m 800
|
||||
```
|
||||
|
||||
自 scrcpy v1.22以来,scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。
|
||||
|
||||
你也可以尝试另一种 [编码器](README.md#encoder)。
|
||||
|
||||
|
||||
如果您在 Android 12 上遇到此异常,则只需升级到 scrcpy >= 1.18 (见 [#2129]):
|
||||
|
||||
```
|
||||
> ERROR: Exception on thread Thread[main,5,main]
|
||||
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
|
||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
|
||||
...
|
||||
Caused by: java.lang.reflect.InvocationTargetException
|
||||
at java.lang.reflect.Method.invoke(Native Method)
|
||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
|
||||
... 7 more
|
||||
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
|
||||
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
|
||||
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
|
||||
... 9 more
|
||||
```
|
||||
|
||||
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
|
||||
|
||||
|
||||
## Windows命令行
|
||||
|
||||
从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如:
|
||||
|
||||
```
|
||||
scrcpy --record file.mkv
|
||||
```
|
||||
|
||||
您也可以打开终端并手动转到 scrcpy 文件夹:
|
||||
一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`:
|
||||
|
||||
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
|
||||
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
|
||||
@@ -277,7 +233,7 @@ scrcpy --record file.mkv
|
||||
scrcpy --prefer-text --turn-screen-off --stay-awake
|
||||
```
|
||||
|
||||
然后只需双击刚刚创建的文件。
|
||||
然后双击刚刚创建的文件。
|
||||
|
||||
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
1016
README.de.md
1016
README.de.md
File diff suppressed because it is too large
Load Diff
@@ -218,7 +218,7 @@ variation] does not impact the recorded file.
|
||||
|
||||
#### Wireless
|
||||
|
||||
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan `adb` dapat [terhubung] ke perangkat melalui TCP / IP:
|
||||
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP:
|
||||
|
||||
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
|
||||
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
|
||||
@@ -281,7 +281,7 @@ Dari terminal lain:
|
||||
scrcpy
|
||||
```
|
||||
|
||||
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan `-R`):
|
||||
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`):
|
||||
|
||||
```bash
|
||||
adb kill-server # matikan server adb lokal di 5037
|
||||
@@ -579,7 +579,7 @@ Lihat juga [Masalah #14].
|
||||
|
||||
Dalam daftar berikut, <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
|
||||
# gunakan RCtrl untuk jalan pintas
|
||||
@@ -672,7 +672,7 @@ Baca [halaman pengembang].
|
||||
## Lisensi
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
344
README.it.md
344
README.it.md
@@ -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)
|
||||
|
||||
_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_.
|
||||
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
|
||||
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
||||
|
||||

|
||||
|
||||
Si concentra su:
|
||||
|
||||
- **leggerezza**: nativo, mostra solo lo schermo del dispositivo
|
||||
- **prestazioni**: 30~120fps, in funzione del dispositivo
|
||||
- **qualità**: 1920×1080 o superiore
|
||||
- **bassa latenza**: [35~70ms][lowlatency]
|
||||
- **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine
|
||||
- **non invadenza**: nulla rimane installato sul dispositivo
|
||||
- **vantaggi per l'utente**: nessun account, nessuna pubblicità, non è richiesta alcuna connessione a internet
|
||||
- **libertà**: software libero e a codice aperto (_free and open source_)
|
||||
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
|
||||
- **prestazioni** (30~60fps)
|
||||
- **qualità** (1920×1080 o superiore)
|
||||
- **bassa latenza** ([35~70ms][lowlatency])
|
||||
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
|
||||
- **non invadenza** (nulla viene lasciato installato sul dispositivo)
|
||||
|
||||
[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
|
||||
|
||||
@@ -68,18 +49,12 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si
|
||||
|
||||
### Linux
|
||||
|
||||
Su Debian e Ubuntu:
|
||||
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
|
||||
|
||||
```
|
||||
apt install scrcpy
|
||||
```
|
||||
|
||||
Su Arch Linux:
|
||||
|
||||
```
|
||||
pacman -S scrcpy
|
||||
```
|
||||
|
||||
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
|
||||
|
||||
[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-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].
|
||||
|
||||
@@ -163,7 +142,7 @@ Collega un dispositivo Android ed esegui:
|
||||
scrcpy
|
||||
```
|
||||
|
||||
Scrcpy accetta argomenti da riga di comando, elencati con:
|
||||
Scrcpy accetta argomenti da riga di comando, essi sono listati con:
|
||||
|
||||
```bash
|
||||
scrcpy --help
|
||||
@@ -207,14 +186,6 @@ scrcpy --max-fps 15
|
||||
|
||||
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
|
||||
|
||||
L'attuale frame rate di acquisizione può essere stampato sulla console:
|
||||
|
||||
```
|
||||
scrcpy --print-fps
|
||||
```
|
||||
|
||||
Può anche essere abilitato o disabilitato in qualsiasi momento con <kbd>MOD</kbd>+<kbd>i</kbd>.
|
||||
|
||||
#### Ritaglio
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
@@ -350,72 +321,42 @@ scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
|
||||
e per il V4L2 sink:
|
||||
|
||||
```bash
|
||||
scrcpy --v4l2-buffer=500 # aggiungi 500 ms di buffer per il v4l2 sink
|
||||
scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink
|
||||
```
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```bash
|
||||
adb shell ip route | awk '{print $9}'
|
||||
```
|
||||
|
||||
4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
|
||||
5. Scollega il tuo dispositivo.
|
||||
6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP`
|
||||
con l'indirizzo IP del dispositivo che hai trovato)_.
|
||||
7. Esegui `scrcpy` come al solito.
|
||||
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
|
||||
4. Scollega il tuo dispositivo.
|
||||
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
|
||||
6. Esegui `scrcpy` come al solito.
|
||||
|
||||
Da Android 11, una [opzione di debug wireless][adb-wireless] permette di evitare di dover collegare fisicamente il dispositivo direttamente al computer.
|
||||
|
||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
|
||||
|
||||
Se la connessione cade casualmente, esegui il comando `scrcpy` per riconnetterti. Se il comando dice che non ci sono dispositivi/emulatori trovati, prova ad eseguire `adb connect DEVICE_IP:5555` di nuovo, e poi `scrcpy` come al solito. Se dice ancora che non ne ha trovato nessuno, prova ad eseguire `adb disconnect` e poi esegui di nuovo questi due comandi.
|
||||
|
||||
Potrebbe essere utile diminuire il bit-rate e la definizione:
|
||||
Potrebbe essere utile diminuire il bit-rate e la definizione
|
||||
|
||||
```bash
|
||||
scrcpy --bit-rate 2M --max-size 800
|
||||
scrcpy -b2M -m800 # versione breve
|
||||
scrcpy -b2M -m800 # versione breve
|
||||
```
|
||||
|
||||
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||
|
||||
|
||||
#### Multi dispositivo
|
||||
|
||||
Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_:
|
||||
Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
|
||||
|
||||
```bash
|
||||
scrcpy --serial 0123456789abcdef
|
||||
@@ -429,18 +370,6 @@ scrcpy --serial 192.168.0.1:5555
|
||||
scrcpy -s 192.168.0.1:5555 # versione breve
|
||||
```
|
||||
|
||||
Se solo un dispositivo è collegato via USB o TCP/IP, è possibile selezionarlo automaticamente:
|
||||
|
||||
```bash
|
||||
# Select the only device connected via USB
|
||||
scrcpy -d # like adb -d
|
||||
scrcpy --select-usb # long version
|
||||
|
||||
# Select the only device connected via TCP/IP
|
||||
scrcpy -e # like adb -e
|
||||
scrcpy --select-tcpip # long version
|
||||
```
|
||||
|
||||
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
|
||||
|
||||
|
||||
@@ -454,77 +383,37 @@ autoadb scrcpy -s '{}'
|
||||
|
||||
[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_). ).
|
||||
|
||||
##### Server ADB remoto
|
||||
|
||||
Per connettersi a un server ADB remoto, fate ascoltare il server su tutte le interfacce:
|
||||
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_):
|
||||
|
||||
```bash
|
||||
adb kill-server
|
||||
adb -a nodaemon server start
|
||||
# tienilo aperto
|
||||
adb kill-server # termina il server adb locale su 5037
|
||||
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# tieni questo aperto
|
||||
```
|
||||
|
||||
**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.**
|
||||
|
||||
Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy:
|
||||
Da un altro terminale:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
||||
scrcpy --tunnel-host=192.168.1.2
|
||||
```
|
||||
|
||||
Per impostazione predefinita, scrcpy utilizza la porta locale utilizzata per il tunnel `adb forward` (tipicamente `27183`, vedi `--port`). È anche possibile forzare una diversa porta del tunnel (può essere utile in situazioni più complesse, quando sono coinvolti più reindirizzamenti):
|
||||
|
||||
```
|
||||
scrcpy --tunnel-port=1234
|
||||
```
|
||||
|
||||
##### SSH tunnel
|
||||
|
||||
Per comunicare con un server ADB remoto in modo sicuro, è preferibile utilizzare un tunnel SSH.
|
||||
|
||||
Per prima cosa, assicurati che il server ADB sia in esecuzione sul computer remoto:
|
||||
|
||||
```bash
|
||||
adb start-server
|
||||
```
|
||||
|
||||
Poi, crea un tunnel SSH:
|
||||
|
||||
```bash
|
||||
# local 5038 --> remote 5037
|
||||
# local 27183 <-- remote 27183
|
||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# keep this open
|
||||
```
|
||||
|
||||
Da un altro terminale, esegui scrcpy:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy
|
||||
```
|
||||
|
||||
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
|
||||
|
||||
```bash
|
||||
# local 5038 --> remote 5037
|
||||
# local 27183 --> remote 27183
|
||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
adb kill-server # termina il server adb locale su 5037
|
||||
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
# tieni questo aperto
|
||||
```
|
||||
|
||||
Da un altro terminale, esegui scrcpy:
|
||||
Da un altro terminale:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy --force-adb-forward
|
||||
```
|
||||
|
||||
|
||||
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
|
||||
|
||||
```
|
||||
@@ -662,14 +551,6 @@ scrcpy --turn-screen-off --stay-awake
|
||||
scrcpy -Sw
|
||||
```
|
||||
|
||||
#### Spegnimento alla chiusura
|
||||
|
||||
Per spegnere lo schermo del dispositivo quando si chiude scrcpy:
|
||||
|
||||
```bash
|
||||
scrcpy --power-off-on-close
|
||||
```
|
||||
|
||||
|
||||
#### Mostrare i tocchi
|
||||
|
||||
@@ -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>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.
|
||||
|
||||
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>x</kbd> invia `CUT`
|
||||
- <kbd>MOD</kbd>+<kbd>v</kbd> invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
||||
- <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
|
||||
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
|
||||
- <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.
|
||||
|
||||
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>).
|
||||
|
||||
Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`.
|
||||
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>).
|
||||
|
||||
#### 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.
|
||||
|
||||
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
|
||||
Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
|
||||
|
||||
|
||||
Quando questa modalità è attivata, il mouse del computer viene "catturato" (il puntatore del mouse scompare dal computer e appare invece sul dispositivo Android).
|
||||
|
||||
I tasti speciali di cattura, <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
|
||||
#### Preferenze di iniezione del testo
|
||||
|
||||
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
|
||||
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
|
||||
- _eventi di testo_, segnalano che del testo è stato inserito.
|
||||
|
||||
In maniera predefinita le lettere sono inviate usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
|
||||
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:
|
||||
|
||||
@@ -839,21 +636,13 @@ scrcpy --prefer-text
|
||||
|
||||
(ma questo romperà il normale funzionamento della tastiera nei giochi)
|
||||
|
||||
Al contrario, si potrebbe forzare per inviare sempre eventi di pressione grezzi:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
Queste opzioni non hanno effetto sulla tastiera HID (tutti gli eventi di pressione sono inviati come scancodes in questa modalità).
|
||||
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||
|
||||
|
||||
#### Ripetizione di tasti
|
||||
|
||||
In maniera predefinita, tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
|
||||
In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
|
||||
|
||||
Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
||||
|
||||
@@ -861,12 +650,9 @@ Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
||||
scrcpy --no-key-repeat
|
||||
```
|
||||
|
||||
Questa opzione non ha effetto sulla tastiera HID (la ripetizione dei tasti è gestita da Android direttamente in questa modalità).
|
||||
|
||||
|
||||
#### Click destro e click centrale
|
||||
|
||||
In maniera predefinita, click destro aziona BACK (indietro) 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
|
||||
scrcpy --forward-all-clicks
|
||||
@@ -919,7 +705,7 @@ scrcpy --shortcut-mod=rctrl
|
||||
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
|
||||
<!-- 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 `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 `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_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
||||
| 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 impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
|
||||
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copia negli appunti⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Taglia negli appunti⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Sincronizza gli appunti e incolla⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Invia il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| Copia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Taglia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Sincronizza gli appunti e incolla⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
||||
| Trascina file APK | Installa APK dal computer
|
||||
| Trascina file non-APK | [Trasferisci file verso il dispositivo](#push-file-to-device)
|
||||
|
||||
_¹Doppio click sui bordi neri per rimuoverli._
|
||||
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
||||
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._
|
||||
_⁴Per le app native react in sviluppo, `MENU` attiva il menu di sviluppo._
|
||||
_⁵Solo in Android >= 7._
|
||||
_⁴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":
|
||||
|
||||
@@ -1007,7 +790,7 @@ Leggi la [pagina per sviluppatori].
|
||||
## Licenza (in inglese)
|
||||
|
||||
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");
|
||||
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-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)
|
||||
|
||||
@@ -776,7 +776,7 @@ _⁴Android 7以上のみ._
|
||||
## ライセンス
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
|
||||
## 라이선스
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
414
README.md
414
README.md
@@ -1,13 +1,11 @@
|
||||
# scrcpy (v1.24)
|
||||
# scrcpy (v1.20)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
_pronounced "**scr**een **c**o**py**"_
|
||||
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
[Read in another language](#translations)
|
||||
|
||||
This application provides display and control of Android devices connected via
|
||||
USB or [over TCP/IP](#tcpip-wireless). It does not require any _root_ access.
|
||||
USB (or [over TCP/IP](#wireless)). It does not require any _root_ access.
|
||||
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
||||
|
||||

|
||||
@@ -19,7 +17,7 @@ It focuses on:
|
||||
- **quality**: 1920×1080 or above
|
||||
- **low latency**: [35~70ms][lowlatency]
|
||||
- **low startup time**: ~1 second to display the first image
|
||||
- **non-intrusiveness**: nothing is left installed on the Android device
|
||||
- **non-intrusiveness**: nothing is left installed on the device
|
||||
- **user benefits**: no account, no ads, no internet required
|
||||
- **freedom**: free and open source software
|
||||
|
||||
@@ -27,25 +25,24 @@ It focuses on:
|
||||
|
||||
Its features include:
|
||||
- [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
|
||||
- [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 mouse simulation (HID)](#physical-mouse-simulation-hid)
|
||||
- [OTG mode](#otg)
|
||||
(Linux-only)
|
||||
- and more…
|
||||
|
||||
## Requirements
|
||||
|
||||
The Android device requires at least API 21 (Android 5.0).
|
||||
|
||||
Make sure you [enable adb debugging][enable-adb] on your device(s).
|
||||
Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||
|
||||
On some devices, you also need to enable [an additional option][control] to
|
||||
control it using a keyboard and mouse.
|
||||
control it using keyboard and mouse.
|
||||
|
||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
@@ -68,18 +65,12 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple])
|
||||
|
||||
### Linux
|
||||
|
||||
On Debian and Ubuntu:
|
||||
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
|
||||
|
||||
```
|
||||
apt install scrcpy
|
||||
```
|
||||
|
||||
On Arch Linux:
|
||||
|
||||
```
|
||||
pacman -S scrcpy
|
||||
```
|
||||
|
||||
A [Snap] package is available: [`scrcpy`][snap-link].
|
||||
|
||||
[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-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].
|
||||
|
||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||
|
||||
You can also [build the app manually][BUILD] ([simplified
|
||||
You could also [build the app manually][BUILD] ([simplified
|
||||
process][BUILD_simple]).
|
||||
|
||||
|
||||
### Windows
|
||||
|
||||
For Windows, a prebuilt archive with all the dependencies (including `adb`) is
|
||||
available:
|
||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||
(including `adb`) is available:
|
||||
|
||||
- [`scrcpy-win64-v1.24.zip`][direct-win64]
|
||||
<sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub>
|
||||
- [`scrcpy-win64-v1.20.zip`][direct-win64]
|
||||
_(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]:
|
||||
|
||||
@@ -148,7 +143,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
||||
brew install android-platform-tools
|
||||
```
|
||||
|
||||
It's also available in [MacPorts], which sets up `adb` for you:
|
||||
It's also available in [MacPorts], which sets up adb for you:
|
||||
|
||||
```bash
|
||||
sudo port install scrcpy
|
||||
@@ -162,7 +157,7 @@ You can also [build the app manually][BUILD].
|
||||
|
||||
## Run
|
||||
|
||||
Plug an Android device into your computer, and execute:
|
||||
Plug an Android device, and execute:
|
||||
|
||||
```bash
|
||||
scrcpy
|
||||
@@ -180,7 +175,7 @@ scrcpy --help
|
||||
|
||||
#### 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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
The other dimension is computed so that the Android device aspect ratio is
|
||||
preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
|
||||
The other dimension is computed to that the device aspect ratio is preserved.
|
||||
That way, a device in 1920×1080 will be mirrored at 1024×576.
|
||||
|
||||
|
||||
#### Change bit-rate
|
||||
@@ -213,20 +208,11 @@ scrcpy --max-fps 15
|
||||
|
||||
This is officially supported since Android 10, but may work on earlier versions.
|
||||
|
||||
The actual capture framerate may be printed to the console:
|
||||
|
||||
```
|
||||
scrcpy --print-fps
|
||||
```
|
||||
|
||||
It may also be enabled or disabled at any time with <kbd>MOD</kbd>+<kbd>i</kbd>.
|
||||
|
||||
|
||||
#### Crop
|
||||
|
||||
The device screen may be cropped to mirror only part of the screen.
|
||||
|
||||
This is useful, for example, to mirror only one eye of the Oculus Go:
|
||||
This is useful for example to mirror only one eye of the Oculus Go:
|
||||
|
||||
```bash
|
||||
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||
@@ -237,6 +223,7 @@ If `--max-size` is also specified, resizing is applied after cropping.
|
||||
|
||||
#### Lock video orientation
|
||||
|
||||
|
||||
To lock the orientation of the mirroring:
|
||||
|
||||
```bash
|
||||
@@ -261,7 +248,7 @@ crash. It is possible to select a different encoder:
|
||||
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:
|
||||
|
||||
```bash
|
||||
@@ -325,7 +312,7 @@ v4l2-ctl --list-devices
|
||||
ls /dev/video*
|
||||
```
|
||||
|
||||
To start `scrcpy` using a v4l2 sink:
|
||||
To start scrcpy using a v4l2 sink:
|
||||
|
||||
```bash
|
||||
scrcpy --v4l2-sink=/dev/videoN
|
||||
@@ -333,7 +320,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
|
||||
scrcpy --v4l2-sink=/dev/videoN -N # short version
|
||||
```
|
||||
|
||||
(replace `N` with the device ID, check with `ls /dev/video*`)
|
||||
(replace `N` by the device ID, check with `ls /dev/video*`)
|
||||
|
||||
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
|
||||
|
||||
It is possible to add buffering. This increases latency, but reduces jitter (see
|
||||
It is possible to add buffering. This increases latency but reduces jitter (see
|
||||
[#2464]).
|
||||
|
||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
||||
@@ -369,66 +356,25 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
|
||||
|
||||
### Connection
|
||||
|
||||
#### TCP/IP (wireless)
|
||||
#### Wireless
|
||||
|
||||
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
|
||||
device over TCP/IP. The device must be connected on the same network as the
|
||||
computer.
|
||||
device over TCP/IP:
|
||||
|
||||
##### Automatic
|
||||
|
||||
An option `--tcpip` allows to configure the connection automatically. There are
|
||||
two variants.
|
||||
|
||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
||||
port (typically 5555) for incoming _adb_ connections, then run:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
If _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
|
||||
1. Connect the device to the same Wi-Fi as your computer.
|
||||
2. Get your device IP address, in Settings → About phone → Status, or by
|
||||
executing this command:
|
||||
|
||||
```bash
|
||||
adb shell ip route | awk '{print $9}'
|
||||
```
|
||||
|
||||
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
|
||||
5. Unplug your device.
|
||||
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
|
||||
with the device IP address you found)_.
|
||||
7. Run `scrcpy` as usual.
|
||||
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
|
||||
4. Unplug your device.
|
||||
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
|
||||
6. Run `scrcpy` as usual.
|
||||
|
||||
Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass
|
||||
having to physically connect your device directly to your computer.
|
||||
|
||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
|
||||
|
||||
If the connection randomly drops, run your `scrcpy` command to reconnect. If it
|
||||
says there are no devices/emulators found, try running `adb connect
|
||||
DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are
|
||||
none found, try running `adb disconnect`, and then run those two commands again.
|
||||
|
||||
It may be useful to decrease the bit-rate and the resolution:
|
||||
It may be useful to decrease the bit-rate and the definition:
|
||||
|
||||
```bash
|
||||
scrcpy --bit-rate 2M --max-size 800
|
||||
@@ -440,16 +386,13 @@ scrcpy -b2M -m800 # short version
|
||||
|
||||
#### 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
|
||||
scrcpy --serial 0123456789abcdef
|
||||
scrcpy -s 0123456789abcdef # short version
|
||||
```
|
||||
|
||||
The serial may also be provided via the environment variable `ANDROID_SERIAL`
|
||||
(also used by `adb`).
|
||||
|
||||
If the device is connected over TCP/IP:
|
||||
|
||||
```bash
|
||||
@@ -457,19 +400,6 @@ scrcpy --serial 192.168.0.1:5555
|
||||
scrcpy -s 192.168.0.1:5555 # short version
|
||||
```
|
||||
|
||||
If only one device is connected via either USB or TCP/IP, it is possible to
|
||||
select it automatically:
|
||||
|
||||
```bash
|
||||
# Select the only device connected via USB
|
||||
scrcpy -d # like adb -d
|
||||
scrcpy --select-usb # long version
|
||||
|
||||
# Select the only device connected via TCP/IP
|
||||
scrcpy -e # like adb -e
|
||||
scrcpy --select-tcpip # long version
|
||||
```
|
||||
|
||||
You can start several instances of _scrcpy_ for several devices.
|
||||
|
||||
#### Autostart on device connection
|
||||
@@ -482,67 +412,21 @@ autoadb scrcpy -s '{}'
|
||||
|
||||
[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
|
||||
a remote `adb` server (provided they use the same version of the _adb_
|
||||
protocol).
|
||||
|
||||
##### Remote ADB server
|
||||
|
||||
To connect to a remote _adb server_, make the server listen on all interfaces:
|
||||
protocol):
|
||||
|
||||
```bash
|
||||
adb kill-server
|
||||
adb -a nodaemon server start
|
||||
adb kill-server # kill the local adb server on 5037
|
||||
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# 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`:
|
||||
From another terminal:
|
||||
|
||||
```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
|
||||
adb start-server
|
||||
```
|
||||
|
||||
Then, establish an SSH tunnel:
|
||||
|
||||
```bash
|
||||
# local 5038 --> remote 5037
|
||||
# local 27183 <-- remote 27183
|
||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# keep this open
|
||||
```
|
||||
|
||||
From another terminal, run `scrcpy`:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy
|
||||
```
|
||||
|
||||
@@ -550,16 +434,14 @@ To avoid enabling remote port forwarding, you could force a forward connection
|
||||
instead (notice the `-L` instead of `-R`):
|
||||
|
||||
```bash
|
||||
# local 5038 --> remote 5037
|
||||
# local 27183 --> remote 27183
|
||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
adb kill-server # kill the local adb server on 5037
|
||||
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
# keep this open
|
||||
```
|
||||
|
||||
From another terminal, run `scrcpy`:
|
||||
From another terminal:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy --force-adb-forward
|
||||
```
|
||||
|
||||
@@ -598,7 +480,7 @@ scrcpy --window-borderless
|
||||
|
||||
#### Always on top
|
||||
|
||||
To keep the _scrcpy_ window always on top:
|
||||
To keep the scrcpy window always on top:
|
||||
|
||||
```bash
|
||||
scrcpy --always-on-top
|
||||
@@ -623,7 +505,7 @@ The window may be rotated:
|
||||
scrcpy --rotation 1
|
||||
```
|
||||
|
||||
Possible values:
|
||||
Possibles values are:
|
||||
- `0`: no rotation
|
||||
- `1`: 90 degrees counterclockwise
|
||||
- `2`: 180 degrees
|
||||
@@ -672,19 +554,19 @@ adb shell dumpsys display # search "mDisplayId=" in the output
|
||||
```
|
||||
|
||||
The secondary display may only be controlled if the device runs at least Android
|
||||
10 (otherwise it is mirrored as read-only).
|
||||
10 (otherwise it is mirrored in read-only).
|
||||
|
||||
|
||||
#### 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
|
||||
scrcpy --stay-awake
|
||||
scrcpy -w
|
||||
```
|
||||
|
||||
The initial state is restored when _scrcpy_ is closed.
|
||||
The initial state is restored when scrcpy is closed.
|
||||
|
||||
|
||||
#### Turn screen off
|
||||
@@ -702,10 +584,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>.
|
||||
|
||||
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>),
|
||||
it will force to turn the screen off after a small delay (on a best effort
|
||||
basis). The physical `POWER` button will still cause the screen to be turned
|
||||
on.
|
||||
`POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
|
||||
will force to turn the screen off after a small delay (on a best effort basis).
|
||||
The physical `POWER` button will still cause the screen to be turned on.
|
||||
|
||||
It can also be useful to prevent the device from sleeping:
|
||||
|
||||
@@ -716,22 +597,12 @@ scrcpy -Sw
|
||||
|
||||
#### Power off on close
|
||||
|
||||
To turn the device screen off when closing _scrcpy_:
|
||||
To turn the device screen off when closing scrcpy:
|
||||
|
||||
```bash
|
||||
scrcpy --power-off-on-close
|
||||
```
|
||||
|
||||
#### Power on on start
|
||||
|
||||
By default, on start, the device is powered on.
|
||||
|
||||
To prevent this behavior:
|
||||
|
||||
```bash
|
||||
scrcpy --no-power-on
|
||||
```
|
||||
|
||||
|
||||
#### Show touches
|
||||
|
||||
@@ -748,13 +619,12 @@ scrcpy --show-touches
|
||||
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
|
||||
|
||||
By default, _scrcpy_ does not prevent the screensaver from running on the
|
||||
computer.
|
||||
By default, scrcpy does not prevent the screensaver to run on the computer.
|
||||
|
||||
To disable it:
|
||||
|
||||
@@ -796,53 +666,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
|
||||
clipboard synchronization)
|
||||
|
||||
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> injects the computer
|
||||
clipboard text as a sequence of key events. This is useful when the component
|
||||
does not accept text pasting (for example in _Termux_), but it can break
|
||||
non-ASCII content.
|
||||
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
|
||||
computer clipboard text as a sequence of key events. This is useful when the
|
||||
component does not accept text pasting (for example in _Termux_), but it can
|
||||
break non-ASCII content.
|
||||
|
||||
**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
|
||||
into the Android clipboard. As a consequence, any Android application could read
|
||||
its content. You should avoid pasting sensitive content (like passwords) that
|
||||
into the device clipboard. As a consequence, any Android application could read
|
||||
its content. You should avoid to paste sensitive content (like passwords) that
|
||||
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
|
||||
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
|
||||
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||
|
||||
To disable automatic clipboard synchronization, use
|
||||
`--no-clipboard-autosync`.
|
||||
|
||||
#### Pinch-to-zoom
|
||||
|
||||
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||
|
||||
More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
|
||||
Until the left-click button is released, all mouse movements scale and rotate
|
||||
the content (if supported by the app) relative to the center of the screen.
|
||||
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
|
||||
the left-click button is released, all mouse movements scale and rotate the
|
||||
content (if supported by the app) relative to the center of the screen.
|
||||
|
||||
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
||||
at a location inverted through the center of the screen.
|
||||
Concretely, scrcpy generates additional touch events from a "virtual finger" at
|
||||
a location inverted through the center of the screen.
|
||||
|
||||
#### Physical keyboard simulation (HID)
|
||||
|
||||
By default, _scrcpy_ uses Android key or text injection: it works everywhere,
|
||||
but is limited to ASCII.
|
||||
By default, scrcpy uses Android key or text injection: it works everywhere, but
|
||||
is limited to ASCII.
|
||||
|
||||
Alternatively, `scrcpy` can simulate a physical USB keyboard on Android to
|
||||
provide a better input experience (using [USB HID over AOAv2][hid-aoav2]): the
|
||||
virtual keyboard is disabled and it works for all characters and IME.
|
||||
On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a
|
||||
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
|
||||
keyboard is disabled and it works for all characters and IME.
|
||||
|
||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||
|
||||
However, it only works if the device is connected via USB.
|
||||
|
||||
Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it
|
||||
is not possible to open a USB device if it is already open by another process
|
||||
like the _adb daemon_).
|
||||
However, it only works if the device is connected by USB, and is currently only
|
||||
supported on Linux.
|
||||
|
||||
To enable this mode:
|
||||
|
||||
@@ -853,7 +717,7 @@ scrcpy -K # short version
|
||||
|
||||
If it fails for some reason (for example because the device is not connected via
|
||||
USB), it automatically fallbacks to the default mode (with a log in the
|
||||
console). This allows 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.
|
||||
|
||||
In this mode, raw key events (scancodes) are sent to the device, independently
|
||||
@@ -861,79 +725,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
|
||||
input → [Physical keyboard].
|
||||
|
||||
This settings page can be started directly:
|
||||
|
||||
```bash
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
```
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or when
|
||||
a physical keyboard is connected).
|
||||
|
||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
||||
|
||||
#### Physical mouse simulation (HID)
|
||||
|
||||
Similarly to the physical keyboard simulation, it is possible to simulate a
|
||||
physical mouse. Likewise, it only works if the device is connected by USB.
|
||||
|
||||
By default, _scrcpy_ uses Android mouse events injection with absolute
|
||||
coordinates. By simulating a physical mouse, a mouse pointer appears on the
|
||||
Android device, and relative mouse motion, clicks and scrolls are injected.
|
||||
|
||||
To enable this mode:
|
||||
|
||||
```bash
|
||||
scrcpy --hid-mouse
|
||||
scrcpy -M # short version
|
||||
```
|
||||
|
||||
You can also add `--forward-all-clicks` to [forward all mouse
|
||||
buttons][forward_all_clicks].
|
||||
|
||||
[forward_all_clicks]: #right-click-and-middle-click
|
||||
|
||||
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
|
||||
disappears from the computer and appears on the Android device instead).
|
||||
|
||||
Special capture keys, either <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
|
||||
|
||||
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;
|
||||
- _text events_, signaling that a text has been entered.
|
||||
|
||||
@@ -949,13 +746,7 @@ scrcpy --prefer-text
|
||||
|
||||
(but this will break keyboard behavior in games)
|
||||
|
||||
On the contrary, you could force to always inject raw key events:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
These options have no effect on HID keyboard (all key events are sent as
|
||||
This option has no effect on HID keyboard (all key events are sent as
|
||||
scancodes in this mode).
|
||||
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
@@ -1051,7 +842,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 `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-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_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||
@@ -1062,9 +853,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 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>
|
||||
| Copy to clipboard⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Cut to clipboard⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Synchronize clipboards and paste⁴ | <kbd>MOD</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>
|
||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||
@@ -1074,10 +865,9 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
_³4th and 5th mouse buttons, if your mouse has them._
|
||||
_⁴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":
|
||||
|
||||
1. Press and keep pressing <kbd>MOD</kbd>.
|
||||
@@ -1090,7 +880,7 @@ handled by the active application.
|
||||
|
||||
## Custom paths
|
||||
|
||||
To use a specific `adb` binary, configure its path in the environment variable
|
||||
To use a specific _adb_ binary, configure its path in the environment variable
|
||||
`ADB`:
|
||||
|
||||
```bash
|
||||
@@ -1103,7 +893,7 @@ To override the path of the `scrcpy-server` file, configure its path in
|
||||
To override the icon, configure its path in `SCRCPY_ICON_PATH`.
|
||||
|
||||
|
||||
## Why the name _scrcpy_?
|
||||
## Why _scrcpy_?
|
||||
|
||||
A colleague challenged me to find a name as unpronounceable as [gnirehtet].
|
||||
|
||||
@@ -1120,9 +910,7 @@ See [BUILD].
|
||||
|
||||
## Common issues
|
||||
|
||||
See the [FAQ].
|
||||
|
||||
[FAQ]: FAQ.md
|
||||
See the [FAQ](FAQ.md).
|
||||
|
||||
|
||||
## Developers
|
||||
@@ -1135,7 +923,7 @@ Read the [developers page].
|
||||
## Licence
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -1157,29 +945,17 @@ Read the [developers page].
|
||||
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||
|
||||
## Contact
|
||||
|
||||
If you encounter a bug, please read the [FAQ] first, then open an [issue].
|
||||
|
||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
||||
|
||||
For general questions or discussions, you can also use:
|
||||
|
||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
||||
|
||||
## Translations
|
||||
|
||||
This README is available in other languages:
|
||||
|
||||
- [Deutsch (German, `de`) - v1.22](README.de.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)
|
||||
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
|
||||
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
|
||||
- [Español (Spanish, `sp`) - v1.21](README.sp.md)
|
||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md)
|
||||
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
|
||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md)
|
||||
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
||||
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)
|
||||
|
||||
|
||||
@@ -857,7 +857,7 @@ Leia a [página dos desenvolvedores][developers page].
|
||||
## Licença
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
371
README.sp.md
371
README.sp.md
@@ -1,36 +1,24 @@
|
||||
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 control e imagen de un dispositivo Android conectado
|
||||
por USB (o [por TCP/IP](#conexión)). No requiere acceso _root_.
|
||||
Esta aplicación proporciona imagen y control de un dispositivo Android conectado
|
||||
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
|
||||
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
|
||||
|
||||

|
||||
|
||||
Se enfoca en:
|
||||
- **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo
|
||||
- **rendimiento**: 30~120fps, dependiendo del dispositivo
|
||||
- **calidad**: 1920×1080 o superior
|
||||
- **baja latencia**: [35~70ms][lowlatency]
|
||||
- **inicio rápido**: ~1 segundo para mostrar la primera imagen
|
||||
- **no intrusivo**: no deja nada instalado en el dispositivo
|
||||
- **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet
|
||||
- **libertad**: software gratis y de código abierto
|
||||
Sus características principales son:
|
||||
|
||||
- **ligero** (nativo, solo muestra la imagen del dispositivo)
|
||||
- **desempeño** (30~60fps)
|
||||
- **calidad** (1920×1080 o superior)
|
||||
- **baja latencia** ([35~70ms][lowlatency])
|
||||
- **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
|
||||
- **no intrusivo** (no se deja nada instalado en el dispositivo)
|
||||
|
||||
[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
|
||||
|
||||
@@ -63,7 +51,7 @@ Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
|
||||
|
||||
### Linux
|
||||
|
||||
En Debian y Ubuntu:
|
||||
En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
|
||||
|
||||
```
|
||||
apt install scrcpy
|
||||
@@ -137,7 +125,7 @@ Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
|
||||
brew install android-platform-tools
|
||||
```
|
||||
|
||||
También está disponible en [MacPorts], que configura el adb automáticamente:
|
||||
También está disponible en [MacPorts], que configurará el adb automáticamente:
|
||||
|
||||
```bash
|
||||
sudo port install scrcpy
|
||||
@@ -165,7 +153,7 @@ scrcpy --help
|
||||
|
||||
## Características
|
||||
|
||||
### Configuración de captura
|
||||
### Capturar configuració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:
|
||||
|
||||
```bash
|
||||
scrcpy --lock-video-orientation # orientación inicial
|
||||
scrcpy --lock-video-orientation=0 # orientación normal
|
||||
scrcpy --lock-video-orientation=1 # 90° contrarreloj
|
||||
scrcpy --lock-video-orientation=2 # 180°
|
||||
scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj
|
||||
scrcpy --lock-video-orientation 0 # orientación normal
|
||||
scrcpy --lock-video-orientation 1 # 90° contrarreloj
|
||||
scrcpy --lock-video-orientation 2 # 180°
|
||||
scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
|
||||
```
|
||||
|
||||
Esto afecta la rotación de la grabación.
|
||||
@@ -246,10 +233,7 @@ Para listar los codificadores disponibles, puedes pasar un nombre de codificador
|
||||
scrcpy --encoder _
|
||||
```
|
||||
|
||||
### Capturas y grabaciones
|
||||
|
||||
|
||||
#### Grabación
|
||||
### Grabación
|
||||
|
||||
Es posible grabar la pantalla mientras se transmite:
|
||||
|
||||
@@ -266,117 +250,17 @@ scrcpy -Nr file.mkv
|
||||
# interrumpe la grabación con Ctrl+C
|
||||
```
|
||||
|
||||
Los "skipped frames" son grabados, incluso si no se mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
|
||||
"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.
|
||||
|
||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||
|
||||
|
||||
#### v4l2loopback
|
||||
|
||||
En Linux se puede mandar el stream del video a un dispositivo loopback v4l2, por
|
||||
lo que se puede abrir el dispositivo Android como una webcam con cualquier
|
||||
programa compatible con v4l2.
|
||||
|
||||
Se debe instalar el modulo `v4l2loopback`:
|
||||
|
||||
```bash
|
||||
sudo apt install v4l2loopback-dkms
|
||||
```
|
||||
|
||||
Para crear un dispositivo v4l2:
|
||||
|
||||
```bash
|
||||
sudo modprobe v4l2loopback
|
||||
```
|
||||
|
||||
Esto va a crear un nuevo dispositivo de video en `/dev/videoN`, donde `N` es un número
|
||||
(hay más [opciones](https://github.com/umlaeute/v4l2loopback#options) disponibles
|
||||
para crear múltiples dispositivos o usar un ID en específico).
|
||||
|
||||
Para ver los dispositivos disponibles:
|
||||
|
||||
```bash
|
||||
# requiere el paquete v4l-utils
|
||||
v4l2-ctl --list-devices
|
||||
# simple pero generalmente suficiente
|
||||
ls /dev/video*
|
||||
```
|
||||
|
||||
Para iniciar scrcpy usando una fuente v4l2:
|
||||
|
||||
```bash
|
||||
scrcpy --v4l2-sink=/dev/videoN
|
||||
scrcpy --v4l2-sink=/dev/videoN --no-display # deshabilita la transmisión de imagen
|
||||
scrcpy --v4l2-sink=/dev/videoN -N # más corto
|
||||
```
|
||||
|
||||
(reemplace `N` con el ID del dispositivo, compruebe con `ls /dev/video*`)
|
||||
|
||||
Una vez habilitado, podés abrir el stream del video con una herramienta compatible con v4l2:
|
||||
|
||||
```bash
|
||||
ffplay -i /dev/videoN
|
||||
vlc v4l2:///dev/videoN # VLC puede agregar un delay por buffering
|
||||
```
|
||||
|
||||
Por ejemplo, podrías capturar el video usando [OBS].
|
||||
|
||||
[OBS]: https://obsproject.com/
|
||||
|
||||
|
||||
#### Buffering
|
||||
|
||||
Es posible agregar buffering al video. Esto reduce el ruido en la imagen ("jitter")
|
||||
pero aumenta la latencia (vea [#2464]).
|
||||
|
||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
||||
|
||||
La opción de buffering está disponible para la transmisión de imagen:
|
||||
|
||||
```bash
|
||||
scrcpy --display-buffer=50 # agrega 50 ms de buffering a la imagen
|
||||
```
|
||||
|
||||
y las fuentes V4L2:
|
||||
|
||||
```bash
|
||||
scrcpy --v4l2-buffer=500 # agrega 500 ms de buffering a la fuente v4l2
|
||||
```
|
||||
|
||||
|
||||
### Conexión
|
||||
|
||||
#### TCP/IP (Inalámbrica)
|
||||
#### Inalámbrica
|
||||
|
||||
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP.
|
||||
El dispositivo debe estar conectado a la misma red que la computadora:
|
||||
|
||||
##### Automático
|
||||
|
||||
La opción `--tcpip` permite configurar la conexión automáticamente. Hay 2 variables.
|
||||
|
||||
Si el dispositivo (accesible en 192.168.1.1 para este ejemplo) ya está escuchando
|
||||
en un puerto (generalmente 5555) esperando una conexión adb entrante, entonces corré:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # el puerto default es 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
Si el dispositivo no tiene habilitado el modo adb TCP/IP (o si no sabés la dirección IP),
|
||||
entonces conectá el dispositivo por USB y corré:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip # sin argumentos
|
||||
```
|
||||
|
||||
El programa buscará automáticamente la IP del dispositivo, habilitará el modo TCP/IP, y
|
||||
se conectará al dispositivo antes de comenzar a transmitir la imagen.
|
||||
|
||||
##### Manual
|
||||
|
||||
Como alternativa, se puede habilitar la conexión TCP/IP manualmente usando `adb`:
|
||||
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP:
|
||||
|
||||
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:
|
||||
@@ -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.
|
||||
|
||||
#### Iniciar automáticamente al detectar dispositivo
|
||||
#### Autoiniciar al detectar dispositivo
|
||||
|
||||
Puedes utilizar [AutoAdb]:
|
||||
|
||||
@@ -428,82 +312,37 @@ autoadb scrcpy -s '{}'
|
||||
|
||||
[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_).
|
||||
|
||||
##### Servidor ADB remoto
|
||||
|
||||
Para conectarse a un servidor ADB remoto, haz que el servidor escuche en todas las interfaces:
|
||||
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_):
|
||||
|
||||
```bash
|
||||
adb kill-server
|
||||
adb -a nodaemon server start
|
||||
adb kill-server # cierra el servidor local adb en 5037
|
||||
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# conserva este servidor abierto
|
||||
```
|
||||
|
||||
**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.**
|
||||
|
||||
Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra
|
||||
terminal, corré scrcpy:
|
||||
Desde otra terminal:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
||||
scrcpy --tunnel-host=192.168.1.2
|
||||
```
|
||||
|
||||
Por default, scrcpy usa el puerto local que se usó para establecer el tunel
|
||||
`adb forward` (típicamente `27183`, vea `--port`). También es posible forzar un
|
||||
puerto diferente (puede resultar útil en situaciones más complejas, donde haya
|
||||
múltiples redirecciones):
|
||||
|
||||
```
|
||||
scrcpy --tunnel-port=1234
|
||||
```
|
||||
|
||||
|
||||
##### Túnel SSH
|
||||
|
||||
Para comunicarse con un servidor ADB remoto de forma segura, es preferible usar un túnel SSH.
|
||||
|
||||
Primero, asegurate que el servidor ADB está corriendo en la computadora remota:
|
||||
|
||||
```bash
|
||||
adb start-server
|
||||
```
|
||||
|
||||
Después, establecé el túnel SSH:
|
||||
|
||||
```bash
|
||||
# local 5038 --> remoto 5037
|
||||
# local 27183 <-- remoto 27183
|
||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# conserva este servidor abierto
|
||||
```
|
||||
|
||||
Desde otra terminal, corré scrcpy:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy
|
||||
```
|
||||
|
||||
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
|
||||
|
||||
```bash
|
||||
# local 5038 --> remoto 5037
|
||||
# local 27183 --> remoto 27183
|
||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
adb kill-server # cierra el servidor local adb en 5037
|
||||
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
# conserva este servidor abierto
|
||||
```
|
||||
|
||||
Desde otra terminal, corré scrcpy:
|
||||
Desde otra terminal:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy --force-adb-forward
|
||||
```
|
||||
|
||||
|
||||
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
|
||||
|
||||
```
|
||||
@@ -563,7 +402,7 @@ Se puede rotar la ventana:
|
||||
scrcpy --rotation 1
|
||||
```
|
||||
|
||||
Los posibles valores son:
|
||||
Los valores posibles son:
|
||||
- `0`: sin rotación
|
||||
- `1`: 90 grados contrarreloj
|
||||
- `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.
|
||||
|
||||
|
||||
### Otras opciones
|
||||
### Otras opciones menores
|
||||
|
||||
#### 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
|
||||
scrcpy --power-off-on-close
|
||||
scrcpy --render-expired-frames
|
||||
```
|
||||
|
||||
#### 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>).
|
||||
|
||||
Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`.
|
||||
|
||||
#### Pellizcar para zoom
|
||||
|
||||
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.
|
||||
|
||||
#### 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
|
||||
|
||||
@@ -776,23 +573,13 @@ scrcpy --prefer-text
|
||||
|
||||
(Pero esto romperá el comportamiento del teclado en los juegos)
|
||||
|
||||
Por el contrario, se puede forzar scrcpy para siempre injectar _raw key events_:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
Estas opciones no tienen efecto en los teclados HID (todos los _key events_ son enviados como
|
||||
_scancodes_ en este modo).
|
||||
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||
|
||||
|
||||
#### Repetir tecla
|
||||
|
||||
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede
|
||||
causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
|
||||
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
|
||||
|
||||
Para evitar enviar _key events_ repetidos:
|
||||
|
||||
@@ -800,9 +587,6 @@ Para evitar enviar _key events_ repetidos:
|
||||
scrcpy --no-key-repeat
|
||||
```
|
||||
|
||||
Estas opciones no tienen efecto en los teclados HID (Android maneja directamente
|
||||
las repeticiones de teclas en este modo)
|
||||
|
||||
|
||||
#### Botón derecho y botón del medio
|
||||
|
||||
@@ -824,15 +608,14 @@ No hay respuesta visual, un mensaje se escribirá en la consola.
|
||||
|
||||
#### Enviar archivos al dispositivo
|
||||
|
||||
Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte
|
||||
un archivo (no APK) a la ventana de _scrcpy_.
|
||||
Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
|
||||
|
||||
No hay ninguna respuesta visual, un mensaje se escribirá en la consola.
|
||||
No hay respuesta visual, un mensaje se escribirá en la consola.
|
||||
|
||||
El directorio de destino puede ser modificado al iniciar:
|
||||
|
||||
```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)
|
||||
|
||||
| Acción | Atajo
|
||||
| ------------------------------------------- |:-----------------------------
|
||||
| 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 derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
|
||||
| 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¹_
|
||||
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click medio_
|
||||
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click derecho²_
|
||||
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Cuarto botón³_
|
||||
| Click en `MENÚ` (desbloquear pantalla)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
|
||||
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
|
||||
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||
| Encendido | _Botón derecho²_
|
||||
| Apagar pantalla (manteniendo la transmisión) | <kbd>MOD</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>
|
||||
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Quinto botón³_
|
||||
| Abrir panel de configuración | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doble quinto botón³_
|
||||
| Cerrar paneles | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copiar al portapapeles⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Cortar al portapapeles⁵ | <kbd>MOD</kbd>+<kbd>x</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>
|
||||
| Acción | Atajo
|
||||
| ------------------------------------------- |:-----------------------------
|
||||
| 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 derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
|
||||
| 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¹_
|
||||
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
|
||||
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
|
||||
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
|
||||
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
|
||||
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||
| Encendido | _Botón derecho²_
|
||||
| Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</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>
|
||||
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||
| Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</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>
|
||||
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| 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)
|
||||
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
|
||||
|
||||
_¹Doble click en los bordes negros para eliminarlos._
|
||||
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
|
||||
_³Cuarto y quinto botón del mouse, si tu mouse los tiene._
|
||||
_⁴Para las apps react-native en desarrollo, `MENU` activa el menú de desarrollo._
|
||||
_⁵Solo en Android >= 7._
|
||||
|
||||
Los shortcuts con teclas repetidas se ejecutan soltando y volviendo a apretar la tecla
|
||||
por segunda vez. Por ejemplo, para ejecutar "Abrir panel de configuración":
|
||||
|
||||
1. Apretá y mantené apretado <kbd>MOD</kbd>.
|
||||
2. Después apretá dos veces la tecla <kbd>n</kbd>.
|
||||
3. Por último, soltá la tecla <kbd>MOD</kbd>.
|
||||
_³Solo en Android >= 7._
|
||||
|
||||
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 ícono, configure el path en `SCRCPY_ICON_PATH`.
|
||||
|
||||
|
||||
## ¿Por qué _scrcpy_?
|
||||
|
||||
@@ -951,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
|
||||
## Licencia
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
|
||||
## Lisans
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
_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]
|
||||
Current version is based on [65b023a]
|
||||
|
||||
本文根据[f4c7044]进行翻译。
|
||||
本文根据[65b023a]进行翻译。
|
||||
|
||||
[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md
|
||||
[65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md
|
||||
|
||||
# scrcpy (v1.22)
|
||||
# scrcpy (v1.20)
|
||||
|
||||
<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_。
|
||||
|
||||

|
||||
@@ -38,8 +36,6 @@ _发音为 "**scr**een **c**o**py**"_
|
||||
- [可配置显示质量](#采集设置)
|
||||
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
|
||||
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
|
||||
- [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux)
|
||||
- [OTG模式](#otg) (仅限 Linux)
|
||||
- 更多 ……
|
||||
|
||||
## 系统要求
|
||||
@@ -72,18 +68,12 @@ _发音为 "**scr**een **c**o**py**"_
|
||||
|
||||
### Linux
|
||||
|
||||
在 Debian 和 Ubuntu 上:
|
||||
在 Debian (目前仅支持 _testing_ 和 _sid_ 分支) 和Ubuntu (20.04) 上:
|
||||
|
||||
```
|
||||
apt install scrcpy
|
||||
```
|
||||
|
||||
在 Arch Linux 上:
|
||||
|
||||
```
|
||||
pacman -S scrcpy
|
||||
```
|
||||
|
||||
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
|
||||
|
||||
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||
@@ -95,6 +85,11 @@ pacman -S scrcpy
|
||||
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||
|
||||
对 Arch Linux 我们提供 [AUR] 包: [`scrcpy`][aur-link]。
|
||||
|
||||
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||
|
||||
对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
|
||||
|
||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||
@@ -348,32 +343,9 @@ scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
|
||||
|
||||
### 连接
|
||||
|
||||
#### TCP/IP (无线)
|
||||
#### 无线
|
||||
|
||||
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。
|
||||
|
||||
##### 自动配置
|
||||
|
||||
参数 `--tcpip` 允许自动配置连接。这里有两种方式。
|
||||
|
||||
对于传入的 adb 连接,如果设备(在这个例子中以192.168.1.1为可用地址)已经监听了一个端口(通常是5555),运行:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # 默认端口是5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
如果adb TCP/IP(无线) 模式在某些设备上不被启用(或者你不知道IP地址),用USB连接设备,然后运行:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip # 无需其他参数
|
||||
```
|
||||
|
||||
这将会自动寻找设备IP地址,启用TCP/IP模式,然后在启动之前连接到设备。
|
||||
|
||||
##### 手动配置
|
||||
|
||||
或者,可以通过 `adb` 使用手动启用 TCP/IP 连接:
|
||||
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备:
|
||||
|
||||
1. 将设备和电脑连接至同一 Wi-Fi。
|
||||
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
|
||||
@@ -382,12 +354,12 @@ scrcpy --tcpip # 无需其他参数
|
||||
adb shell ip route | awk '{print $9}'
|
||||
```
|
||||
|
||||
3. 启用设备的网络 adb 功能:`adb tcpip 5555`。
|
||||
3. 启用设备的网络 adb 功能: `adb tcpip 5555`。
|
||||
4. 断开设备的 USB 连接。
|
||||
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。
|
||||
6. 正常运行 `scrcpy`。
|
||||
|
||||
降低比特率和分辨率可能很有用:
|
||||
可能降低码率和分辨率会更好一些:
|
||||
|
||||
```bash
|
||||
scrcpy --bit-rate 2M --max-size 800
|
||||
@@ -425,75 +397,33 @@ autoadb scrcpy -s '{}'
|
||||
|
||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||
|
||||
#### 隧道
|
||||
#### SSH 隧道
|
||||
|
||||
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)。
|
||||
|
||||
##### 远程ADB服务器
|
||||
|
||||
要连接到一个远程ADB服务器,让服务器在所有接口上监听:
|
||||
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同):
|
||||
|
||||
```bash
|
||||
adb kill-server
|
||||
adb -a nodaemon server start
|
||||
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
|
||||
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# 保持该窗口开启
|
||||
```
|
||||
|
||||
**警告:所有客户端与ADB服务器的交流都是未加密的。**
|
||||
|
||||
假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy:
|
||||
在另一个终端:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
||||
scrcpy --tunnel-host=192.168.1.2
|
||||
```
|
||||
|
||||
默认情况下,scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用):
|
||||
|
||||
```
|
||||
scrcpy --tunnel-port=1234
|
||||
```
|
||||
|
||||
|
||||
##### SSH 隧道
|
||||
|
||||
为了安全地与远程ADB服务器通信,最好使用SSH隧道。
|
||||
|
||||
首先,确保ADB服务器正在远程计算机上运行:
|
||||
|
||||
```bash
|
||||
adb start-server
|
||||
```
|
||||
|
||||
然后,建立一个SSH隧道:
|
||||
|
||||
```bash
|
||||
# 本地 5038 --> 远程 5037
|
||||
# 本地 27183 <-- 远程 27183
|
||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||
# 保持该窗口开启
|
||||
```
|
||||
|
||||
在另一个终端上,运行scrcpy:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy
|
||||
```
|
||||
|
||||
若要不使用远程端口转发,可以强制使用正向连接(注意是 `-L` 而不是 `-R` ):
|
||||
若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` 和 `-R` 的区别):
|
||||
|
||||
```bash
|
||||
# 本地 5038 --> 远程 5037
|
||||
# 本地 27183 <-- 远程 27183
|
||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
|
||||
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
# 保持该窗口开启
|
||||
```
|
||||
|
||||
在另一个终端上,运行scrcpy:
|
||||
在另一个终端:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
||||
scrcpy --force-adb-forward
|
||||
```
|
||||
|
||||
@@ -511,7 +441,7 @@ scrcpy -b2M -m800 --max-fps 15
|
||||
窗口的标题默认为设备型号。可以通过如下命令修改:
|
||||
|
||||
```bash
|
||||
scrcpy --window-title "我的设备"
|
||||
scrcpy --window-title 'My device'
|
||||
```
|
||||
|
||||
#### 位置和大小
|
||||
@@ -700,15 +630,13 @@ 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>) 来注入电脑剪贴板内容。
|
||||
|
||||
要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。
|
||||
|
||||
#### 双指缩放
|
||||
|
||||
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按下并拖动鼠标_。
|
||||
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
|
||||
|
||||
在按住 <kbd>Ctrl</kbd> 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。
|
||||
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
|
||||
|
||||
具体来说,_scrcpy_ 会在鼠标位置,以及鼠标以屏幕中心镜像的位置分别生成触摸事件。
|
||||
实际上,_scrcpy_ 会在关于屏幕中心对称的位置上用“虚拟手指”发出触摸事件。
|
||||
|
||||
#### 物理键盘模拟 (HID)
|
||||
|
||||
@@ -731,60 +659,11 @@ scrcpy -K # 简写
|
||||
|
||||
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 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 线直接插入设备一样。
|
||||
|
||||
在这个模式下,_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 上支持。
|
||||
|
||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
||||
|
||||
#### 文本注入偏好
|
||||
|
||||
输入文字的时候,系统会产生两种[事件][textevents]:
|
||||
打字的时候,系统会产生两种[事件][textevents]:
|
||||
- _按键事件_ ,代表一个按键被按下或松开。
|
||||
- _文本事件_ ,代表一个字符被输入。
|
||||
|
||||
@@ -796,13 +675,7 @@ scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
|
||||
scrcpy --prefer-text
|
||||
```
|
||||
|
||||
(但这会导致键盘在游戏中工作不正常)
|
||||
|
||||
相反,您可以强制始终注入原始按键事件:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
(这会导致键盘在游戏中工作不正常)
|
||||
|
||||
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
|
||||
|
||||
@@ -892,7 +765,7 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
|
||||
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
|
||||
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
|
||||
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
|
||||
| 点按 `菜单` (解锁屏幕)⁴ | <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>p</kbd>
|
||||
@@ -903,9 +776,9 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
|
||||
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
|
||||
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
|
||||
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| 复制到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| 剪切到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| 同步剪贴板并粘贴⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| 复制到剪贴板⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| 剪切到剪贴板⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| 同步剪贴板并粘贴⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
|
||||
@@ -915,8 +788,7 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
|
||||
_¹双击黑边可以去除黑边。_
|
||||
_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_
|
||||
_³鼠标的第4键和第5键。_
|
||||
_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_
|
||||
_⁵需要安卓版本 Android >= 7。_
|
||||
_⁴需要安卓版本 Android >= 7。_
|
||||
|
||||
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
|
||||
|
||||
@@ -944,7 +816,7 @@ ADB=/path/to/adb scrcpy
|
||||
|
||||
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
|
||||
|
||||
[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。
|
||||
[`strcpy`] 复制一个 **str**ing (字符串); `scrcpy` 复制一个 **scr**een (屏幕)。
|
||||
|
||||
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||
@@ -970,7 +842,7 @@ ADB=/path/to/adb scrcpy
|
||||
## 许可协议
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -679,7 +679,7 @@ _³只支援 Android 7+。_
|
||||
## Licence
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -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 |
@@ -1 +0,0 @@
|
||||
@cmd
|
||||
@@ -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
|
||||
124
app/meson.build
124
app/meson.build
@@ -1,19 +1,16 @@
|
||||
src = [
|
||||
'src/main.c',
|
||||
'src/adb/adb.c',
|
||||
'src/adb/adb_device.c',
|
||||
'src/adb/adb_parser.c',
|
||||
'src/adb/adb_tunnel.c',
|
||||
'src/adb.c',
|
||||
'src/adb_tunnel.c',
|
||||
'src/cli.c',
|
||||
'src/clock.c',
|
||||
'src/compat.c',
|
||||
'src/control_msg.c',
|
||||
'src/controller.c',
|
||||
'src/decoder.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
'src/file_handler.c',
|
||||
'src/fps_counter.c',
|
||||
'src/frame_buffer.c',
|
||||
'src/input_manager.c',
|
||||
@@ -26,11 +23,9 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/version.c',
|
||||
'src/stream.c',
|
||||
'src/video_buffer.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/file.c',
|
||||
'src/util/intmap.c',
|
||||
'src/util/intr.c',
|
||||
'src/util/log.c',
|
||||
'src/util/net.c',
|
||||
@@ -44,76 +39,61 @@ src = [
|
||||
'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'
|
||||
windows = import('windows')
|
||||
src += [
|
||||
'src/sys/win/file.c',
|
||||
'src/sys/win/process.c',
|
||||
windows.compile_resources('scrcpy-windows.rc'),
|
||||
]
|
||||
conf.set('_WIN32_WINNT', '0x0600')
|
||||
conf.set('WINVER', '0x0600')
|
||||
else
|
||||
src += [
|
||||
'src/sys/unix/file.c',
|
||||
'src/sys/unix/process.c',
|
||||
]
|
||||
if host_machine.system() == 'darwin'
|
||||
conf.set('_DARWIN_C_SOURCE', true)
|
||||
endif
|
||||
endif
|
||||
|
||||
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux'
|
||||
v4l2_support = host_machine.system() == 'linux'
|
||||
if v4l2_support
|
||||
src += [ 'src/v4l2_sink.c' ]
|
||||
endif
|
||||
|
||||
usb_support = get_option('usb')
|
||||
if usb_support
|
||||
aoa_hid_support = host_machine.system() == 'linux'
|
||||
if aoa_hid_support
|
||||
src += [
|
||||
'src/usb/aoa_hid.c',
|
||||
'src/usb/hid_keyboard.c',
|
||||
'src/usb/hid_mouse.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
'src/usb/screen_otg.c',
|
||||
'src/usb/usb.c',
|
||||
'src/aoa_hid.c',
|
||||
'src/hid_keyboard.c',
|
||||
]
|
||||
endif
|
||||
|
||||
check_functions = [
|
||||
'strdup'
|
||||
]
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
|
||||
|
||||
if not crossbuild_windows
|
||||
if not get_option('crossbuild_windows')
|
||||
|
||||
# native build
|
||||
dependencies = [
|
||||
dependency('libavformat', version: '>= 57.33'),
|
||||
dependency('libavcodec', version: '>= 57.37'),
|
||||
dependency('libavformat'),
|
||||
dependency('libavcodec'),
|
||||
dependency('libavutil'),
|
||||
dependency('sdl2', version: '>= 2.0.5'),
|
||||
dependency('sdl2'),
|
||||
]
|
||||
|
||||
if v4l2_support
|
||||
dependencies += dependency('libavdevice')
|
||||
endif
|
||||
|
||||
if usb_support
|
||||
if aoa_hid_support
|
||||
dependencies += dependency('libusb-1.0')
|
||||
endif
|
||||
|
||||
else
|
||||
# cross-compile mingw32 build (from Linux to Windows)
|
||||
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
||||
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
|
||||
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
|
||||
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
|
||||
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
|
||||
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
|
||||
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
|
||||
|
||||
sdl2 = declare_dependency(
|
||||
dependencies: [
|
||||
@@ -123,40 +103,22 @@ else
|
||||
include_directories: include_directories(sdl2_include_dir)
|
||||
)
|
||||
|
||||
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
|
||||
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
|
||||
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
|
||||
|
||||
# ffmpeg 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')
|
||||
|
||||
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
|
||||
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
|
||||
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
|
||||
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
|
||||
ffmpeg = declare_dependency(
|
||||
dependencies: [
|
||||
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
|
||||
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
|
||||
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avutil-56', dirs: ffmpeg_bin_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 = [
|
||||
ffmpeg,
|
||||
sdl2,
|
||||
libusb,
|
||||
cc.find_library('mingw32')
|
||||
]
|
||||
|
||||
@@ -166,11 +128,7 @@ if host_machine.system() == 'windows'
|
||||
dependencies += cc.find_library('ws2_32')
|
||||
endif
|
||||
|
||||
check_functions = [
|
||||
'strdup',
|
||||
'asprintf',
|
||||
'vasprintf',
|
||||
]
|
||||
conf = configuration_data()
|
||||
|
||||
foreach f : check_functions
|
||||
if cc.has_function(f)
|
||||
@@ -179,9 +137,6 @@ foreach f : check_functions
|
||||
endif
|
||||
endforeach
|
||||
|
||||
conf.set('HAVE_SOCK_CLOEXEC', host_machine.system() != 'windows' and
|
||||
cc.has_header_symbol('sys/socket.h', 'SOCK_CLOEXEC'))
|
||||
|
||||
# the version, updated on release
|
||||
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||
|
||||
@@ -211,7 +166,7 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
|
||||
conf.set('HAVE_V4L2', v4l2_support)
|
||||
|
||||
# enable HID over AOA support (linux only)
|
||||
conf.set('HAVE_USB', usb_support)
|
||||
conf.set('HAVE_AOA_HID', aoa_hid_support)
|
||||
|
||||
configure_file(configuration: conf, output: 'config.h')
|
||||
|
||||
@@ -224,13 +179,9 @@ executable('scrcpy', src,
|
||||
c_args: [])
|
||||
|
||||
install_man('scrcpy.1')
|
||||
install_data('data/icon.png',
|
||||
install_data('../data/icon.png',
|
||||
rename: 'scrcpy.png',
|
||||
install_dir: 'share/icons/hicolor/256x256/apps')
|
||||
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
|
||||
@@ -238,15 +189,8 @@ install_data('data/bash-completion/scrcpy',
|
||||
# do not build tests in release (assertions would not be executed at all)
|
||||
if get_option('buildtype') == 'debug'
|
||||
tests = [
|
||||
['test_adb_parser', [
|
||||
'tests/test_adb_parser.c',
|
||||
'src/adb/adb_device.c',
|
||||
'src/adb/adb_parser.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_buffer_util', [
|
||||
'tests/test_buffer_util.c',
|
||||
'tests/test_buffer_util.c'
|
||||
]],
|
||||
['test_cbuf', [
|
||||
'tests/test_cbuf.c',
|
||||
@@ -255,7 +199,6 @@ if get_option('buildtype') == 'debug'
|
||||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
'src/options.c',
|
||||
'src/util/net.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/term.c',
|
||||
@@ -286,9 +229,6 @@ if get_option('buildtype') == 'debug'
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_vector', [
|
||||
'tests/test_vector.c',
|
||||
]],
|
||||
]
|
||||
|
||||
foreach t : tests
|
||||
|
||||
1
app/prebuilt-deps/.gitignore
vendored
1
app/prebuilt-deps/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/data
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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/ \
|
||||
@@ -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>
|
||||
@@ -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
|
||||
118
app/scrcpy.1
118
app/scrcpy.1
@@ -43,12 +43,6 @@ The values are expressed in the device natural orientation (typically, portrait
|
||||
.B \-\-max\-size
|
||||
value is computed on the cropped size.
|
||||
|
||||
.TP
|
||||
.B \-d, \-\-select\-usb
|
||||
Use USB device (if there is exactly one, like adb -d).
|
||||
|
||||
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-\-disable-screensaver"
|
||||
Disable screensaver while scrcpy is running.
|
||||
@@ -68,12 +62,6 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc
|
||||
|
||||
Default is 0 (no buffering).
|
||||
|
||||
.TP
|
||||
.B \-e, \-\-select\-tcpip
|
||||
Use TCP/IP device (if there is exactly one, like adb -e).
|
||||
|
||||
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-\-encoder " name
|
||||
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
||||
@@ -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.
|
||||
|
||||
It may only work over USB.
|
||||
|
||||
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
||||
|
||||
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.
|
||||
It may only work over USB, and is currently only supported on Linux.
|
||||
|
||||
.TP
|
||||
.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).
|
||||
|
||||
.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
|
||||
.B \-n, \-\-no\-control
|
||||
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
|
||||
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
|
||||
.BI "\-p, \-\-port " port[:port]
|
||||
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,
|
||||
but breaks the expected behavior of alpha keys in games (typically WASD).
|
||||
|
||||
.TP
|
||||
.B "\-\-print\-fps
|
||||
Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i.
|
||||
|
||||
.TP
|
||||
.BI "\-\-push\-target " path
|
||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||
|
||||
Default is "/sdcard/Download/".
|
||||
|
||||
.TP
|
||||
.B \-\-raw\-key\-events
|
||||
Inject key events for all input keys, and ignore text events.
|
||||
|
||||
.TP
|
||||
.BI "\-r, \-\-record " file
|
||||
Record screen to
|
||||
@@ -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).
|
||||
|
||||
.TP
|
||||
.BI "\-\-tcpip[=ip[:port]]
|
||||
Configure and reconnect the device over TCP/IP.
|
||||
|
||||
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
||||
|
||||
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
|
||||
|
||||
.TP
|
||||
.B \-S, \-\-turn\-screen\-off
|
||||
Turn the device screen off immediately.
|
||||
@@ -287,18 +203,6 @@ Enable "show touches" on start, restore the initial value on exit.
|
||||
|
||||
It only shows physical touches (not clicks from scrcpy).
|
||||
|
||||
.TP
|
||||
.BI "\-\-tunnel\-host " ip
|
||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||
|
||||
Default is localhost.
|
||||
|
||||
.TP
|
||||
.BI "\-\-tunnel\-port " port
|
||||
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||
|
||||
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
||||
|
||||
.TP
|
||||
.BI "\-\-v4l2-sink " /dev/videoN
|
||||
Output to v4l2loopback device.
|
||||
@@ -359,12 +263,6 @@ Set the initial window height.
|
||||
|
||||
Default is 0 (automatic).
|
||||
|
||||
.SH EXIT STATUS
|
||||
.B scrcpy
|
||||
will exit with code 0 on normal program termination. If an initial
|
||||
connection cannot be established, the exit code 1 will be returned. If the
|
||||
device disconnects while a session is active, exit code 2 will be returned.
|
||||
|
||||
.SH SHORTCUTS
|
||||
|
||||
In the following list, MOD is the shortcut modifier. By default, it's (left)
|
||||
@@ -479,19 +377,11 @@ Push file to device (see \fB\-\-push\-target\fR)
|
||||
|
||||
.TP
|
||||
.B ADB
|
||||
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.
|
||||
Specify the path to adb.
|
||||
|
||||
.TP
|
||||
.B SCRCPY_SERVER_PATH
|
||||
Path to the server binary.
|
||||
Specify the path to server binary.
|
||||
|
||||
|
||||
.SH AUTHORS
|
||||
@@ -516,7 +406,7 @@ Copyright \(co 2018 Genymobile
|
||||
Genymobile
|
||||
.UE
|
||||
|
||||
Copyright \(co 2018\-2022
|
||||
Copyright \(co 2018\-2020
|
||||
.MT rom@rom1v.com
|
||||
Romain Vimont
|
||||
.ME
|
||||
|
||||
277
app/src/adb.c
Normal file
277
app/src/adb.c
Normal file
@@ -0,0 +1,277 @@
|
||||
#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/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);
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_execute_p(const char *serial, const char *const adb_cmd[],
|
||||
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) {
|
||||
int i;
|
||||
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, pin, pout, perr);
|
||||
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) {
|
||||
return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_forward(const char *serial, uint16_t local_port,
|
||||
const char *device_socket_name) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
const char *const adb_cmd[] = {"forward", local, remote};
|
||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_forward_remove(const char *serial, uint16_t local_port) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
const char *const adb_cmd[] = {"forward", "--remove", local};
|
||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_reverse(const char *serial, const char *device_socket_name,
|
||||
uint16_t local_port) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
const char *const adb_cmd[] = {"reverse", remote, local};
|
||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_reverse_remove(const char *serial, const char *device_socket_name) {
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
const char *const adb_cmd[] = {"reverse", "--remove", remote};
|
||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_push(const char *serial, const char *local, const char *remote) {
|
||||
#ifdef __WINDOWS__
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = 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));
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
free((void *) remote);
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
sc_pid
|
||||
adb_install(const char *serial, const char *local) {
|
||||
#ifdef __WINDOWS__
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = 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));
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
adb_execute_for_output(const char *serial, const char *const adb_cmd[],
|
||||
size_t adb_cmd_len, char *buf, size_t buf_len,
|
||||
const char *name) {
|
||||
sc_pipe pout;
|
||||
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
|
||||
|
||||
ssize_t r = sc_pipe_read_all(pout, buf, buf_len);
|
||||
sc_pipe_close(pout);
|
||||
|
||||
if (!sc_process_check_success(pid, name, true)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static size_t
|
||||
truncate_first_line(char *data, size_t len) {
|
||||
data[len - 1] = '\0';
|
||||
char *eol = strpbrk(data, "\r\n");
|
||||
if (eol) {
|
||||
*eol = '\0';
|
||||
len = eol - data;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
char *
|
||||
adb_get_serialno(void) {
|
||||
char buf[128];
|
||||
|
||||
const char *const adb_cmd[] = {"get-serialno"};
|
||||
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
|
||||
buf, sizeof(buf), "get-serialno");
|
||||
if (r <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
truncate_first_line(buf, r);
|
||||
return strdup(buf);
|
||||
}
|
||||
42
app/src/adb.h
Normal file
42
app/src/adb.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef SC_ADB_H
|
||||
#define SC_ADB_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "util/process.h"
|
||||
|
||||
sc_pid
|
||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
||||
|
||||
sc_pid
|
||||
adb_execute_p(const char *serial, const char *const adb_cmd[],
|
||||
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
|
||||
|
||||
sc_pid
|
||||
adb_forward(const char *serial, uint16_t local_port,
|
||||
const char *device_socket_name);
|
||||
|
||||
sc_pid
|
||||
adb_forward_remove(const char *serial, uint16_t local_port);
|
||||
|
||||
sc_pid
|
||||
adb_reverse(const char *serial, const char *device_socket_name,
|
||||
uint16_t local_port);
|
||||
|
||||
sc_pid
|
||||
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||
|
||||
sc_pid
|
||||
adb_push(const char *serial, const char *local, const char *remote);
|
||||
|
||||
sc_pid
|
||||
adb_install(const char *serial, const char *local);
|
||||
|
||||
// Return the result of "adb get-serialno".
|
||||
char *
|
||||
adb_get_serialno(void);
|
||||
|
||||
#endif
|
||||
@@ -1,807 +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_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) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r == -1) {
|
||||
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';
|
||||
|
||||
return sc_adb_parse_installed_apk_version(buf);
|
||||
}
|
||||
@@ -1,133 +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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -9,6 +9,33 @@
|
||||
|
||||
#define SC_SOCKET_NAME "scrcpy"
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port);
|
||||
return sc_process_check_success_intr(intr, pid, "adb reverse");
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_reverse(struct sc_intr *intr, const char *serial) {
|
||||
sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME);
|
||||
return sc_process_check_success_intr(intr, pid, "adb reverse --remove");
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME);
|
||||
return sc_process_check_success_intr(intr, pid, "adb forward");
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port) {
|
||||
sc_pid pid = adb_forward_remove(serial, local_port);
|
||||
return sc_process_check_success_intr(intr, pid, "adb forward --remove");
|
||||
}
|
||||
|
||||
static bool
|
||||
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
|
||||
@@ -20,8 +47,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
||||
struct sc_port_range port_range) {
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
|
||||
SC_ADB_NO_STDOUT)) {
|
||||
if (!enable_tunnel_reverse(intr, serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
return false;
|
||||
}
|
||||
@@ -52,8 +78,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
||||
}
|
||||
|
||||
// failure, disable tunnel and try another port
|
||||
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
||||
SC_ADB_NO_STDOUT)) {
|
||||
if (!disable_tunnel_reverse(intr, serial)) {
|
||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||
}
|
||||
|
||||
@@ -83,8 +108,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
||||
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
|
||||
SC_ADB_NO_STDOUT)) {
|
||||
if (enable_tunnel_forward(intr, serial, port)) {
|
||||
// success
|
||||
tunnel->local_port = port;
|
||||
tunnel->enabled = true;
|
||||
@@ -149,11 +173,9 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
||||
|
||||
bool ret;
|
||||
if (tunnel->forward) {
|
||||
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
|
||||
SC_ADB_NO_STDOUT);
|
||||
ret = disable_tunnel_forward(intr, serial, tunnel->local_port);
|
||||
} else {
|
||||
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
||||
SC_ADB_NO_STDOUT);
|
||||
ret = disable_tunnel_reverse(intr, serial);
|
||||
|
||||
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
||||
if (!net_close(tunnel->server_socket)) {
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "aoa_hid.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
||||
#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;
|
||||
char *buffer = malloc(buffer_size);
|
||||
if (!buffer) {
|
||||
LOG_OOM();
|
||||
return;
|
||||
}
|
||||
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->buffer = buffer;
|
||||
hid_event->size = buffer_size;
|
||||
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
|
||||
hid_event->delay = 0;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -45,9 +43,82 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) {
|
||||
free(hid_event->buffer);
|
||||
}
|
||||
|
||||
static inline void
|
||||
log_libusb_error(enum libusb_error errcode) {
|
||||
LOGW("libusb error: %s", libusb_strerror(errcode));
|
||||
}
|
||||
|
||||
static bool
|
||||
accept_device(libusb_device *device, const char *serial) {
|
||||
// do not log any USB error in this function, it is expected that many USB
|
||||
// devices available on the computer have permission restrictions
|
||||
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(device, &desc);
|
||||
|
||||
if (!desc.iSerialNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
libusb_device_handle *handle;
|
||||
int result = libusb_open(device, &handle);
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[128];
|
||||
result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
|
||||
(unsigned char *) buffer,
|
||||
sizeof(buffer));
|
||||
libusb_close(handle);
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer[sizeof(buffer) - 1] = '\0'; // just in case
|
||||
|
||||
// accept the device if its serial matches
|
||||
return !strcmp(buffer, serial);
|
||||
}
|
||||
|
||||
static libusb_device *
|
||||
sc_aoa_find_usb_device(const char *serial) {
|
||||
if (!serial) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
libusb_device **list;
|
||||
libusb_device *result = NULL;
|
||||
ssize_t count = libusb_get_device_list(NULL, &list);
|
||||
if (count < 0) {
|
||||
log_libusb_error((enum libusb_error) count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (size_t) count; ++i) {
|
||||
libusb_device *device = list[i];
|
||||
|
||||
if (accept_device(device, serial)) {
|
||||
result = libusb_ref_device(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
||||
int result = libusb_open(device, handle);
|
||||
if (result < 0) {
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||
struct sc_acksync *acksync) {
|
||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||
cbuf_init(&aoa->queue);
|
||||
|
||||
if (!sc_mutex_init(&aoa->mutex)) {
|
||||
@@ -59,9 +130,31 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||
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->acksync = acksync;
|
||||
aoa->usb = usb;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -74,6 +167,9 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
|
||||
sc_hid_event_destroy(&event);
|
||||
}
|
||||
|
||||
libusb_close(aoa->usb_handle);
|
||||
libusb_unref_device(aoa->usb_device);
|
||||
libusb_exit(aoa->usb_context);
|
||||
sc_cond_destroy(&aoa->event_cond);
|
||||
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;
|
||||
unsigned char *buffer = NULL;
|
||||
uint16_t length = 0;
|
||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||
request, value, index, buffer, length,
|
||||
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||
value, index, buffer, length,
|
||||
DEFAULT_TIMEOUT);
|
||||
if (result < 0) {
|
||||
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result));
|
||||
sc_usb_check_disconnected(aoa->usb, result);
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
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
|
||||
unsigned char *buffer = (unsigned char *) report_desc;
|
||||
uint16_t length = report_desc_size;
|
||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||
request, value, index, buffer, length,
|
||||
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||
value, index, buffer, length,
|
||||
DEFAULT_TIMEOUT);
|
||||
if (result < 0) {
|
||||
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result));
|
||||
sc_usb_check_disconnected(aoa->usb, result);
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
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;
|
||||
unsigned char *buffer = event->buffer;
|
||||
uint16_t length = event->size;
|
||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||
request, value, index, buffer, length,
|
||||
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||
value, index, buffer, length,
|
||||
DEFAULT_TIMEOUT);
|
||||
if (result < 0) {
|
||||
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result));
|
||||
sc_usb_check_disconnected(aoa->usb, result);
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,12 +286,11 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
|
||||
uint16_t index = 0;
|
||||
unsigned char *buffer = NULL;
|
||||
uint16_t length = 0;
|
||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||
request, value, index, buffer, length,
|
||||
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||
value, index, buffer, length,
|
||||
DEFAULT_TIMEOUT);
|
||||
if (result < 0) {
|
||||
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result));
|
||||
sc_usb_check_disconnected(aoa->usb, result);
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -240,33 +332,23 @@ run_aoa_thread(void *data) {
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
|
||||
uint64_t ack_to_wait = event.ack_to_wait;
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
||||
|
||||
// If some events have ack_to_wait set, then sc_aoa must have been
|
||||
// initialized with a non NULL acksync
|
||||
assert(aoa->acksync);
|
||||
|
||||
// Do not block the loop indefinitely if the ack never comes (it
|
||||
// should never happen)
|
||||
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
||||
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);
|
||||
assert(event.delay >= 0);
|
||||
if (event.delay) {
|
||||
// Wait during the specified delay before injecting the HID event
|
||||
sc_tick deadline = sc_tick_now() + event.delay;
|
||||
bool timed_out = false;
|
||||
while (!aoa->stopped && !timed_out) {
|
||||
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
|
||||
deadline);
|
||||
}
|
||||
if (aoa->stopped) {
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
bool ok = sc_aoa_send_hid_event(aoa, &event);
|
||||
sc_hid_event_destroy(&event);
|
||||
if (!ok) {
|
||||
@@ -280,9 +362,9 @@ bool
|
||||
sc_aoa_start(struct sc_aoa *aoa) {
|
||||
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) {
|
||||
LOGE("Could not start AOA thread");
|
||||
LOGC("Could not start AOA thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,10 +377,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
|
||||
aoa->stopped = true;
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
if (aoa->acksync) {
|
||||
sc_acksync_interrupt(aoa->acksync);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include "usb.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
@@ -16,7 +14,7 @@ struct sc_hid_event {
|
||||
uint16_t accessory_id;
|
||||
unsigned char *buffer;
|
||||
uint16_t size;
|
||||
uint64_t ack_to_wait;
|
||||
sc_tick delay;
|
||||
};
|
||||
|
||||
// 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_aoa {
|
||||
struct sc_usb *usb;
|
||||
libusb_context *usb_context;
|
||||
libusb_device *usb_device;
|
||||
libusb_device_handle *usb_handle;
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond event_cond;
|
||||
bool stopped;
|
||||
struct sc_hid_event_queue queue;
|
||||
|
||||
struct sc_acksync *acksync;
|
||||
};
|
||||
|
||||
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
|
||||
sc_aoa_destroy(struct sc_aoa *aoa);
|
||||
505
app/src/cli.c
505
app/src/cli.c
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "options.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/str.h"
|
||||
#include "util/strbuf.h"
|
||||
#include "util/term.h"
|
||||
@@ -47,16 +46,6 @@
|
||||
#define OPT_V4L2_SINK 1027
|
||||
#define OPT_DISPLAY_BUFFER 1028
|
||||
#define OPT_V4L2_BUFFER 1029
|
||||
#define OPT_TUNNEL_HOST 1030
|
||||
#define OPT_TUNNEL_PORT 1031
|
||||
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
||||
#define OPT_TCPIP 1033
|
||||
#define OPT_RAW_KEY_EVENTS 1034
|
||||
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
|
||||
#define OPT_OTG 1036
|
||||
#define OPT_NO_CLEANUP 1037
|
||||
#define OPT_PRINT_FPS 1038
|
||||
#define OPT_NO_POWER_ON 1039
|
||||
|
||||
struct sc_option {
|
||||
char shortopt;
|
||||
@@ -76,16 +65,6 @@ struct sc_shortcut {
|
||||
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 {
|
||||
char *optstring;
|
||||
struct option *longopts;
|
||||
@@ -124,13 +103,7 @@ static const struct sc_option options[] = {
|
||||
.text = "Crop the device screen on the server.\n"
|
||||
"The values are expressed in the device natural orientation "
|
||||
"(typically, portrait for a phone, landscape for a tablet). "
|
||||
"Any --max-size value is computed 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).",
|
||||
"Any --max-size value is cmoputed on the cropped size.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_DISABLE_SCREENSAVER,
|
||||
@@ -155,12 +128,6 @@ static const struct sc_option options[] = {
|
||||
"This increases latency to compensate for jitter.\n"
|
||||
"Default is 0 (no buffering).",
|
||||
},
|
||||
{
|
||||
.shortopt = 'e',
|
||||
.longopt = "select-tcpip",
|
||||
.text = "Use TCP/IP device (if there is exactly one, like adb -e).\n"
|
||||
"Also see -d (--select-usb).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_ENCODER_NAME,
|
||||
.longopt = "encoder",
|
||||
@@ -192,15 +159,8 @@ static const struct sc_option options[] = {
|
||||
"It provides a better experience for IME users, and allows to "
|
||||
"generate non-ASCII characters, contrary to the default "
|
||||
"injection method.\n"
|
||||
"It may only work over USB.\n"
|
||||
"The keyboard layout must be configured (once and for all) on "
|
||||
"the device, via Settings -> System -> Languages and input -> "
|
||||
"Physical keyboard. This settings page can be started "
|
||||
"directly: `adb shell am start -a "
|
||||
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"However, the option is only available when the HID keyboard "
|
||||
"is enabled (or a physical keyboard is connected).\n"
|
||||
"Also see --hid-mouse.",
|
||||
"It may only work over USB, and is currently only supported "
|
||||
"on Linux.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
@@ -236,17 +196,6 @@ static const struct sc_option options[] = {
|
||||
.text = "Limit the frame rate of screen capture (officially supported "
|
||||
"since Android 10, but may work on earlier versions).",
|
||||
},
|
||||
{
|
||||
.shortopt = 'M',
|
||||
.longopt = "hid-mouse",
|
||||
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
|
||||
"In this mode, the computer mouse is captured to control the "
|
||||
"device directly (relative mouse mode).\n"
|
||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||
"control of the mouse back to the computer.\n"
|
||||
"It may only work over USB.\n"
|
||||
"Also see --hid-keyboard.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'm',
|
||||
.longopt = "max-size",
|
||||
@@ -256,30 +205,6 @@ static const struct sc_option options[] = {
|
||||
"is preserved.\n"
|
||||
"Default is 0 (unlimited).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_CLEANUP,
|
||||
.longopt = "no-cleanup",
|
||||
.text = "By default, scrcpy removes the server binary from the device "
|
||||
"and restores the device state (show touches, stay awake and "
|
||||
"power mode) on exit.\n"
|
||||
"This option disables this cleanup."
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
|
||||
.longopt = "no-clipboard-autosync",
|
||||
.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',
|
||||
.longopt = "no-control",
|
||||
@@ -288,8 +213,11 @@ static const struct sc_option options[] = {
|
||||
{
|
||||
.shortopt = 'N',
|
||||
.longopt = "no-display",
|
||||
.text = "Do not display device (only when screen recording or V4L2 "
|
||||
"sink is enabled).",
|
||||
.text = "Do not display device (only when screen recording "
|
||||
#ifdef HAVE_V4L2
|
||||
"or V4L2 sink "
|
||||
#endif
|
||||
"is enabled).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_KEY_REPEAT,
|
||||
@@ -303,26 +231,6 @@ static const struct sc_option options[] = {
|
||||
"mipmaps are automatically generated to improve downscaling "
|
||||
"quality. This option disables the generation of mipmaps.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_POWER_ON,
|
||||
.longopt = "no-power-on",
|
||||
.text = "Do not power on the device on start.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_OTG,
|
||||
.longopt = "otg",
|
||||
.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',
|
||||
.longopt = "port",
|
||||
@@ -339,18 +247,12 @@ static const struct sc_option options[] = {
|
||||
{
|
||||
.longopt_id = OPT_PREFER_TEXT,
|
||||
.longopt = "prefer-text",
|
||||
.text = "Inject alpha characters and space as text events instead of "
|
||||
.text = "Inject alpha characters and space as text events instead of"
|
||||
"key events.\n"
|
||||
"This avoids issues when combining multiple keys to enter a "
|
||||
"special character, but breaks the expected behavior of alpha "
|
||||
"keys in games (typically WASD).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_PRINT_FPS,
|
||||
.longopt = "print-fps",
|
||||
.text = "Start FPS counter, to print framerate logs to the console. "
|
||||
"It can be started or stopped at any time with MOD+i.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_PUSH_TARGET,
|
||||
.longopt = "push-target",
|
||||
@@ -359,11 +261,6 @@ static const struct sc_option options[] = {
|
||||
"drag & drop. It is passed as is to \"adb push\".\n"
|
||||
"Default is \"/sdcard/Download/\".",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_RAW_KEY_EVENTS,
|
||||
.longopt = "raw-key-events",
|
||||
.text = "Inject key events for all input keys, and ignore text events."
|
||||
},
|
||||
{
|
||||
.shortopt = 'r',
|
||||
.longopt = "record",
|
||||
@@ -433,47 +330,14 @@ static const struct sc_option options[] = {
|
||||
"on exit.\n"
|
||||
"It only shows physical touches (not clicks from scrcpy).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_TCPIP,
|
||||
.longopt = "tcpip",
|
||||
.argdesc = "ip[:port]",
|
||||
.optional_arg = true,
|
||||
.text = "Configure and reconnect the device over TCP/IP.\n"
|
||||
"If a destination address is provided, then scrcpy connects to "
|
||||
"this address before starting. The device must listen on the "
|
||||
"given TCP port (default is 5555).\n"
|
||||
"If no destination address is provided, then scrcpy attempts "
|
||||
"to find the IP address of the current device (typically "
|
||||
"connected over USB), enables TCP/IP mode, then connects to "
|
||||
"this address before starting.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_TUNNEL_HOST,
|
||||
.longopt = "tunnel-host",
|
||||
.argdesc = "ip",
|
||||
.text = "Set the IP address of the adb tunnel to reach the scrcpy "
|
||||
"server. This option automatically enables "
|
||||
"--force-adb-forward.\n"
|
||||
"Default is localhost.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_TUNNEL_PORT,
|
||||
.longopt = "tunnel-port",
|
||||
.argdesc = "port",
|
||||
.text = "Set the TCP port of the adb tunnel to reach the scrcpy "
|
||||
"server. This option automatically enables "
|
||||
"--force-adb-forward.\n"
|
||||
"Default is 0 (not forced): the local port used for "
|
||||
"establishing the tunnel will be used.",
|
||||
},
|
||||
#ifdef HAVE_V4L2
|
||||
{
|
||||
.longopt_id = OPT_V4L2_SINK,
|
||||
.longopt = "v4l2-sink",
|
||||
.argdesc = "/dev/videoN",
|
||||
.text = "Output to v4l2loopback device.\n"
|
||||
"It requires to lock the video orientation (see "
|
||||
"--lock-video-orientation).\n"
|
||||
"This feature is only available on Linux.",
|
||||
"--lock-video-orientation).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_V4L2_BUFFER,
|
||||
@@ -483,9 +347,9 @@ static const struct sc_option options[] = {
|
||||
"frames. This increases latency to compensate for jitter.\n"
|
||||
"This option is similar to --display-buffer, but specific to "
|
||||
"V4L2 sink.\n"
|
||||
"Default is 0 (no buffering).\n"
|
||||
"This option is only available on Linux.",
|
||||
"Default is 0 (no buffering).",
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.shortopt = 'V',
|
||||
.longopt = "verbosity",
|
||||
@@ -583,7 +447,7 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.text = "Click on BACK",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+s", "4th-click" },
|
||||
.shortcuts = { "MOD+s" },
|
||||
.text = "Click on APP_SWITCH",
|
||||
},
|
||||
{
|
||||
@@ -619,7 +483,7 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.text = "Rotate device screen",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+n", "5th-click" },
|
||||
.shortcuts = { "MOD+n" },
|
||||
.text = "Expand notification panel",
|
||||
},
|
||||
{
|
||||
@@ -661,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 *
|
||||
sc_getopt_adapter_create_optstring(void) {
|
||||
struct sc_strbuf buf;
|
||||
@@ -734,17 +563,12 @@ sc_getopt_adapter_create_longopts(void) {
|
||||
struct option *longopts =
|
||||
malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts));
|
||||
if (!longopts) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t out_idx = 0;
|
||||
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
|
||||
const struct sc_option *in = &options[i];
|
||||
|
||||
// If longopt_id is set, then longopt must be set
|
||||
assert(!in->longopt_id || in->longopt);
|
||||
|
||||
if (!in->longopt) {
|
||||
// The longopts array must only contain long options
|
||||
continue;
|
||||
@@ -789,7 +613,7 @@ sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) {
|
||||
@@ -847,12 +671,12 @@ print_option_usage_header(const struct sc_option *opt) {
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n %s\n", buf.s);
|
||||
fprintf(stderr, "\n %s\n", buf.s);
|
||||
free(buf.s);
|
||||
return;
|
||||
|
||||
error:
|
||||
printf("<ERROR>\n");
|
||||
fprintf(stderr, "<ERROR>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -868,11 +692,11 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
|
||||
|
||||
char *text = sc_str_wrap_lines(opt->text, cols, 8);
|
||||
if (!text) {
|
||||
printf("<ERROR>\n");
|
||||
fprintf(stderr, "<ERROR>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s\n", text);
|
||||
fprintf(stderr, "%s\n", text);
|
||||
free(text);
|
||||
}
|
||||
|
||||
@@ -883,11 +707,11 @@ print_shortcuts_intro(unsigned cols) {
|
||||
"(left) Alt or (left) Super, but it can be configured by "
|
||||
"--shortcut-mod (see above).", cols, 4);
|
||||
if (!intro) {
|
||||
printf("<ERROR>\n");
|
||||
fprintf(stderr, "<ERROR>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("\n%s\n", intro);
|
||||
fprintf(stderr, "%s\n", intro);
|
||||
free(intro);
|
||||
}
|
||||
|
||||
@@ -897,57 +721,21 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
|
||||
assert(shortcut->shortcuts[0]); // At least one shortcut
|
||||
assert(shortcut->text);
|
||||
|
||||
printf("\n");
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
unsigned i = 0;
|
||||
while (shortcut->shortcuts[i]) {
|
||||
printf(" %s\n", shortcut->shortcuts[i]);
|
||||
fprintf(stderr, " %s\n", shortcut->shortcuts[i]);
|
||||
++i;
|
||||
};
|
||||
|
||||
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
|
||||
if (!text) {
|
||||
printf("<ERROR>\n");
|
||||
fprintf(stderr, "<ERROR>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s\n", 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);
|
||||
fprintf(stderr, "%s\n", text);
|
||||
free(text);
|
||||
}
|
||||
|
||||
@@ -971,29 +759,18 @@ scrcpy_print_usage(const char *arg0) {
|
||||
}
|
||||
}
|
||||
|
||||
printf("Usage: %s [options]\n\n"
|
||||
"Options:\n", arg0);
|
||||
fprintf(stderr, "Usage: %s [options]\n\n"
|
||||
"Options:\n", arg0);
|
||||
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
|
||||
print_option_usage(&options[i], cols);
|
||||
}
|
||||
|
||||
// Print shortcuts section
|
||||
printf("\nShortcuts:\n");
|
||||
fprintf(stderr, "\nShortcuts:\n\n");
|
||||
print_shortcuts_intro(cols);
|
||||
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
|
||||
print_shortcut(&shortcuts[i], cols);
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1236,7 +1013,7 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
|
||||
}
|
||||
|
||||
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
|
||||
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
|
||||
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
|
||||
static unsigned
|
||||
parse_shortcut_mods_item(const char *item, size_t len) {
|
||||
unsigned mod = 0;
|
||||
@@ -1254,17 +1031,17 @@ parse_shortcut_mods_item(const char *item, size_t len) {
|
||||
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
|
||||
|
||||
if (STREQ("lctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LCTRL;
|
||||
mod |= SC_MOD_LCTRL;
|
||||
} else if (STREQ("rctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RCTRL;
|
||||
mod |= SC_MOD_RCTRL;
|
||||
} else if (STREQ("lalt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LALT;
|
||||
mod |= SC_MOD_LALT;
|
||||
} else if (STREQ("ralt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RALT;
|
||||
mod |= SC_MOD_RALT;
|
||||
} else if (STREQ("lsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LSUPER;
|
||||
mod |= SC_MOD_LSUPER;
|
||||
} else if (STREQ("rsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RSUPER;
|
||||
mod |= SC_MOD_RSUPER;
|
||||
} else {
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
@@ -1346,21 +1123,6 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_ip(const char *optarg, uint32_t *ipv4) {
|
||||
return net_parse_ipv4(optarg, ipv4);
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_port(const char *optarg, uint16_t *port) {
|
||||
long value;
|
||||
if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) {
|
||||
return false;
|
||||
}
|
||||
*port = (uint16_t) value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum sc_record_format
|
||||
guess_record_format(const char *filename) {
|
||||
size_t len = strlen(filename);
|
||||
@@ -1400,12 +1162,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
opts->select_usb = true;
|
||||
break;
|
||||
case 'e':
|
||||
opts->select_tcpip = true;
|
||||
break;
|
||||
case 'f':
|
||||
opts->fullscreen = true;
|
||||
break;
|
||||
@@ -1421,13 +1177,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
args->help = true;
|
||||
break;
|
||||
case 'K':
|
||||
#ifdef HAVE_USB
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
|
||||
break;
|
||||
#else
|
||||
LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
|
||||
return false;
|
||||
#endif
|
||||
case OPT_MAX_FPS:
|
||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
||||
return false;
|
||||
@@ -1438,30 +1189,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
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:
|
||||
if (!parse_lock_video_orientation(optarg,
|
||||
&opts->lock_video_orientation)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_TUNNEL_HOST:
|
||||
if (!parse_ip(optarg, &opts->tunnel_host)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_TUNNEL_PORT:
|
||||
if (!parse_port(optarg, &opts->tunnel_port)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
opts->control = false;
|
||||
break;
|
||||
@@ -1533,18 +1266,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
opts->push_target = optarg;
|
||||
break;
|
||||
case OPT_PREFER_TEXT:
|
||||
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
|
||||
LOGE("--prefer-text is incompatible with --raw-key-events");
|
||||
return false;
|
||||
}
|
||||
opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT;
|
||||
break;
|
||||
case OPT_RAW_KEY_EVENTS:
|
||||
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
|
||||
LOGE("--prefer-text is incompatible with --raw-key-events");
|
||||
return false;
|
||||
}
|
||||
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
|
||||
opts->prefer_text = true;
|
||||
break;
|
||||
case OPT_ROTATION:
|
||||
if (!parse_rotation(optarg, &opts->rotation)) {
|
||||
@@ -1591,51 +1313,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_NO_CLIPBOARD_AUTOSYNC:
|
||||
opts->clipboard_autosync = false;
|
||||
break;
|
||||
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_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
|
||||
case OPT_V4L2_SINK:
|
||||
opts->v4l2_device = optarg;
|
||||
break;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-sink) is disabled (or unsupported on this "
|
||||
"platform).");
|
||||
return false;
|
||||
#endif
|
||||
case OPT_V4L2_BUFFER:
|
||||
#ifdef HAVE_V4L2
|
||||
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
@@ -1643,28 +1329,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
|
||||
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
||||
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
||||
@@ -1672,18 +1336,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->v4l2_device) {
|
||||
if (opts->lock_video_orientation ==
|
||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
LOGI("Video orientation is locked for v4l2 sink. "
|
||||
"See --lock-video-orientation.");
|
||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||
}
|
||||
|
||||
// V4L2 could not handle size change.
|
||||
// Do not log because downsizing on error is the default behavior,
|
||||
// not an explicit request from the user.
|
||||
opts->downsize_on_error = false;
|
||||
if (opts->v4l2_device && opts->lock_video_orientation
|
||||
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
LOGI("Video orientation is locked for v4l2 sink. "
|
||||
"See --lock-video-orientation.");
|
||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||
}
|
||||
|
||||
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
||||
@@ -1697,10 +1354,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
||||
LOGI("Tunnel host/port is set, "
|
||||
"--force-adb-forward automatically enabled.");
|
||||
opts->force_adb_forward = true;
|
||||
int index = optind;
|
||||
if (index < argc) {
|
||||
LOGE("Unexpected additional argument: %s", argv[index]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->record_format && !opts->record_filename) {
|
||||
@@ -1718,73 +1375,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts->control) {
|
||||
if (opts->turn_screen_off) {
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
if (opts->stay_awake) {
|
||||
LOGE("Could not request to stay awake if control is disabled");
|
||||
return false;
|
||||
}
|
||||
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).");
|
||||
if (!opts->control && opts->turn_screen_off) {
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (opts->otg) {
|
||||
// OTG mode is compatible with only very few options.
|
||||
// Only report obvious errors.
|
||||
if (opts->record_filename) {
|
||||
LOGE("OTG mode: could not record");
|
||||
return false;
|
||||
}
|
||||
if (opts->turn_screen_off) {
|
||||
LOGE("OTG mode: could not turn screen off");
|
||||
return false;
|
||||
}
|
||||
if (opts->stay_awake) {
|
||||
LOGE("OTG mode: could not stay awake");
|
||||
return false;
|
||||
}
|
||||
if (opts->show_touches) {
|
||||
LOGE("OTG mode: could not request to show touches");
|
||||
return false;
|
||||
}
|
||||
if (opts->power_off_on_close) {
|
||||
LOGE("OTG mode: could not request power off on close");
|
||||
return false;
|
||||
}
|
||||
if (opts->display_id) {
|
||||
LOGE("OTG mode: could not select display");
|
||||
return false;
|
||||
}
|
||||
# ifdef HAVE_V4L2
|
||||
if (opts->v4l2_device) {
|
||||
LOGE("OTG mode: could not sink to V4L2 device");
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
if (!opts->control && opts->stay_awake) {
|
||||
LOGE("Could not request to stay awake if control is disabled");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_COMMON_H
|
||||
#define SC_COMMON_H
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
@@ -7,7 +7,6 @@
|
||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
|
||||
|
||||
#define container_of(ptr, type, member) \
|
||||
((type *) (((char *) (ptr)) - offsetof(type, member)))
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef HAVE_STRDUP
|
||||
char *strdup(const char *s) {
|
||||
size_t size = strlen(s) + 1;
|
||||
@@ -18,36 +12,3 @@ char *strdup(const char *s) {
|
||||
return dup;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_ASPRINTF
|
||||
int asprintf(char **strp, const char *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int ret = vasprintf(strp, fmt, va);
|
||||
va_end(va);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_VASPRINTF
|
||||
int vasprintf(char **strp, const char *fmt, va_list ap) {
|
||||
va_list va;
|
||||
va_copy(va, ap);
|
||||
int len = vsnprintf(NULL, 0, fmt, va);
|
||||
va_end(va);
|
||||
|
||||
char *str = malloc(len + 1);
|
||||
if (!str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_copy(va, ap);
|
||||
int len2 = vsnprintf(str, len + 1, fmt, va);
|
||||
(void) len2;
|
||||
assert(len == len2);
|
||||
va_end(va);
|
||||
|
||||
*strp = str;
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
#ifndef SC_COMPAT_H
|
||||
#define SC_COMPAT_H
|
||||
#ifndef 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 <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:
|
||||
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
||||
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
||||
@@ -37,6 +34,15 @@
|
||||
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
||||
#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)
|
||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
@@ -51,12 +57,4 @@
|
||||
char *strdup(const char *s);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_ASPRINTF
|
||||
int asprintf(char **strp, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_VASPRINTF
|
||||
int vasprintf(char **strp, const char *fmt, va_list ap);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -41,7 +41,7 @@ static const char *const android_motionevent_action_labels[] = {
|
||||
"pointer-up",
|
||||
"hover-move",
|
||||
"scroll",
|
||||
"hover-enter",
|
||||
"hover-enter"
|
||||
"hover-exit",
|
||||
"btn-press",
|
||||
"btn-release",
|
||||
@@ -55,25 +55,19 @@ static const char *const screen_power_mode_labels[] = {
|
||||
"suspend",
|
||||
};
|
||||
|
||||
static const char *const copy_key_labels[] = {
|
||||
"none",
|
||||
"copy",
|
||||
"cut",
|
||||
};
|
||||
|
||||
static void
|
||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
||||
sc_write32be(&buf[0], position->point.x);
|
||||
sc_write32be(&buf[4], position->point.y);
|
||||
sc_write16be(&buf[8], position->screen_size.width);
|
||||
sc_write16be(&buf[10], position->screen_size.height);
|
||||
buffer_write32be(&buf[0], position->point.x);
|
||||
buffer_write32be(&buf[4], position->point.y);
|
||||
buffer_write16be(&buf[8], position->screen_size.width);
|
||||
buffer_write16be(&buf[10], position->screen_size.height);
|
||||
}
|
||||
|
||||
// write length (4 bytes) + string (non null-terminated)
|
||||
// write length (2 bytes) + string (non nul-terminated)
|
||||
static size_t
|
||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||
sc_write32be(buf, len);
|
||||
buffer_write32be(buf, len);
|
||||
memcpy(&buf[4], utf8, len);
|
||||
return 4 + len;
|
||||
}
|
||||
@@ -89,58 +83,55 @@ to_fixed_point_16(float f) {
|
||||
}
|
||||
|
||||
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;
|
||||
switch (msg->type) {
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||
buf[1] = msg->inject_keycode.action;
|
||||
sc_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||
sc_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||
return 14;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||
size_t len =
|
||||
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;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||
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);
|
||||
uint16_t pressure =
|
||||
to_fixed_point_16(msg->inject_touch_event.pressure);
|
||||
sc_write16be(&buf[22], pressure);
|
||||
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
|
||||
buffer_write16be(&buf[22], pressure);
|
||||
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
|
||||
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);
|
||||
sc_write32be(&buf[13],
|
||||
buffer_write32be(&buf[13],
|
||||
(uint32_t) msg->inject_scroll_event.hscroll);
|
||||
sc_write32be(&buf[17],
|
||||
buffer_write32be(&buf[17],
|
||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||
sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
|
||||
return 25;
|
||||
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
return 21;
|
||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
buf[1] = msg->inject_keycode.action;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
buf[1] = msg->get_clipboard.copy_key;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||
buf[9] = !!msg->set_clipboard.paste;
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||
buf[1] = !!msg->set_clipboard.paste;
|
||||
size_t len = write_string(msg->set_clipboard.text,
|
||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||
&buf[10]);
|
||||
return 10 + len;
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||
&buf[2]);
|
||||
return 2 + len;
|
||||
}
|
||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
@@ -150,20 +141,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||
}
|
||||
|
||||
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__)
|
||||
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",
|
||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
|
||||
(int) msg->inject_keycode.keycode,
|
||||
msg->inject_keycode.repeat,
|
||||
(long) msg->inject_keycode.metastate);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||
LOG_CMSG("text \"%s\"", msg->inject_text.text);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
|
||||
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
|
||||
int action = msg->inject_touch_event.action
|
||||
& AMOTION_EVENT_ACTION_MASK;
|
||||
uint64_t id = msg->inject_touch_event.pointer_id;
|
||||
@@ -179,6 +170,11 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
(long) msg->inject_touch_event.buttons);
|
||||
} else {
|
||||
// numeric pointer id
|
||||
#ifndef __WIN32
|
||||
# define PRIu64_ PRIu64
|
||||
#else
|
||||
# define PRIu64_ "I64u" // Windows...
|
||||
#endif
|
||||
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
||||
PRIi32 " pressure=%g buttons=%06lx",
|
||||
id,
|
||||
@@ -190,43 +186,40 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||
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.y,
|
||||
msg->inject_scroll_event.hscroll,
|
||||
msg->inject_scroll_event.vscroll,
|
||||
(long) msg->inject_scroll_event.buttons);
|
||||
msg->inject_scroll_event.vscroll);
|
||||
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",
|
||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
LOG_CMSG("get clipboard copy_key=%s",
|
||||
copy_key_labels[msg->get_clipboard.copy_key]);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
||||
msg->set_clipboard.sequence,
|
||||
msg->set_clipboard.paste ? "paste" : "nopaste",
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
LOG_CMSG("clipboard %s \"%s\"",
|
||||
msg->set_clipboard.paste ? "paste" : "copy",
|
||||
msg->set_clipboard.text);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
LOG_CMSG("power mode %s",
|
||||
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
LOG_CMSG("expand notification panel");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
LOG_CMSG("expand settings panel");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
LOG_CMSG("collapse panels");
|
||||
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");
|
||||
break;
|
||||
default:
|
||||
@@ -236,12 +229,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||
control_msg_destroy(struct control_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||
free(msg->inject_text.text);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
free(msg->set_clipboard.text);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_CONTROLMSG_H
|
||||
#define SC_CONTROLMSG_H
|
||||
#ifndef CONTROLMSG_H
|
||||
#define CONTROLMSG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -11,44 +11,38 @@
|
||||
#include "android/keycodes.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
|
||||
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
||||
|
||||
enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
enum control_msg_type {
|
||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
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>
|
||||
SC_SCREEN_POWER_MODE_OFF = 0,
|
||||
SC_SCREEN_POWER_MODE_NORMAL = 2,
|
||||
SCREEN_POWER_MODE_OFF = 0,
|
||||
SCREEN_POWER_MODE_NORMAL = 2,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
SC_COPY_KEY_NONE,
|
||||
SC_COPY_KEY_COPY,
|
||||
SC_COPY_KEY_CUT,
|
||||
};
|
||||
|
||||
struct sc_control_msg {
|
||||
enum sc_control_msg_type type;
|
||||
struct control_msg {
|
||||
enum control_msg_type type;
|
||||
union {
|
||||
struct {
|
||||
enum android_keyevent_action action;
|
||||
@@ -70,22 +64,17 @@ struct sc_control_msg {
|
||||
struct sc_position position;
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
enum android_motionevent_buttons buttons;
|
||||
} inject_scroll_event;
|
||||
struct {
|
||||
enum android_keyevent_action action; // action for the BACK key
|
||||
// screen may only be turned on on ACTION_DOWN
|
||||
} back_or_screen_on;
|
||||
struct {
|
||||
enum sc_copy_key copy_key;
|
||||
} get_clipboard;
|
||||
struct {
|
||||
uint64_t sequence;
|
||||
char *text; // owned, to be freed by free()
|
||||
bool paste;
|
||||
} set_clipboard;
|
||||
struct {
|
||||
enum sc_screen_power_mode mode;
|
||||
enum screen_power_mode mode;
|
||||
} set_screen_power_mode;
|
||||
};
|
||||
};
|
||||
@@ -93,12 +82,12 @@ struct sc_control_msg {
|
||||
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
||||
// return the number of bytes written
|
||||
size_t
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
|
||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||
|
||||
void
|
||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
||||
control_msg_log(const struct control_msg *msg);
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
||||
control_msg_destroy(struct control_msg *msg);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
struct sc_acksync *acksync) {
|
||||
controller_init(struct controller *controller, sc_socket control_socket) {
|
||||
cbuf_init(&controller->queue);
|
||||
|
||||
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
|
||||
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -34,23 +33,23 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
}
|
||||
|
||||
void
|
||||
sc_controller_destroy(struct sc_controller *controller) {
|
||||
controller_destroy(struct controller *controller) {
|
||||
sc_cond_destroy(&controller->msg_cond);
|
||||
sc_mutex_destroy(&controller->mutex);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
struct control_msg msg;
|
||||
while (cbuf_take(&controller->queue, &msg)) {
|
||||
sc_control_msg_destroy(&msg);
|
||||
control_msg_destroy(&msg);
|
||||
}
|
||||
|
||||
receiver_destroy(&controller->receiver);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_controller_push_msg(struct sc_controller *controller,
|
||||
const struct sc_control_msg *msg) {
|
||||
controller_push_msg(struct controller *controller,
|
||||
const struct control_msg *msg) {
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
sc_control_msg_log(msg);
|
||||
control_msg_log(msg);
|
||||
}
|
||||
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
@@ -64,10 +63,9 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
}
|
||||
|
||||
static bool
|
||||
process_msg(struct sc_controller *controller,
|
||||
const struct sc_control_msg *msg) {
|
||||
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
||||
process_msg(struct controller *controller, const struct control_msg *msg) {
|
||||
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||
size_t length = control_msg_serialize(msg, serialized_msg);
|
||||
if (!length) {
|
||||
return false;
|
||||
}
|
||||
@@ -78,7 +76,7 @@ process_msg(struct sc_controller *controller,
|
||||
|
||||
static int
|
||||
run_controller(void *data) {
|
||||
struct sc_controller *controller = data;
|
||||
struct controller *controller = data;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
@@ -90,14 +88,14 @@ run_controller(void *data) {
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
break;
|
||||
}
|
||||
struct sc_control_msg msg;
|
||||
struct control_msg msg;
|
||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
bool ok = process_msg(controller, &msg);
|
||||
sc_control_msg_destroy(&msg);
|
||||
control_msg_destroy(&msg);
|
||||
if (!ok) {
|
||||
LOGD("Could not write msg to socket");
|
||||
break;
|
||||
@@ -107,18 +105,18 @@ run_controller(void *data) {
|
||||
}
|
||||
|
||||
bool
|
||||
sc_controller_start(struct sc_controller *controller) {
|
||||
controller_start(struct controller *controller) {
|
||||
LOGD("Starting controller thread");
|
||||
|
||||
bool ok = sc_thread_create(&controller->thread, run_controller,
|
||||
"scrcpy-ctl", controller);
|
||||
"controller", controller);
|
||||
if (!ok) {
|
||||
LOGE("Could not start controller thread");
|
||||
LOGC("Could not start controller thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!receiver_start(&controller->receiver)) {
|
||||
sc_controller_stop(controller);
|
||||
controller_stop(controller);
|
||||
sc_thread_join(&controller->thread, NULL);
|
||||
return false;
|
||||
}
|
||||
@@ -127,7 +125,7 @@ sc_controller_start(struct sc_controller *controller) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_controller_stop(struct sc_controller *controller) {
|
||||
controller_stop(struct controller *controller) {
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
controller->stopped = true;
|
||||
sc_cond_signal(&controller->msg_cond);
|
||||
@@ -135,7 +133,7 @@ sc_controller_stop(struct sc_controller *controller) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_controller_join(struct sc_controller *controller) {
|
||||
controller_join(struct controller *controller) {
|
||||
sc_thread_join(&controller->thread, NULL);
|
||||
receiver_join(&controller->receiver);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_CONTROLLER_H
|
||||
#define SC_CONTROLLER_H
|
||||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -7,41 +7,39 @@
|
||||
|
||||
#include "control_msg.h"
|
||||
#include "receiver.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/net.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_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond msg_cond;
|
||||
bool stopped;
|
||||
struct sc_control_msg_queue queue;
|
||||
struct control_msg_queue queue;
|
||||
struct receiver receiver;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
struct sc_acksync *acksync);
|
||||
controller_init(struct controller *controller, sc_socket control_socket);
|
||||
|
||||
void
|
||||
sc_controller_destroy(struct sc_controller *controller);
|
||||
controller_destroy(struct controller *controller);
|
||||
|
||||
bool
|
||||
sc_controller_start(struct sc_controller *controller);
|
||||
controller_start(struct controller *controller);
|
||||
|
||||
void
|
||||
sc_controller_stop(struct sc_controller *controller);
|
||||
controller_stop(struct controller *controller);
|
||||
|
||||
void
|
||||
sc_controller_join(struct sc_controller *controller);
|
||||
controller_join(struct controller *controller);
|
||||
|
||||
bool
|
||||
sc_controller_push_msg(struct sc_controller *controller,
|
||||
const struct sc_control_msg *msg);
|
||||
controller_push_msg(struct controller *controller,
|
||||
const struct control_msg *msg);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include "util/log.h"
|
||||
|
||||
/** 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
|
||||
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
||||
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
|
||||
while (count) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[--count];
|
||||
sink->ops->close(sink);
|
||||
@@ -20,17 +20,17 @@ sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
||||
sc_decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||
decoder_close_sinks(struct decoder *decoder) {
|
||||
decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||
}
|
||||
|
||||
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) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||
if (!sink->ops->open(sink)) {
|
||||
LOGE("Could not open frame sink %d", i);
|
||||
sc_decoder_close_first_sinks(decoder, i);
|
||||
decoder_close_first_sinks(decoder, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -39,15 +39,13 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
||||
}
|
||||
|
||||
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);
|
||||
if (!decoder->codec_ctx) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not allocate decoder context");
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
||||
LOGE("Could not open codec");
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
@@ -56,13 +54,13 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
|
||||
decoder->frame = av_frame_alloc();
|
||||
if (!decoder->frame) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not create decoder frame");
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_decoder_open_sinks(decoder)) {
|
||||
if (!decoder_open_sinks(decoder)) {
|
||||
LOGE("Could not open decoder sinks");
|
||||
av_frame_free(&decoder->frame);
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
@@ -74,15 +72,15 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_decoder_close(struct sc_decoder *decoder) {
|
||||
sc_decoder_close_sinks(decoder);
|
||||
decoder_close(struct decoder *decoder) {
|
||||
decoder_close_sinks(decoder);
|
||||
av_frame_free(&decoder->frame);
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
}
|
||||
|
||||
static bool
|
||||
push_frame_to_sinks(struct 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) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||
if (!sink->ops->push(sink, frame)) {
|
||||
@@ -95,7 +93,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
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;
|
||||
if (is_config) {
|
||||
// nothing to do
|
||||
@@ -124,40 +122,39 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_open(decoder, codec);
|
||||
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||
struct decoder *decoder = DOWNCAST(sink);
|
||||
return decoder_open(decoder, codec);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
sc_decoder_close(decoder);
|
||||
decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||
struct decoder *decoder = DOWNCAST(sink);
|
||||
decoder_close(decoder);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
const AVPacket *packet) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_push(decoder, packet);
|
||||
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||
struct decoder *decoder = DOWNCAST(sink);
|
||||
return decoder_push(decoder, packet);
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder) {
|
||||
decoder_init(struct decoder *decoder) {
|
||||
decoder->sink_count = 0;
|
||||
|
||||
static const struct sc_packet_sink_ops ops = {
|
||||
.open = sc_decoder_packet_sink_open,
|
||||
.close = sc_decoder_packet_sink_close,
|
||||
.push = sc_decoder_packet_sink_push,
|
||||
.open = decoder_packet_sink_open,
|
||||
.close = decoder_packet_sink_close,
|
||||
.push = decoder_packet_sink_push,
|
||||
};
|
||||
|
||||
decoder->packet_sink.ops = &ops;
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) {
|
||||
assert(decoder->sink_count < SC_DECODER_MAX_SINKS);
|
||||
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
|
||||
assert(decoder->sink_count < DECODER_MAX_SINKS);
|
||||
assert(sink);
|
||||
assert(sink->ops);
|
||||
decoder->sinks[decoder->sink_count++] = sink;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#ifndef SC_DECODER_H
|
||||
#define SC_DECODER_H
|
||||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.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_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
||||
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
|
||||
unsigned sink_count;
|
||||
|
||||
AVCodecContext *codec_ctx;
|
||||
@@ -22,9 +21,9 @@ struct sc_decoder {
|
||||
};
|
||||
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder);
|
||||
decoder_init(struct decoder *decoder);
|
||||
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "device_msg.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -18,13 +17,13 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
msg->type = buf[0];
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
size_t clipboard_len = sc_read32be(&buf[1]);
|
||||
size_t clipboard_len = buffer_read32be(&buf[1]);
|
||||
if (clipboard_len > len - 5) {
|
||||
return 0; // not available
|
||||
}
|
||||
char *text = malloc(clipboard_len + 1);
|
||||
if (!text) {
|
||||
LOG_OOM();
|
||||
LOGW("Could not allocate text for clipboard");
|
||||
return -1;
|
||||
}
|
||||
if (clipboard_len) {
|
||||
@@ -35,11 +34,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
msg->clipboard.text = text;
|
||||
return 5 + clipboard_len;
|
||||
}
|
||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
|
||||
uint64_t sequence = sc_read64be(&buf[1]);
|
||||
msg->ack_clipboard.sequence = sequence;
|
||||
return 9;
|
||||
}
|
||||
default:
|
||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||
return -1; // error, we cannot recover
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_DEVICEMSG_H
|
||||
#define SC_DEVICEMSG_H
|
||||
#ifndef DEVICEMSG_H
|
||||
#define DEVICEMSG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
enum device_msg_type {
|
||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
|
||||
};
|
||||
|
||||
struct device_msg {
|
||||
@@ -22,9 +21,6 @@ struct device_msg {
|
||||
struct {
|
||||
char *text; // owned, to be freed by free()
|
||||
} clipboard;
|
||||
struct {
|
||||
uint64_t sequence;
|
||||
} ack_clipboard;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||
|
||||
197
app/src/file_handler.c
Normal file
197
app/src/file_handler.c
Normal file
@@ -0,0 +1,197 @@
|
||||
#include "file_handler.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "adb.h"
|
||||
#include "util/log.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) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (serial) {
|
||||
file_handler->serial = strdup(serial);
|
||||
if (!file_handler->serial) {
|
||||
LOGW("Could not strdup serial");
|
||||
sc_cond_destroy(&file_handler->event_cond);
|
||||
sc_mutex_destroy(&file_handler->mutex);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
file_handler->serial = NULL;
|
||||
}
|
||||
|
||||
// lazy initialization
|
||||
file_handler->initialized = false;
|
||||
|
||||
file_handler->stopped = false;
|
||||
file_handler->current_process = SC_PROCESS_NONE;
|
||||
|
||||
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
file_handler_destroy(struct file_handler *file_handler) {
|
||||
sc_cond_destroy(&file_handler->event_cond);
|
||||
sc_mutex_destroy(&file_handler->mutex);
|
||||
free(file_handler->serial);
|
||||
|
||||
struct file_handler_request req;
|
||||
while (cbuf_take(&file_handler->queue, &req)) {
|
||||
file_handler_request_destroy(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static sc_pid
|
||||
install_apk(const char *serial, const char *file) {
|
||||
return adb_install(serial, file);
|
||||
}
|
||||
|
||||
static sc_pid
|
||||
push_file(const char *serial, const char *file, const char *push_target) {
|
||||
return adb_push(serial, file, push_target);
|
||||
}
|
||||
|
||||
bool
|
||||
file_handler_request(struct file_handler *file_handler,
|
||||
file_handler_action_t action, char *file) {
|
||||
// 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;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&file_handler->mutex);
|
||||
file_handler->current_process = SC_PROCESS_NONE;
|
||||
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
|
||||
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
|
||||
}
|
||||
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_pid pid;
|
||||
if (req.action == ACTION_INSTALL_APK) {
|
||||
LOGI("Installing %s...", req.file);
|
||||
pid = install_apk(file_handler->serial, req.file);
|
||||
} else {
|
||||
LOGI("Pushing %s...", req.file);
|
||||
pid = push_file(file_handler->serial, req.file,
|
||||
file_handler->push_target);
|
||||
}
|
||||
file_handler->current_process = pid;
|
||||
sc_mutex_unlock(&file_handler->mutex);
|
||||
|
||||
if (req.action == ACTION_INSTALL_APK) {
|
||||
if (sc_process_check_success(pid, "adb install", false)) {
|
||||
LOGI("%s successfully installed", req.file);
|
||||
} else {
|
||||
LOGE("Failed to install %s", req.file);
|
||||
}
|
||||
} else {
|
||||
if (sc_process_check_success(pid, "adb push", false)) {
|
||||
LOGI("%s successfully pushed to %s", req.file,
|
||||
file_handler->push_target);
|
||||
} else {
|
||||
LOGE("Failed to push %s to %s", req.file,
|
||||
file_handler->push_target);
|
||||
}
|
||||
}
|
||||
|
||||
sc_mutex_lock(&file_handler->mutex);
|
||||
// Close the process (it is necessarily already terminated)
|
||||
// Execute this call with mutex locked to avoid race conditions with
|
||||
// file_handler_stop()
|
||||
sc_process_close(file_handler->current_process);
|
||||
file_handler->current_process = SC_PROCESS_NONE;
|
||||
sc_mutex_unlock(&file_handler->mutex);
|
||||
|
||||
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);
|
||||
if (file_handler->current_process != SC_PROCESS_NONE) {
|
||||
if (!sc_process_terminate(file_handler->current_process)) {
|
||||
LOGW("Could not terminate push/install process");
|
||||
}
|
||||
}
|
||||
sc_mutex_unlock(&file_handler->mutex);
|
||||
}
|
||||
|
||||
void
|
||||
file_handler_join(struct file_handler *file_handler) {
|
||||
sc_thread_join(&file_handler->thread, NULL);
|
||||
}
|
||||
58
app/src/file_handler.h
Normal file
58
app/src/file_handler.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#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"
|
||||
|
||||
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;
|
||||
sc_pid current_process;
|
||||
struct file_handler_request_queue queue;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
|
||||
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
|
||||
|
||||
bool
|
||||
sc_fps_counter_init(struct sc_fps_counter *counter) {
|
||||
fps_counter_init(struct fps_counter *counter) {
|
||||
bool ok = sc_mutex_init(&counter->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
@@ -27,26 +27,26 @@ sc_fps_counter_init(struct sc_fps_counter *counter) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_fps_counter_destroy(struct sc_fps_counter *counter) {
|
||||
fps_counter_destroy(struct fps_counter *counter) {
|
||||
sc_cond_destroy(&counter->state_cond);
|
||||
sc_mutex_destroy(&counter->mutex);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// must be called with mutex locked
|
||||
static void
|
||||
display_fps(struct sc_fps_counter *counter) {
|
||||
display_fps(struct fps_counter *counter) {
|
||||
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) {
|
||||
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
|
||||
counter->nr_skipped);
|
||||
@@ -57,7 +57,7 @@ display_fps(struct sc_fps_counter *counter) {
|
||||
|
||||
// must be called with mutex locked
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -67,13 +67,13 @@ check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
|
||||
counter->nr_skipped = 0;
|
||||
// add a multiple of the interval
|
||||
uint32_t elapsed_slices =
|
||||
(now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1;
|
||||
counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices;
|
||||
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
|
||||
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
|
||||
}
|
||||
|
||||
static int
|
||||
run_fps_counter(void *data) {
|
||||
struct sc_fps_counter *counter = data;
|
||||
struct fps_counter *counter = data;
|
||||
|
||||
sc_mutex_lock(&counter->mutex);
|
||||
while (!counter->interrupted) {
|
||||
@@ -94,9 +94,9 @@ run_fps_counter(void *data) {
|
||||
}
|
||||
|
||||
bool
|
||||
sc_fps_counter_start(struct sc_fps_counter *counter) {
|
||||
fps_counter_start(struct fps_counter *counter) {
|
||||
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_skipped = 0;
|
||||
sc_mutex_unlock(&counter->mutex);
|
||||
@@ -108,7 +108,7 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
|
||||
// same thread, no need to lock
|
||||
if (!counter->thread_started) {
|
||||
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
|
||||
"scrcpy-fps", counter);
|
||||
"fps counter", counter);
|
||||
if (!ok) {
|
||||
LOGE("Could not start FPS counter thread");
|
||||
return false;
|
||||
@@ -117,24 +117,22 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
|
||||
counter->thread_started = true;
|
||||
}
|
||||
|
||||
LOGI("FPS counter started");
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_fps_counter_stop(struct sc_fps_counter *counter) {
|
||||
fps_counter_stop(struct fps_counter *counter) {
|
||||
set_started(counter, false);
|
||||
sc_cond_signal(&counter->state_cond);
|
||||
LOGI("FPS counter stopped");
|
||||
}
|
||||
|
||||
bool
|
||||
sc_fps_counter_is_started(struct sc_fps_counter *counter) {
|
||||
fps_counter_is_started(struct fps_counter *counter) {
|
||||
return is_started(counter);
|
||||
}
|
||||
|
||||
void
|
||||
sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
|
||||
fps_counter_interrupt(struct fps_counter *counter) {
|
||||
if (!counter->thread_started) {
|
||||
return;
|
||||
}
|
||||
@@ -147,7 +145,7 @@ sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_fps_counter_join(struct sc_fps_counter *counter) {
|
||||
fps_counter_join(struct fps_counter *counter) {
|
||||
if (counter->thread_started) {
|
||||
// interrupted must be set by the thread calling join(), so no need to
|
||||
// lock for the assertion
|
||||
@@ -158,7 +156,7 @@ sc_fps_counter_join(struct sc_fps_counter *counter) {
|
||||
}
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
@@ -171,7 +169,7 @@ sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
|
||||
}
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_FPSCOUNTER_H
|
||||
#define SC_FPSCOUNTER_H
|
||||
#ifndef FPSCOUNTER_H
|
||||
#define FPSCOUNTER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "util/thread.h"
|
||||
|
||||
struct sc_fps_counter {
|
||||
struct fps_counter {
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond state_cond;
|
||||
@@ -28,32 +28,32 @@ struct sc_fps_counter {
|
||||
};
|
||||
|
||||
bool
|
||||
sc_fps_counter_init(struct sc_fps_counter *counter);
|
||||
fps_counter_init(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
sc_fps_counter_destroy(struct sc_fps_counter *counter);
|
||||
fps_counter_destroy(struct fps_counter *counter);
|
||||
|
||||
bool
|
||||
sc_fps_counter_start(struct sc_fps_counter *counter);
|
||||
fps_counter_start(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
sc_fps_counter_stop(struct sc_fps_counter *counter);
|
||||
fps_counter_stop(struct fps_counter *counter);
|
||||
|
||||
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)
|
||||
// must be called before sc_fps_counter_join()
|
||||
// must be called before fps_counter_join()
|
||||
void
|
||||
sc_fps_counter_interrupt(struct sc_fps_counter *counter);
|
||||
fps_counter_interrupt(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
sc_fps_counter_join(struct sc_fps_counter *counter);
|
||||
fps_counter_join(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter);
|
||||
fps_counter_add_rendered_frame(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter);
|
||||
fps_counter_add_skipped_frame(struct fps_counter *counter);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,13 +10,11 @@ bool
|
||||
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
|
||||
fb->pending_frame = av_frame_alloc();
|
||||
if (!fb->pending_frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
fb->tmp_frame = av_frame_alloc();
|
||||
if (!fb->tmp_frame) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&fb->pending_frame);
|
||||
return false;
|
||||
}
|
||||
@@ -50,7 +48,9 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
||||
|
||||
bool
|
||||
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
||||
bool *previous_frame_skipped) {
|
||||
bool *previous_frame_skipped) {
|
||||
sc_mutex_lock(&fb->mutex);
|
||||
|
||||
// Use a temporary frame to preserve pending_frame in case of error.
|
||||
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
||||
int r = av_frame_ref(fb->tmp_frame, frame);
|
||||
@@ -59,8 +59,6 @@ sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_mutex_lock(&fb->mutex);
|
||||
|
||||
// Now that av_frame_ref() succeeded, we can replace the previous
|
||||
// pending_frame
|
||||
swap_frames(&fb->pending_frame, &fb->tmp_frame);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "hid_keyboard.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to hid_keyboard */
|
||||
@@ -126,105 +126,31 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
0xC0
|
||||
};
|
||||
|
||||
/**
|
||||
* A keyboard HID event is 8 bytes long:
|
||||
*
|
||||
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
|
||||
* - byte 1: reserved (always 0)
|
||||
* - bytes 2 to 7: pressed keys (6 at most)
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* +---------------+
|
||||
* byte 0: |. . . . . . . .| modifiers
|
||||
* +---------------+
|
||||
* ^ ^ ^ ^ ^ ^ ^ ^
|
||||
* | | | | | | | `- left Ctrl
|
||||
* | | | | | | `--- left Shift
|
||||
* | | | | | `----- left Alt
|
||||
* | | | | `------- left Gui
|
||||
* | | | `--------- right Ctrl
|
||||
* | | `----------- right Shift
|
||||
* | `------------- right Alt
|
||||
* `--------------- right Gui
|
||||
*
|
||||
* +---------------+
|
||||
* byte 1: |0 0 0 0 0 0 0 0| reserved
|
||||
* +---------------+
|
||||
*
|
||||
* +---------------+
|
||||
* bytes 2 to 7: |. . . . . . . .| scancode of 1st key pressed
|
||||
* +---------------+
|
||||
* |. . . . . . . .| scancode of 2nd key pressed
|
||||
* +---------------+
|
||||
* |. . . . . . . .| scancode of 3rd key pressed
|
||||
* +---------------+
|
||||
* |. . . . . . . .| scancode of 4th key pressed
|
||||
* +---------------+
|
||||
* |. . . . . . . .| scancode of 5th key pressed
|
||||
* +---------------+
|
||||
* |. . . . . . . .| scancode of 6th key pressed
|
||||
* +---------------+
|
||||
*
|
||||
* If there are less than 6 keys pressed, the last items are set to 0.
|
||||
* For example, if A and W are pressed:
|
||||
*
|
||||
* +---------------+
|
||||
* bytes 2 to 7: |0 0 0 0 0 1 0 0| A is pressed (scancode = 4)
|
||||
* +---------------+
|
||||
* |0 0 0 1 1 0 1 0| W is pressed (scancode = 26)
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 0 0 0| ^
|
||||
* +---------------+ | only 2 keys are pressed, the
|
||||
* |0 0 0 0 0 0 0 0| | remaining items are set to 0
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 0| |
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 0| v
|
||||
* +---------------+
|
||||
*
|
||||
* Pressing more than 6 keys is not supported. If this happens (typically,
|
||||
* never in practice), report a "phantom state":
|
||||
*
|
||||
* +---------------+
|
||||
* bytes 2 to 7: |0 0 0 0 0 0 0 1| ^
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 1| | more than 6 keys pressed:
|
||||
* +---------------+ | the list is filled with a special
|
||||
* |0 0 0 0 0 0 0 1| | rollover error code (0x01)
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 1| |
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 1| |
|
||||
* +---------------+ |
|
||||
* |0 0 0 0 0 0 0 1| v
|
||||
* +---------------+
|
||||
*/
|
||||
|
||||
static unsigned char
|
||||
sdl_keymod_to_hid_modifiers(uint16_t mod) {
|
||||
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
|
||||
unsigned char modifiers = HID_MODIFIER_NONE;
|
||||
if (mod & SC_MOD_LCTRL) {
|
||||
if (mod & KMOD_LCTRL) {
|
||||
modifiers |= HID_MODIFIER_LEFT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_LSHIFT) {
|
||||
if (mod & KMOD_LSHIFT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_LALT) {
|
||||
if (mod & KMOD_LALT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_LGUI) {
|
||||
if (mod & KMOD_LGUI) {
|
||||
modifiers |= HID_MODIFIER_LEFT_GUI;
|
||||
}
|
||||
if (mod & SC_MOD_RCTRL) {
|
||||
if (mod & KMOD_RCTRL) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_RSHIFT) {
|
||||
if (mod & KMOD_RSHIFT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_RALT) {
|
||||
if (mod & KMOD_RALT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_RGUI) {
|
||||
if (mod & KMOD_RGUI) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_GUI;
|
||||
}
|
||||
return modifiers;
|
||||
@@ -234,7 +160,6 @@ static bool
|
||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
||||
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
||||
if (!buffer) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,15 +173,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
||||
}
|
||||
|
||||
static inline bool
|
||||
scancode_is_modifier(enum sc_scancode scancode) {
|
||||
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
|
||||
scancode_is_modifier(SDL_Scancode scancode) {
|
||||
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
struct sc_hid_event *hid_event,
|
||||
const struct sc_key_event *event) {
|
||||
enum sc_scancode scancode = event->scancode;
|
||||
const SDL_KeyboardEvent *event) {
|
||||
SDL_Scancode scancode = event->keysym.scancode;
|
||||
assert(scancode >= 0);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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,
|
||||
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
|
||||
// phantom state should be reported
|
||||
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
|
||||
// Phantom state:
|
||||
// Pantom state:
|
||||
// - Modifiers
|
||||
// - Reserved
|
||||
// - ErrorRollOver * HID_MAX_KEYS
|
||||
@@ -306,17 +231,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
|
||||
end:
|
||||
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
|
||||
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
|
||||
event->scancode, modifiers);
|
||||
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
|
||||
event->keysym.scancode, modifiers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
|
||||
bool capslock = mods_state & SC_MOD_CAPS;
|
||||
bool numlock = mods_state & SC_MOD_NUM;
|
||||
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
|
||||
bool capslock = sdl_mod & KMOD_CAPS;
|
||||
bool numlock = sdl_mod & KMOD_NUM;
|
||||
if (!capslock && !numlock) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
@@ -328,6 +253,8 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
|
||||
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
|
||||
unsigned i = 0;
|
||||
if (capslock) {
|
||||
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||
@@ -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)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could not request HID event (mod lock state)");
|
||||
LOGW("Could request HID event");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -351,8 +278,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
|
||||
|
||||
static void
|
||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event,
|
||||
uint64_t ack_to_wait) {
|
||||
const SDL_KeyboardEvent *event) {
|
||||
if (event->repeat) {
|
||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||
// just ignore key repeat here.
|
||||
@@ -367,26 +293,39 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
if (!kb->mod_lock_synchronized) {
|
||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||
// keyboard state
|
||||
if (push_mod_lock_state(kb, event->mods_state)) {
|
||||
if (push_mod_lock_state(kb, event->keysym.mod)) {
|
||||
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
|
||||
// requested. Wait until clipboard synchronization is acknowledged
|
||||
// by the server, otherwise it could paste the old clipboard
|
||||
// content.
|
||||
hid_event.ack_to_wait = ack_to_wait;
|
||||
// requested. Wait a bit so that the clipboard is set before
|
||||
// injecting Ctrl+v via HID, otherwise it would paste the old
|
||||
// clipboard content.
|
||||
hid_event.delay = SC_TICK_FROM_MS(5);
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_hid_event(kb->aoa, &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
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_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 = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
// Never forward text input via HID (all the keys are injected
|
||||
// separately)
|
||||
.process_text = NULL,
|
||||
.process_text = sc_key_processor_process_text,
|
||||
};
|
||||
|
||||
// Clipboard synchronization is requested over the control socket, while HID
|
||||
// events are sent over AOA, so it must wait for clipboard synchronization
|
||||
// to be acknowledged by the device before injecting Ctrl+v.
|
||||
kb->key_processor.async_paste = true;
|
||||
kb->key_processor.ops = &ops;
|
||||
|
||||
return true;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
@@ -32,7 +31,7 @@ get_icon_path(void) {
|
||||
char *icon_path = strdup(icon_path_env);
|
||||
#endif
|
||||
if (!icon_path) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
||||
@@ -43,7 +42,7 @@ get_icon_path(void) {
|
||||
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
||||
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
||||
if (!icon_path) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
@@ -64,7 +63,7 @@ decode_image(const char *path) {
|
||||
|
||||
AVFormatContext *ctx = avformat_alloc_context();
|
||||
if (!ctx) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate image decoder context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -86,7 +85,7 @@ decode_image(const char *path) {
|
||||
|
||||
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) {
|
||||
LOGE("Could not find image decoder");
|
||||
goto close_input;
|
||||
@@ -94,7 +93,7 @@ decode_image(const char *path) {
|
||||
|
||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (!codec_ctx) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate codec context");
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
@@ -110,13 +109,13 @@ decode_image(const char *path) {
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate frame");
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
if (!packet) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate packet");
|
||||
av_frame_free(&frame);
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_ICON_H
|
||||
#define SC_ICON_H
|
||||
#ifndef ICON_H
|
||||
#define ICON_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
@@ -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
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_INPUTMANAGER_H
|
||||
#define SC_INPUTMANAGER_H
|
||||
#ifndef INPUTMANAGER_H
|
||||
#define INPUTMANAGER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -8,23 +8,22 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "file_pusher.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "screen.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
struct sc_input_manager {
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_screen *screen;
|
||||
struct input_manager {
|
||||
struct controller *controller;
|
||||
struct screen *screen;
|
||||
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
|
||||
struct {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
@@ -39,28 +38,15 @@ struct sc_input_manager {
|
||||
unsigned key_repeat;
|
||||
SDL_Keycode last_keycode;
|
||||
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
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params);
|
||||
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||
struct screen *screen, struct sc_key_processor *kp,
|
||||
struct sc_mouse_processor *mp,
|
||||
const struct scrcpy_options *options);
|
||||
|
||||
void
|
||||
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
|
||||
bool
|
||||
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,191 +1,108 @@
|
||||
#include "keyboard_inject.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "input_events.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to sc_keyboard_inject */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||
#define DOWNCAST(KP) \
|
||||
container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||
|
||||
static enum android_keyevent_action
|
||||
convert_keycode_action(enum sc_action action) {
|
||||
if (action == SC_ACTION_DOWN) {
|
||||
return AKEY_EVENT_ACTION_DOWN;
|
||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||
#define FAIL default: return false
|
||||
static bool
|
||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
||||
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
|
||||
FAIL;
|
||||
}
|
||||
assert(action == SC_ACTION_UP);
|
||||
return AKEY_EVENT_ACTION_UP;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
|
||||
enum sc_key_inject_mode key_inject_mode) {
|
||||
// Navigation keys and ENTER.
|
||||
// Used in all modes.
|
||||
static const struct sc_intmap_entry special_keys[] = {
|
||||
{SC_KEYCODE_RETURN, AKEYCODE_ENTER},
|
||||
{SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
|
||||
{SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE},
|
||||
{SC_KEYCODE_BACKSPACE, AKEYCODE_DEL},
|
||||
{SC_KEYCODE_TAB, AKEYCODE_TAB},
|
||||
{SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP},
|
||||
{SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL},
|
||||
{SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME},
|
||||
{SC_KEYCODE_END, AKEYCODE_MOVE_END},
|
||||
{SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN},
|
||||
{SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT},
|
||||
{SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT},
|
||||
{SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN},
|
||||
{SC_KEYCODE_UP, AKEYCODE_DPAD_UP},
|
||||
{SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT},
|
||||
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
|
||||
{SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
|
||||
{SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
|
||||
};
|
||||
|
||||
// Numpad navigation keys.
|
||||
// Used in all modes, when NumLock and Shift are disabled.
|
||||
static const struct sc_intmap_entry kp_nav_keys[] = {
|
||||
{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;
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
bool prefer_text) {
|
||||
switch (from) {
|
||||
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
|
||||
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
|
||||
MAP(SDLK_TAB, AKEYCODE_TAB);
|
||||
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
|
||||
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
|
||||
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
|
||||
MAP(SDLK_END, AKEYCODE_MOVE_END);
|
||||
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
|
||||
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
|
||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
|
||||
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||
}
|
||||
|
||||
if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) {
|
||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||
// Handle Numpad events when Num Lock is disabled
|
||||
// If SHIFT is pressed, a text event will be sent instead
|
||||
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
switch(from) {
|
||||
MAP(SDLK_KP_0, AKEYCODE_INSERT);
|
||||
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
|
||||
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
|
||||
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
|
||||
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
|
||||
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
|
||||
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
|
||||
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
|
||||
}
|
||||
}
|
||||
|
||||
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT &&
|
||||
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
|
||||
if (prefer_text && !(mod & KMOD_CTRL)) {
|
||||
// do not forward alpha and space key events (unless Ctrl is pressed)
|
||||
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;
|
||||
}
|
||||
|
||||
// if ALT and META are not pressed, also handle letters and space
|
||||
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
switch (from) {
|
||||
MAP(SDLK_a, AKEYCODE_A);
|
||||
MAP(SDLK_b, AKEYCODE_B);
|
||||
MAP(SDLK_c, AKEYCODE_C);
|
||||
MAP(SDLK_d, AKEYCODE_D);
|
||||
MAP(SDLK_e, AKEYCODE_E);
|
||||
MAP(SDLK_f, AKEYCODE_F);
|
||||
MAP(SDLK_g, AKEYCODE_G);
|
||||
MAP(SDLK_h, AKEYCODE_H);
|
||||
MAP(SDLK_i, AKEYCODE_I);
|
||||
MAP(SDLK_j, AKEYCODE_J);
|
||||
MAP(SDLK_k, AKEYCODE_K);
|
||||
MAP(SDLK_l, AKEYCODE_L);
|
||||
MAP(SDLK_m, AKEYCODE_M);
|
||||
MAP(SDLK_n, AKEYCODE_N);
|
||||
MAP(SDLK_o, AKEYCODE_O);
|
||||
MAP(SDLK_p, AKEYCODE_P);
|
||||
MAP(SDLK_q, AKEYCODE_Q);
|
||||
MAP(SDLK_r, AKEYCODE_R);
|
||||
MAP(SDLK_s, AKEYCODE_S);
|
||||
MAP(SDLK_t, AKEYCODE_T);
|
||||
MAP(SDLK_u, AKEYCODE_U);
|
||||
MAP(SDLK_v, AKEYCODE_V);
|
||||
MAP(SDLK_w, AKEYCODE_W);
|
||||
MAP(SDLK_x, AKEYCODE_X);
|
||||
MAP(SDLK_y, AKEYCODE_Y);
|
||||
MAP(SDLK_z, AKEYCODE_Z);
|
||||
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
||||
FAIL;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -208,69 +125,70 @@ autocomplete_metastate(enum android_metastate metastate) {
|
||||
}
|
||||
|
||||
static enum android_metastate
|
||||
convert_meta_state(uint16_t mod) {
|
||||
convert_meta_state(SDL_Keymod mod) {
|
||||
enum android_metastate metastate = 0;
|
||||
if (mod & SC_MOD_LSHIFT) {
|
||||
if (mod & KMOD_LSHIFT) {
|
||||
metastate |= AMETA_SHIFT_LEFT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_RSHIFT) {
|
||||
if (mod & KMOD_RSHIFT) {
|
||||
metastate |= AMETA_SHIFT_RIGHT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_LCTRL) {
|
||||
if (mod & KMOD_LCTRL) {
|
||||
metastate |= AMETA_CTRL_LEFT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_RCTRL) {
|
||||
if (mod & KMOD_RCTRL) {
|
||||
metastate |= AMETA_CTRL_RIGHT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_LALT) {
|
||||
if (mod & KMOD_LALT) {
|
||||
metastate |= AMETA_ALT_LEFT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_RALT) {
|
||||
if (mod & KMOD_RALT) {
|
||||
metastate |= AMETA_ALT_RIGHT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_LGUI) { // Windows key
|
||||
if (mod & KMOD_LGUI) { // Windows key
|
||||
metastate |= AMETA_META_LEFT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_RGUI) { // Windows key
|
||||
if (mod & KMOD_RGUI) { // Windows key
|
||||
metastate |= AMETA_META_RIGHT_ON;
|
||||
}
|
||||
if (mod & SC_MOD_NUM) {
|
||||
if (mod & KMOD_NUM) {
|
||||
metastate |= AMETA_NUM_LOCK_ON;
|
||||
}
|
||||
if (mod & SC_MOD_CAPS) {
|
||||
if (mod & KMOD_CAPS) {
|
||||
metastate |= AMETA_CAPS_LOCK_ON;
|
||||
}
|
||||
if (mod & KMOD_MODE) { // Alt Gr
|
||||
// no mapping?
|
||||
}
|
||||
|
||||
// fill the dependent fields
|
||||
return autocomplete_metastate(metastate);
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
|
||||
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
|
||||
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
bool prefer_text, uint32_t repeat) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
|
||||
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
|
||||
event->mods_state, key_inject_mode)) {
|
||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
msg->inject_keycode.action = convert_keycode_action(event->action);
|
||||
msg->inject_keycode.repeat = repeat;
|
||||
msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
|
||||
uint16_t mod = from->keysym.mod;
|
||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||
prefer_text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_keycode.repeat = repeat;
|
||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event,
|
||||
uint64_t ack_to_wait) {
|
||||
// The device clipboard synchronization and the key event messages are
|
||||
// serialized, there is nothing special to do to ensure that the clipboard
|
||||
// is set before injecting Ctrl+v.
|
||||
(void) ack_to_wait;
|
||||
|
||||
const SDL_KeyboardEvent *event) {
|
||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||
|
||||
if (event->repeat) {
|
||||
@@ -282,9 +200,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
ki->repeat = 0;
|
||||
}
|
||||
|
||||
struct sc_control_msg msg;
|
||||
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
|
||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
||||
struct control_msg msg;
|
||||
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
|
||||
if (!controller_push_msg(ki->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
@@ -292,31 +210,26 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
|
||||
static void
|
||||
sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
const struct sc_text_event *event) {
|
||||
const SDL_TextInputEvent *event) {
|
||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
// Never inject text events
|
||||
return;
|
||||
}
|
||||
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
|
||||
if (!ki->prefer_text) {
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
assert(event->text[1] == '\0');
|
||||
// Letters and space are handled as raw key events
|
||||
// letters and space are handled as raw key event
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||
msg.inject_text.text = strdup(event->text);
|
||||
if (!msg.inject_text.text) {
|
||||
LOGW("Could not strdup input text");
|
||||
return;
|
||||
}
|
||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
||||
if (!controller_push_msg(ki->controller, &msg)) {
|
||||
free(msg.inject_text.text);
|
||||
LOGW("Could not request 'inject text'");
|
||||
}
|
||||
@@ -324,12 +237,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
|
||||
void
|
||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat) {
|
||||
struct controller *controller,
|
||||
const struct scrcpy_options *options) {
|
||||
ki->controller = controller;
|
||||
ki->key_inject_mode = key_inject_mode;
|
||||
ki->forward_key_repeat = forward_key_repeat;
|
||||
ki->prefer_text = options->prefer_text;
|
||||
ki->forward_key_repeat = options->forward_key_repeat;
|
||||
|
||||
ki->repeat = 0;
|
||||
|
||||
@@ -338,7 +250,5 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
.process_text = sc_key_processor_process_text,
|
||||
};
|
||||
|
||||
// Key injection and clipboard synchronization are serialized
|
||||
ki->key_processor.async_paste = false;
|
||||
ki->key_processor.ops = &ops;
|
||||
}
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
struct sc_keyboard_inject {
|
||||
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
|
||||
// number of repetitions. This variable keeps track of the count.
|
||||
unsigned repeat;
|
||||
|
||||
enum sc_key_inject_mode key_inject_mode;
|
||||
bool prefer_text;
|
||||
bool forward_key_repeat;
|
||||
};
|
||||
|
||||
void
|
||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat);
|
||||
struct controller *controller,
|
||||
const struct scrcpy_options *options);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,9 +13,30 @@
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "usb/scrcpy_otg.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
|
||||
main(int argc, char *argv[]) {
|
||||
@@ -26,9 +47,6 @@ main(int argc, char *argv[]) {
|
||||
setbuf(stderr, NULL);
|
||||
#endif
|
||||
|
||||
printf("scrcpy " SCRCPY_VERSION
|
||||
" <https://github.com/Genymobile/scrcpy>\n");
|
||||
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.help = false,
|
||||
@@ -40,21 +58,23 @@ main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
if (!scrcpy_parse_args(&args, argc, argv)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sc_set_log_level(args.opts.log_level);
|
||||
|
||||
if (args.help) {
|
||||
scrcpy_print_usage(argv[0]);
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.version) {
|
||||
scrcpy_print_version();
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
print_version();
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
|
||||
|
||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
av_register_all();
|
||||
#endif
|
||||
@@ -66,17 +86,12 @@ main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
if (avformat_network_init()) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_USB
|
||||
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
|
||||
int res = scrcpy(&args.opts) ? 0 : 1;
|
||||
|
||||
avformat_network_deinit(); // ignore failure
|
||||
|
||||
return ret;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#include "mouse_inject.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "input_events.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast mouse processor to sc_mouse_inject */
|
||||
@@ -15,147 +14,198 @@
|
||||
static enum android_motionevent_buttons
|
||||
convert_mouse_buttons(uint32_t state) {
|
||||
enum android_motionevent_buttons buttons = 0;
|
||||
if (state & SC_MOUSE_BUTTON_LEFT) {
|
||||
if (state & SDL_BUTTON_LMASK) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
|
||||
}
|
||||
if (state & SC_MOUSE_BUTTON_RIGHT) {
|
||||
if (state & SDL_BUTTON_RMASK) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
|
||||
}
|
||||
if (state & SC_MOUSE_BUTTON_MIDDLE) {
|
||||
if (state & SDL_BUTTON_MMASK) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
||||
}
|
||||
if (state & SC_MOUSE_BUTTON_X1) {
|
||||
if (state & SDL_BUTTON_X1MASK) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
||||
}
|
||||
if (state & SC_MOUSE_BUTTON_X2) {
|
||||
if (state & SDL_BUTTON_X2MASK) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
static enum android_motionevent_action
|
||||
convert_mouse_action(enum sc_action action) {
|
||||
if (action == SC_ACTION_DOWN) {
|
||||
return AMOTION_EVENT_ACTION_DOWN;
|
||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||
#define FAIL default: return false
|
||||
static bool
|
||||
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
|
||||
FAIL;
|
||||
}
|
||||
assert(action == SC_ACTION_UP);
|
||||
return AMOTION_EVENT_ACTION_UP;
|
||||
}
|
||||
|
||||
static enum android_motionevent_action
|
||||
convert_touch_action(enum sc_touch_action action) {
|
||||
switch (action) {
|
||||
case SC_TOUCH_ACTION_MOVE:
|
||||
return AMOTION_EVENT_ACTION_MOVE;
|
||||
case SC_TOUCH_ACTION_DOWN:
|
||||
return AMOTION_EVENT_ACTION_DOWN;
|
||||
default:
|
||||
assert(action == SC_TOUCH_ACTION_UP);
|
||||
return AMOTION_EVENT_ACTION_UP;
|
||||
static bool
|
||||
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
|
||||
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
|
||||
FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||
to->inject_touch_event.position.point =
|
||||
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||
to->inject_touch_event.pressure = 1.f;
|
||||
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_touch_event.pointer_id = from->fingerId;
|
||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
int32_t x = from->x * dw;
|
||||
int32_t y = from->y * dh;
|
||||
to->inject_touch_event.position.point =
|
||||
screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||
|
||||
to->inject_touch_event.pressure = from->pressure;
|
||||
to->inject_touch_event.buttons = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||
|
||||
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||
to->inject_touch_event.position.point =
|
||||
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||
to->inject_touch_event.pressure =
|
||||
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
|
||||
to->inject_touch_event.buttons =
|
||||
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||
struct control_msg *to) {
|
||||
|
||||
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
struct 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
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
if (!event->buttons_state) {
|
||||
// Do not send motion events when no click is pressed
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg;
|
||||
if (!convert_mouse_motion(event, mi->screen, &msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
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)) {
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
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
|
||||
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_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = convert_touch_action(event->action),
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = event->pressure,
|
||||
.buttons = 0,
|
||||
},
|
||||
};
|
||||
struct control_msg msg;
|
||||
if (convert_touch(event, mi->screen, &msg)) {
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_button(event, mi->screen, &msg)) {
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse button event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_mouse_wheel(event, mi->screen, &msg)) {
|
||||
if (!controller_push_msg(mi->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse wheel event'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
struct sc_controller *controller) {
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
|
||||
struct screen *screen) {
|
||||
mi->controller = controller;
|
||||
mi->screen = screen;
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
.process_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_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.relative_mode = false;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
struct sc_mouse_inject {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_controller *controller;
|
||||
struct controller *controller;
|
||||
struct screen *screen;
|
||||
};
|
||||
|
||||
void
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
struct sc_controller *controller);
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
|
||||
struct screen *screen);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,7 +28,7 @@ sc_opengl_init(struct sc_opengl *gl) {
|
||||
sizeof(OPENGL_ES_PREFIX) - 1);
|
||||
if (gl->is_opengles) {
|
||||
/* skip the prefix */
|
||||
version += sizeof(OPENGL_ES_PREFIX) - 1;
|
||||
version += sizeof(PREFIX) - 1;
|
||||
}
|
||||
|
||||
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
|
||||
|
||||
@@ -19,10 +19,8 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
||||
},
|
||||
.tunnel_host = 0,
|
||||
.tunnel_port = 0,
|
||||
.shortcut_mods = {
|
||||
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
|
||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
|
||||
.count = 2,
|
||||
},
|
||||
.max_size = 0,
|
||||
@@ -37,16 +35,13 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.display_id = 0,
|
||||
.display_buffer = 0,
|
||||
.v4l2_buffer = 0,
|
||||
#ifdef HAVE_USB
|
||||
.otg = false,
|
||||
#endif
|
||||
.show_touches = false,
|
||||
.fullscreen = false,
|
||||
.always_on_top = false,
|
||||
.control = true,
|
||||
.display = true,
|
||||
.turn_screen_off = false,
|
||||
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
|
||||
.prefer_text = false,
|
||||
.window_borderless = false,
|
||||
.mipmaps = true,
|
||||
.stay_awake = false,
|
||||
@@ -56,13 +51,4 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.forward_all_clicks = false,
|
||||
.legacy_paste = 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,
|
||||
};
|
||||
|
||||
@@ -38,34 +38,15 @@ enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_HID,
|
||||
};
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_INJECT,
|
||||
SC_MOUSE_INPUT_MODE_HID,
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
// Inject special keys, letters and space as key events.
|
||||
// Inject numbers and punctuation as text events.
|
||||
// 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
|
||||
|
||||
enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
|
||||
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
|
||||
SC_SHORTCUT_MOD_LALT = 1 << 2,
|
||||
SC_SHORTCUT_MOD_RALT = 1 << 3,
|
||||
SC_SHORTCUT_MOD_LSUPER = 1 << 4,
|
||||
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
|
||||
SC_MOD_LCTRL = 1 << 0,
|
||||
SC_MOD_RCTRL = 1 << 1,
|
||||
SC_MOD_LALT = 1 << 2,
|
||||
SC_MOD_RALT = 1 << 3,
|
||||
SC_MOD_LSUPER = 1 << 4,
|
||||
SC_MOD_RSUPER = 1 << 5,
|
||||
};
|
||||
|
||||
struct sc_shortcut_mods {
|
||||
@@ -95,10 +76,7 @@ struct scrcpy_options {
|
||||
enum sc_log_level log_level;
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
struct sc_shortcut_mods shortcut_mods;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
@@ -112,16 +90,13 @@ struct scrcpy_options {
|
||||
uint32_t display_id;
|
||||
sc_tick display_buffer;
|
||||
sc_tick v4l2_buffer;
|
||||
#ifdef HAVE_USB
|
||||
bool otg;
|
||||
#endif
|
||||
bool show_touches;
|
||||
bool fullscreen;
|
||||
bool always_on_top;
|
||||
bool control;
|
||||
bool display;
|
||||
bool turn_screen_off;
|
||||
enum sc_key_inject_mode key_inject_mode;
|
||||
bool prefer_text;
|
||||
bool window_borderless;
|
||||
bool mipmaps;
|
||||
bool stay_awake;
|
||||
@@ -131,15 +106,6 @@ struct scrcpy_options {
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
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;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
||||
@@ -7,16 +7,12 @@
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
||||
struct sc_acksync *acksync) {
|
||||
receiver_init(struct receiver *receiver, sc_socket control_socket) {
|
||||
bool ok = sc_mutex_init(&receiver->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
receiver->control_socket = control_socket;
|
||||
receiver->acksync = acksync;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,7 +22,7 @@ receiver_destroy(struct receiver *receiver) {
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||
process_msg(struct device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
char *current = SDL_GetClipboardText();
|
||||
@@ -41,17 +37,11 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||
SDL_SetClipboardText(msg->clipboard.text);
|
||||
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
|
||||
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;
|
||||
for (;;) {
|
||||
struct device_msg msg;
|
||||
@@ -63,7 +53,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
||||
return head;
|
||||
}
|
||||
|
||||
process_msg(receiver, &msg);
|
||||
process_msg(&msg);
|
||||
device_msg_destroy(&msg);
|
||||
|
||||
head += r;
|
||||
@@ -91,7 +81,7 @@ run_receiver(void *data) {
|
||||
}
|
||||
|
||||
head += r;
|
||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
||||
ssize_t consumed = process_msgs(buf, head);
|
||||
if (consumed == -1) {
|
||||
// an error occurred
|
||||
break;
|
||||
@@ -111,10 +101,10 @@ bool
|
||||
receiver_start(struct receiver *receiver) {
|
||||
LOGD("Starting receiver thread");
|
||||
|
||||
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
||||
"scrcpy-receiver", receiver);
|
||||
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
|
||||
receiver);
|
||||
if (!ok) {
|
||||
LOGE("Could not start receiver thread");
|
||||
LOGC("Could not start receiver thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#ifndef SC_RECEIVER_H
|
||||
#define SC_RECEIVER_H
|
||||
#ifndef RECEIVER_H
|
||||
#define RECEIVER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/acksync.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
@@ -15,13 +14,10 @@ struct receiver {
|
||||
sc_socket control_socket;
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
|
||||
struct sc_acksync *acksync;
|
||||
};
|
||||
|
||||
bool
|
||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
||||
struct sc_acksync *acksync);
|
||||
receiver_init(struct receiver *receiver, sc_socket control_socket);
|
||||
|
||||
void
|
||||
receiver_destroy(struct receiver *receiver);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "util/str.h"
|
||||
|
||||
/** 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
|
||||
|
||||
@@ -30,17 +30,15 @@ find_muxer(const char *name) {
|
||||
return oformat;
|
||||
}
|
||||
|
||||
static struct sc_record_packet *
|
||||
sc_record_packet_new(const AVPacket *packet) {
|
||||
struct sc_record_packet *rec = malloc(sizeof(*rec));
|
||||
static struct record_packet *
|
||||
record_packet_new(const AVPacket *packet) {
|
||||
struct record_packet *rec = malloc(sizeof(*rec));
|
||||
if (!rec) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rec->packet = av_packet_alloc();
|
||||
if (!rec->packet) {
|
||||
LOG_OOM();
|
||||
free(rec);
|
||||
return NULL;
|
||||
}
|
||||
@@ -54,22 +52,22 @@ sc_record_packet_new(const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_record_packet_delete(struct sc_record_packet *rec) {
|
||||
record_packet_delete(struct record_packet *rec) {
|
||||
av_packet_free(&rec->packet);
|
||||
free(rec);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
|
||||
recorder_queue_clear(struct recorder_queue *queue) {
|
||||
while (!sc_queue_is_empty(queue)) {
|
||||
struct sc_record_packet *rec;
|
||||
struct record_packet *rec;
|
||||
sc_queue_take(queue, next, &rec);
|
||||
sc_record_packet_delete(rec);
|
||||
record_packet_delete(rec);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
sc_recorder_get_format_name(enum sc_record_format format) {
|
||||
recorder_get_format_name(enum sc_record_format format) {
|
||||
switch (format) {
|
||||
case SC_RECORD_FORMAT_MP4: return "mp4";
|
||||
case SC_RECORD_FORMAT_MKV: return "matroska";
|
||||
@@ -78,12 +76,12 @@ sc_recorder_get_format_name(enum sc_record_format format) {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||
if (!extradata) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not allocate extradata");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,19 +101,19 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
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];
|
||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
||||
recorder_write(struct recorder *recorder, AVPacket *packet) {
|
||||
if (!recorder->header_written) {
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
LOGE("The first packet is not a config packet");
|
||||
return false;
|
||||
}
|
||||
bool ok = sc_recorder_write_header(recorder, packet);
|
||||
bool ok = recorder_write_header(recorder, packet);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -128,13 +126,13 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sc_recorder_rescale_packet(recorder, packet);
|
||||
recorder_rescale_packet(recorder, packet);
|
||||
return av_write_frame(recorder->ctx, packet) >= 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_recorder(void *data) {
|
||||
struct sc_recorder *recorder = data;
|
||||
struct recorder *recorder = data;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&recorder->mutex);
|
||||
@@ -148,29 +146,29 @@ run_recorder(void *data) {
|
||||
|
||||
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
struct sc_record_packet *last = recorder->previous;
|
||||
struct record_packet *last = recorder->previous;
|
||||
if (last) {
|
||||
// assign an arbitrary duration to the last packet
|
||||
last->packet->duration = 100000;
|
||||
bool ok = sc_recorder_write(recorder, last->packet);
|
||||
bool ok = recorder_write(recorder, last->packet);
|
||||
if (!ok) {
|
||||
// failing to write the last frame is not very serious, no
|
||||
// future frame may depend on it, so the resulting file
|
||||
// will still be valid
|
||||
LOGW("Could not record last packet");
|
||||
}
|
||||
sc_record_packet_delete(last);
|
||||
record_packet_delete(last);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
struct sc_record_packet *rec;
|
||||
struct record_packet *rec;
|
||||
sc_queue_take(&recorder->queue, next, &rec);
|
||||
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
|
||||
// recorder->previous is only written from this thread, no need to lock
|
||||
struct sc_record_packet *previous = recorder->previous;
|
||||
struct record_packet *previous = recorder->previous;
|
||||
recorder->previous = rec;
|
||||
|
||||
if (!previous) {
|
||||
@@ -186,15 +184,15 @@ run_recorder(void *data) {
|
||||
rec->packet->pts - previous->packet->pts;
|
||||
}
|
||||
|
||||
bool ok = sc_recorder_write(recorder, previous->packet);
|
||||
sc_record_packet_delete(previous);
|
||||
bool ok = recorder_write(recorder, previous->packet);
|
||||
record_packet_delete(previous);
|
||||
if (!ok) {
|
||||
LOGE("Could not record packet");
|
||||
|
||||
sc_mutex_lock(&recorder->mutex);
|
||||
recorder->failed = true;
|
||||
// discard pending packets
|
||||
sc_recorder_queue_clear(&recorder->queue);
|
||||
recorder_queue_clear(&recorder->queue);
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
break;
|
||||
}
|
||||
@@ -216,7 +214,7 @@ run_recorder(void *data) {
|
||||
if (recorder->failed) {
|
||||
LOGE("Recording failed to %s", recorder->filename);
|
||||
} 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,
|
||||
recorder->filename);
|
||||
}
|
||||
@@ -227,14 +225,16 @@ run_recorder(void *data) {
|
||||
}
|
||||
|
||||
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);
|
||||
if (!ok) {
|
||||
LOGC("Could not create mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&recorder->queue_cond);
|
||||
if (!ok) {
|
||||
LOGC("Could not create cond");
|
||||
goto error_mutex_destroy;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
||||
recorder->header_written = false;
|
||||
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);
|
||||
const AVOutputFormat *format = find_muxer(format_name);
|
||||
if (!format) {
|
||||
@@ -254,7 +254,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
||||
|
||||
recorder->ctx = avformat_alloc_context();
|
||||
if (!recorder->ctx) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate output context");
|
||||
goto error_cond_destroy;
|
||||
}
|
||||
|
||||
@@ -287,10 +287,10 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
||||
}
|
||||
|
||||
LOGD("Starting recorder thread");
|
||||
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
|
||||
recorder);
|
||||
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
||||
recorder);
|
||||
if (!ok) {
|
||||
LOGE("Could not start recorder thread");
|
||||
LOGC("Could not start recorder thread");
|
||||
goto error_avio_close;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ error_mutex_destroy:
|
||||
}
|
||||
|
||||
static void
|
||||
sc_recorder_close(struct sc_recorder *recorder) {
|
||||
recorder_close(struct recorder *recorder) {
|
||||
sc_mutex_lock(&recorder->mutex);
|
||||
recorder->stopped = true;
|
||||
sc_cond_signal(&recorder->queue_cond);
|
||||
@@ -326,7 +326,7 @@ sc_recorder_close(struct sc_recorder *recorder) {
|
||||
}
|
||||
|
||||
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);
|
||||
assert(!recorder->stopped);
|
||||
|
||||
@@ -336,9 +336,9 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
||||
struct record_packet *rec = record_packet_new(packet);
|
||||
if (!rec) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not allocate record packet");
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
@@ -351,33 +351,31 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
|
||||
const AVCodec *codec) {
|
||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||
return sc_recorder_open(recorder, codec);
|
||||
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||
struct recorder *recorder = DOWNCAST(sink);
|
||||
return recorder_open(recorder, codec);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||
sc_recorder_close(recorder);
|
||||
recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||
struct recorder *recorder = DOWNCAST(sink);
|
||||
recorder_close(recorder);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
const AVPacket *packet) {
|
||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||
return sc_recorder_push(recorder, packet);
|
||||
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||
struct recorder *recorder = DOWNCAST(sink);
|
||||
return recorder_push(recorder, packet);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_recorder_init(struct sc_recorder *recorder,
|
||||
const char *filename,
|
||||
enum sc_record_format format,
|
||||
struct sc_size declared_frame_size) {
|
||||
recorder_init(struct recorder *recorder,
|
||||
const char *filename,
|
||||
enum sc_record_format format,
|
||||
struct sc_size declared_frame_size) {
|
||||
recorder->filename = strdup(filename);
|
||||
if (!recorder->filename) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not strdup filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -385,9 +383,9 @@ sc_recorder_init(struct sc_recorder *recorder,
|
||||
recorder->declared_frame_size = declared_frame_size;
|
||||
|
||||
static const struct sc_packet_sink_ops ops = {
|
||||
.open = sc_recorder_packet_sink_open,
|
||||
.close = sc_recorder_packet_sink_close,
|
||||
.push = sc_recorder_packet_sink_push,
|
||||
.open = recorder_packet_sink_open,
|
||||
.close = recorder_packet_sink_close,
|
||||
.push = recorder_packet_sink_push,
|
||||
};
|
||||
|
||||
recorder->packet_sink.ops = &ops;
|
||||
@@ -396,6 +394,6 @@ sc_recorder_init(struct sc_recorder *recorder,
|
||||
}
|
||||
|
||||
void
|
||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
||||
recorder_destroy(struct recorder *recorder) {
|
||||
free(recorder->filename);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_RECORDER_H
|
||||
#define SC_RECORDER_H
|
||||
#ifndef RECORDER_H
|
||||
#define RECORDER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
#include "util/queue.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
struct sc_record_packet {
|
||||
struct record_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
|
||||
|
||||
char *filename;
|
||||
@@ -33,21 +33,20 @@ struct sc_recorder {
|
||||
sc_cond queue_cond;
|
||||
bool stopped; // set on recorder_close()
|
||||
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
|
||||
// set its duration (next_pts - current_pts)
|
||||
// "previous" is only accessed from the recorder thread, so it does not
|
||||
// need to be protected by the mutex
|
||||
struct sc_record_packet *previous;
|
||||
struct record_packet *previous;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||
enum sc_record_format format,
|
||||
struct sc_size declared_frame_size);
|
||||
recorder_init(struct recorder *recorder, const char *filename,
|
||||
enum sc_record_format format, struct sc_size declared_frame_size);
|
||||
|
||||
void
|
||||
sc_recorder_destroy(struct sc_recorder *recorder);
|
||||
recorder_destroy(struct recorder *recorder);
|
||||
|
||||
#endif
|
||||
|
||||
513
app/src/scrcpy.c
513
app/src/scrcpy.c
@@ -15,21 +15,18 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "demuxer.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 "mouse_inject.h"
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#ifdef HAVE_USB
|
||||
# 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 "stream.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#ifdef HAVE_V4L2
|
||||
@@ -38,33 +35,26 @@
|
||||
|
||||
struct scrcpy {
|
||||
struct sc_server server;
|
||||
struct sc_screen screen;
|
||||
struct sc_demuxer demuxer;
|
||||
struct sc_decoder decoder;
|
||||
struct sc_recorder recorder;
|
||||
struct screen screen;
|
||||
struct stream stream;
|
||||
struct decoder decoder;
|
||||
struct recorder recorder;
|
||||
#ifdef HAVE_V4L2
|
||||
struct sc_v4l2_sink v4l2_sink;
|
||||
#endif
|
||||
struct sc_controller controller;
|
||||
struct sc_file_pusher file_pusher;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_usb usb;
|
||||
struct controller controller;
|
||||
struct file_handler file_handler;
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_aoa aoa;
|
||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
||||
struct sc_acksync acksync;
|
||||
#endif
|
||||
union {
|
||||
struct sc_keyboard_inject keyboard_inject;
|
||||
#ifdef HAVE_USB
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_hid_keyboard keyboard_hid;
|
||||
#endif
|
||||
};
|
||||
union {
|
||||
struct sc_mouse_inject mouse_inject;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_hid_mouse mouse_hid;
|
||||
#endif
|
||||
};
|
||||
struct sc_mouse_inject mouse_inject;
|
||||
struct input_manager input_manager;
|
||||
};
|
||||
|
||||
static inline void
|
||||
@@ -101,10 +91,12 @@ sdl_set_hints(const char *render_driver) {
|
||||
LOGW("Could not enable linear filtering");
|
||||
}
|
||||
|
||||
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
|
||||
// Handle a click to gain focus as any other click
|
||||
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
||||
LOGW("Could not enable mouse focus clickthrough");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
// Disable synthetic mouse events from touch events
|
||||
@@ -143,47 +135,101 @@ sdl_configure(bool display, bool disable_screensaver) {
|
||||
}
|
||||
|
||||
if (disable_screensaver) {
|
||||
LOGD("Screensaver disabled");
|
||||
SDL_DisableScreenSaver();
|
||||
} else {
|
||||
LOGD("Screensaver enabled");
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
}
|
||||
|
||||
static enum scrcpy_exit_code
|
||||
event_loop(struct scrcpy *s) {
|
||||
static bool
|
||||
is_apk(const char *file) {
|
||||
const char *ext = strrchr(file, '.');
|
||||
return ext && !strcmp(ext, ".apk");
|
||||
}
|
||||
|
||||
enum event_result {
|
||||
EVENT_RESULT_CONTINUE,
|
||||
EVENT_RESULT_STOPPED_BY_USER,
|
||||
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;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case EVENT_STREAM_STOPPED:
|
||||
enum event_result result = handle_event(s, options, &event);
|
||||
switch (result) {
|
||||
case EVENT_RESULT_STOPPED_BY_USER:
|
||||
return true;
|
||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
||||
LOGW("Device disconnected");
|
||||
return SCRCPY_EXIT_DISCONNECTED;
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
default:
|
||||
sc_screen_handle_event(&s->screen, &event);
|
||||
return false;
|
||||
case EVENT_RESULT_CONTINUE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true on success, false on error
|
||||
static bool
|
||||
await_for_server(bool *connected) {
|
||||
await_for_server(void) {
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
*connected = false;
|
||||
return true;
|
||||
return false;
|
||||
case EVENT_SERVER_CONNECTION_FAILED:
|
||||
LOGE("Server connection failed");
|
||||
return false;
|
||||
case EVENT_SERVER_CONNECTED:
|
||||
LOGD("Server connected");
|
||||
*connected = true;
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
@@ -222,7 +268,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
||||
size_t fmt_len = strlen(fmt);
|
||||
char *local_fmt = malloc(fmt_len + 10);
|
||||
if (!local_fmt) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not allocate string");
|
||||
return;
|
||||
}
|
||||
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
|
||||
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
||||
(void) demuxer;
|
||||
stream_on_eos(struct stream *stream, void *userdata) {
|
||||
(void) stream;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||
@@ -265,48 +311,40 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
||||
// event
|
||||
}
|
||||
|
||||
enum scrcpy_exit_code
|
||||
bool
|
||||
scrcpy(struct scrcpy_options *options) {
|
||||
static struct scrcpy scrcpy;
|
||||
struct scrcpy *s = &scrcpy;
|
||||
|
||||
// Minimal SDL initialization
|
||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
atexit(SDL_Quit);
|
||||
|
||||
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
|
||||
bool ret = false;
|
||||
|
||||
bool server_started = false;
|
||||
bool file_pusher_initialized = false;
|
||||
bool file_handler_initialized = false;
|
||||
bool recorder_initialized = false;
|
||||
#ifdef HAVE_V4L2
|
||||
bool v4l2_sink_initialized = false;
|
||||
#endif
|
||||
bool demuxer_started = false;
|
||||
#ifdef HAVE_USB
|
||||
bool stream_started = false;
|
||||
#ifdef HAVE_AOA_HID
|
||||
bool aoa_hid_initialized = false;
|
||||
bool hid_keyboard_initialized = false;
|
||||
bool hid_mouse_initialized = false;
|
||||
#endif
|
||||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
bool screen_initialized = false;
|
||||
|
||||
struct sc_acksync *acksync = NULL;
|
||||
|
||||
struct sc_server_params params = {
|
||||
.req_serial = options->serial,
|
||||
.select_usb = options->select_usb,
|
||||
.select_tcpip = options->select_tcpip,
|
||||
.serial = options->serial,
|
||||
.log_level = options->log_level,
|
||||
.crop = options->crop,
|
||||
.port_range = options->port_range,
|
||||
.tunnel_host = options->tunnel_host,
|
||||
.tunnel_port = options->tunnel_port,
|
||||
.max_size = options->max_size,
|
||||
.bit_rate = options->bit_rate,
|
||||
.max_fps = options->max_fps,
|
||||
@@ -319,12 +357,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.encoder_name = options->encoder_name,
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
.power_off_on_close = options->power_off_on_close,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
.downsize_on_error = options->downsize_on_error,
|
||||
.tcpip = options->tcpip,
|
||||
.tcpip_dst = options->tcpip_dst,
|
||||
.cleanup = options->cleanup,
|
||||
.power_on = options->power_on,
|
||||
};
|
||||
|
||||
static const struct sc_server_callbacks cbs = {
|
||||
@@ -333,7 +365,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.on_disconnected = sc_server_on_disconnected,
|
||||
};
|
||||
if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_server_start(&s->server)) {
|
||||
@@ -348,57 +380,44 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
// Initialize SDL video in addition if display is enabled
|
||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
|
||||
sdl_configure(options->display, options->disable_screensaver);
|
||||
|
||||
// Await for server without blocking Ctrl+C handling
|
||||
bool connected;
|
||||
if (!await_for_server(&connected)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
// This is not an error, user requested to quit
|
||||
ret = SCRCPY_EXIT_SUCCESS;
|
||||
if (!await_for_server()) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// It is necessarily initialized here, since the device is connected
|
||||
struct sc_server_info *info = &s->server.info;
|
||||
|
||||
const char *serial = s->server.serial;
|
||||
assert(serial);
|
||||
|
||||
struct sc_file_pusher *fp = NULL;
|
||||
|
||||
if (options->display && options->control) {
|
||||
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
||||
options->push_target)) {
|
||||
if (!file_handler_init(&s->file_handler, options->serial,
|
||||
options->push_target)) {
|
||||
goto end;
|
||||
}
|
||||
fp = &s->file_pusher;
|
||||
file_pusher_initialized = true;
|
||||
file_handler_initialized = true;
|
||||
}
|
||||
|
||||
struct sc_decoder *dec = NULL;
|
||||
struct decoder *dec = NULL;
|
||||
bool needs_decoder = options->display;
|
||||
#ifdef HAVE_V4L2
|
||||
needs_decoder |= !!options->v4l2_device;
|
||||
#endif
|
||||
if (needs_decoder) {
|
||||
sc_decoder_init(&s->decoder);
|
||||
decoder_init(&s->decoder);
|
||||
dec = &s->decoder;
|
||||
}
|
||||
|
||||
struct sc_recorder *rec = NULL;
|
||||
struct recorder *rec = NULL;
|
||||
if (options->record_filename) {
|
||||
if (!sc_recorder_init(&s->recorder,
|
||||
options->record_filename,
|
||||
options->record_format,
|
||||
info->frame_size)) {
|
||||
if (!recorder_init(&s->recorder,
|
||||
options->record_filename,
|
||||
options->record_format,
|
||||
info->frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
rec = &s->recorder;
|
||||
@@ -407,187 +426,46 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
av_log_set_callback(av_log_callback);
|
||||
|
||||
static const struct sc_demuxer_callbacks demuxer_cbs = {
|
||||
.on_eos = sc_demuxer_on_eos,
|
||||
static const struct stream_callbacks stream_cbs = {
|
||||
.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) {
|
||||
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink);
|
||||
stream_add_sink(&s->stream, &dec->packet_sink);
|
||||
}
|
||||
|
||||
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) {
|
||||
#ifdef HAVE_USB
|
||||
bool use_hid_keyboard =
|
||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
|
||||
bool use_hid_mouse =
|
||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
|
||||
if (use_hid_keyboard || use_hid_mouse) {
|
||||
bool 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)) {
|
||||
if (!controller_init(&s->controller, s->server.control_socket)) {
|
||||
goto end;
|
||||
}
|
||||
controller_initialized = true;
|
||||
|
||||
if (!sc_controller_start(&s->controller)) {
|
||||
if (!controller_start(&s->controller)) {
|
||||
goto end;
|
||||
}
|
||||
controller_started = true;
|
||||
controller = &s->controller;
|
||||
|
||||
if (options->turn_screen_off) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||
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'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// There is a controller if and only if control is enabled
|
||||
assert(options->control == !!controller);
|
||||
|
||||
if (options->display) {
|
||||
const char *window_title =
|
||||
options->window_title ? options->window_title : info->device_name;
|
||||
|
||||
struct sc_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,
|
||||
struct screen_params screen_params = {
|
||||
.window_title = window_title,
|
||||
.frame_size = info->frame_size,
|
||||
.always_on_top = options->always_on_top,
|
||||
@@ -599,16 +477,15 @@ aoa_hid_end:
|
||||
.rotation = options->rotation,
|
||||
.mipmaps = options->mipmaps,
|
||||
.fullscreen = options->fullscreen,
|
||||
.start_fps_counter = options->start_fps_counter,
|
||||
.buffering_time = options->display_buffer,
|
||||
};
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
if (!screen_init(&s->screen, &screen_params)) {
|
||||
goto end;
|
||||
}
|
||||
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
|
||||
@@ -618,52 +495,115 @@ aoa_hid_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;
|
||||
}
|
||||
#endif
|
||||
|
||||
// now we consumed the header values, the socket receives the video stream
|
||||
// start the demuxer
|
||||
if (!sc_demuxer_start(&s->demuxer)) {
|
||||
// start the stream
|
||||
if (!stream_start(&s->stream)) {
|
||||
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;
|
||||
|
||||
char *serialno = NULL;
|
||||
|
||||
const char *serial = options->serial;
|
||||
if (!serial) {
|
||||
serialno = adb_get_serialno();
|
||||
if (!serialno) {
|
||||
LOGE("Could not get device serial");
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
serial = serialno;
|
||||
LOGI("Device serial: %s", serial);
|
||||
}
|
||||
|
||||
bool ok = sc_aoa_init(&s->aoa, serial);
|
||||
free(serialno);
|
||||
if (!ok) {
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
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...");
|
||||
|
||||
// Close the window immediately on closing, because screen_destroy() may
|
||||
// only be called once the demuxer thread is joined (it may take time)
|
||||
sc_screen_hide_window(&s->screen);
|
||||
// only be called once the stream thread is joined (it may take time)
|
||||
screen_hide_window(&s->screen);
|
||||
|
||||
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
|
||||
#ifdef HAVE_USB
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (aoa_hid_initialized) {
|
||||
if (hid_keyboard_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
}
|
||||
if (hid_mouse_initialized) {
|
||||
sc_hid_mouse_destroy(&s->mouse_hid);
|
||||
}
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
sc_aoa_stop(&s->aoa);
|
||||
sc_usb_stop(&s->usb);
|
||||
}
|
||||
if (acksync) {
|
||||
sc_acksync_destroy(acksync);
|
||||
}
|
||||
#endif
|
||||
if (controller_started) {
|
||||
sc_controller_stop(&s->controller);
|
||||
controller_stop(&s->controller);
|
||||
}
|
||||
if (file_pusher_initialized) {
|
||||
sc_file_pusher_stop(&s->file_pusher);
|
||||
if (file_handler_initialized) {
|
||||
file_handler_stop(&s->file_handler);
|
||||
}
|
||||
if (screen_initialized) {
|
||||
sc_screen_interrupt(&s->screen);
|
||||
screen_interrupt(&s->screen);
|
||||
}
|
||||
|
||||
if (server_started) {
|
||||
@@ -671,10 +611,10 @@ end:
|
||||
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
|
||||
if (demuxer_started) {
|
||||
sc_demuxer_join(&s->demuxer);
|
||||
if (stream_started) {
|
||||
stream_join(&s->stream);
|
||||
}
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
@@ -683,37 +623,34 @@ end:
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_USB
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (aoa_hid_initialized) {
|
||||
sc_aoa_join(&s->aoa);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
sc_usb_join(&s->usb);
|
||||
sc_usb_disconnect(&s->usb);
|
||||
sc_usb_destroy(&s->usb);
|
||||
}
|
||||
#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
|
||||
if (screen_initialized) {
|
||||
sc_screen_join(&s->screen);
|
||||
sc_screen_destroy(&s->screen);
|
||||
screen_join(&s->screen);
|
||||
screen_destroy(&s->screen);
|
||||
}
|
||||
|
||||
if (controller_started) {
|
||||
sc_controller_join(&s->controller);
|
||||
controller_join(&s->controller);
|
||||
}
|
||||
if (controller_initialized) {
|
||||
sc_controller_destroy(&s->controller);
|
||||
controller_destroy(&s->controller);
|
||||
}
|
||||
|
||||
if (recorder_initialized) {
|
||||
sc_recorder_destroy(&s->recorder);
|
||||
recorder_destroy(&s->recorder);
|
||||
}
|
||||
|
||||
if (file_pusher_initialized) {
|
||||
sc_file_pusher_join(&s->file_pusher);
|
||||
sc_file_pusher_destroy(&s->file_pusher);
|
||||
if (file_handler_initialized) {
|
||||
file_handler_join(&s->file_handler);
|
||||
file_handler_destroy(&s->file_handler);
|
||||
}
|
||||
|
||||
sc_server_destroy(&s->server);
|
||||
|
||||
@@ -6,18 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "options.h"
|
||||
|
||||
enum scrcpy_exit_code {
|
||||
// Normal program termination
|
||||
SCRCPY_EXIT_SUCCESS,
|
||||
|
||||
// No connection could be established
|
||||
SCRCPY_EXIT_FAILURE,
|
||||
|
||||
// Device was disconnected while running
|
||||
SCRCPY_EXIT_DISCONNECTED,
|
||||
};
|
||||
|
||||
enum scrcpy_exit_code
|
||||
bool
|
||||
scrcpy(struct scrcpy_options *options);
|
||||
|
||||
#endif
|
||||
|
||||
367
app/src/screen.c
367
app/src/screen.c
@@ -12,7 +12,7 @@
|
||||
|
||||
#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
|
||||
get_rotated_size(struct sc_size size, int rotation) {
|
||||
@@ -29,7 +29,7 @@ get_rotated_size(struct sc_size size, int rotation) {
|
||||
|
||||
// get the window size in a struct sc_size
|
||||
static struct sc_size
|
||||
get_window_size(const struct sc_screen *screen) {
|
||||
get_window_size(const struct screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
@@ -41,7 +41,7 @@ get_window_size(const struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
static struct sc_point
|
||||
get_window_position(const struct sc_screen *screen) {
|
||||
get_window_position(const struct screen *screen) {
|
||||
int x;
|
||||
int 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
|
||||
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->maximized);
|
||||
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
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
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());
|
||||
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 scales down to make it fit in the display_size
|
||||
static struct sc_size
|
||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size,
|
||||
bool within_display_bounds) {
|
||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
||||
if (content_size.width == 0 || content_size.height == 0) {
|
||||
// avoid division by 0
|
||||
return current_size;
|
||||
@@ -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 display_size;
|
||||
if (!within_display_bounds ||
|
||||
!get_preferred_display_bounds(&display_size)) {
|
||||
// do not constraint the size
|
||||
window_size = current_size;
|
||||
if (!get_preferred_display_bounds(&display_size)) {
|
||||
// could not get display bounds, do not constraint the size
|
||||
window_size.width = current_size.width;
|
||||
window_size.height = current_size.height;
|
||||
} else {
|
||||
window_size.width = MIN(current_size.width, display_size.width);
|
||||
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) {
|
||||
struct sc_size window_size;
|
||||
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 {
|
||||
if (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;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||
// screen->im.mp may be NULL if --no-control
|
||||
return screen->im.mp && screen->im.mp->relative_mode;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) {
|
||||
#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) {
|
||||
screen_update_content_rect(struct screen *screen) {
|
||||
int dw;
|
||||
int 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 *
|
||||
create_texture(struct sc_screen *screen) {
|
||||
create_texture(struct screen *screen) {
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
struct sc_size size = screen->frame_size;
|
||||
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
|
||||
// changed, so that the content rectangle is recomputed
|
||||
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) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
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>
|
||||
static int
|
||||
event_watcher(void *data, SDL_Event *event) {
|
||||
struct sc_screen *screen = data;
|
||||
struct screen *screen = data;
|
||||
if (event->type == SDL_WINDOWEVENT
|
||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
// In practice, it seems to always be called from the same thread in
|
||||
// that specific case. Anyway, it's just a workaround.
|
||||
sc_screen_render(screen, true);
|
||||
screen_render(screen, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool
|
||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||
struct screen *screen = DOWNCAST(sink);
|
||||
(void) screen;
|
||||
#ifndef NDEBUG
|
||||
screen->open = true;
|
||||
@@ -342,8 +299,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct screen *screen = DOWNCAST(sink);
|
||||
(void) screen;
|
||||
#ifndef NDEBUG
|
||||
screen->open = false;
|
||||
@@ -353,8 +310,8 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct screen *screen = DOWNCAST(sink);
|
||||
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,
|
||||
void *userdata) {
|
||||
(void) vb;
|
||||
struct sc_screen *screen = userdata;
|
||||
struct screen *screen = userdata;
|
||||
|
||||
// event_failed implies previous_skipped (the previous frame may not have
|
||||
// been consumed if the event was not sent)
|
||||
@@ -370,7 +327,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
|
||||
bool need_new_event;
|
||||
if (previous_skipped) {
|
||||
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||
// this new frame instead, unless the previous event failed
|
||||
need_new_event = screen->event_failed;
|
||||
@@ -395,21 +352,12 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
}
|
||||
|
||||
bool
|
||||
sc_screen_init(struct sc_screen *screen,
|
||||
const struct sc_screen_params *params) {
|
||||
screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
screen->resize_pending = false;
|
||||
screen->has_frame = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->event_failed = false;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
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 = {
|
||||
.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,
|
||||
screen);
|
||||
if (!ok) {
|
||||
LOGE("Could not initialize video buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_video_buffer_start(&screen->vb);
|
||||
if (!ok) {
|
||||
LOGE("Could not start video_buffer");
|
||||
goto error_destroy_video_buffer;
|
||||
}
|
||||
|
||||
if (!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;
|
||||
}
|
||||
|
||||
@@ -439,28 +390,40 @@ sc_screen_init(struct sc_screen *screen,
|
||||
get_rotated_size(screen->frame_size, screen->rotation);
|
||||
screen->content_size = content_size;
|
||||
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(content_size,params->window_width,
|
||||
params->window_height);
|
||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE
|
||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
if (params->always_on_top) {
|
||||
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
#else
|
||||
LOGW("The 'always on top' flag is not available "
|
||||
"(compile with SDL >= 2.0.5 to enable it)");
|
||||
#endif
|
||||
}
|
||||
if (params->window_borderless) {
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
// The window will be positioned and sized on first video frame
|
||||
screen->window =
|
||||
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
||||
window_size.width, window_size.height,
|
||||
window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
LOGC("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
||||
SDL_RENDERER_ACCELERATED);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
LOGC("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
@@ -509,38 +472,35 @@ sc_screen_init(struct sc_screen *screen,
|
||||
params->frame_size.height);
|
||||
screen->texture = create_texture(screen);
|
||||
if (!screen->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
LOGC("Could not create texture: %s", SDL_GetError());
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
if (!screen->frame) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not create screen frame");
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
.controller = params->controller,
|
||||
.fp = params->fp,
|
||||
.screen = screen,
|
||||
.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,
|
||||
};
|
||||
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||
// HiDPI issues with some SDL renderers when several displays having
|
||||
// different HiDPI scaling are connected
|
||||
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
||||
|
||||
sc_input_manager_init(&screen->im, &im_params);
|
||||
screen_update_content_rect(screen);
|
||||
|
||||
if (params->fullscreen) {
|
||||
screen_switch_fullscreen(screen);
|
||||
}
|
||||
|
||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||
SDL_AddEventWatch(event_watcher, screen);
|
||||
#endif
|
||||
|
||||
static const struct sc_frame_sink_ops ops = {
|
||||
.open = sc_screen_frame_sink_open,
|
||||
.close = sc_screen_frame_sink_close,
|
||||
.push = sc_screen_frame_sink_push,
|
||||
.open = screen_frame_sink_open,
|
||||
.close = screen_frame_sink_close,
|
||||
.push = screen_frame_sink_push,
|
||||
};
|
||||
|
||||
screen->frame_sink.ops = &ops;
|
||||
@@ -558,7 +518,7 @@ error_destroy_renderer:
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
fps_counter_destroy(&screen->fps_counter);
|
||||
error_stop_and_join_video_buffer:
|
||||
sc_video_buffer_stop(&screen->vb);
|
||||
sc_video_buffer_join(&screen->vb);
|
||||
@@ -569,49 +529,29 @@ error_destroy_video_buffer:
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
|
||||
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
|
||||
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
|
||||
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(screen->content_size, screen->req.width,
|
||||
screen->req.height);
|
||||
|
||||
set_window_size(screen, window_size);
|
||||
SDL_SetWindowPosition(screen->window, x, y);
|
||||
|
||||
if (screen->req.fullscreen) {
|
||||
sc_screen_switch_fullscreen(screen);
|
||||
}
|
||||
|
||||
if (screen->req.start_fps_counter) {
|
||||
sc_fps_counter_start(&screen->fps_counter);
|
||||
}
|
||||
|
||||
screen_show_window(struct screen *screen) {
|
||||
SDL_ShowWindow(screen->window);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen) {
|
||||
screen_hide_window(struct screen *screen) {
|
||||
SDL_HideWindow(screen->window);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_interrupt(struct sc_screen *screen) {
|
||||
screen_interrupt(struct screen *screen) {
|
||||
sc_video_buffer_stop(&screen->vb);
|
||||
sc_fps_counter_interrupt(&screen->fps_counter);
|
||||
fps_counter_interrupt(&screen->fps_counter);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_join(struct sc_screen *screen) {
|
||||
screen_join(struct screen *screen) {
|
||||
sc_video_buffer_join(&screen->vb);
|
||||
sc_fps_counter_join(&screen->fps_counter);
|
||||
fps_counter_join(&screen->fps_counter);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_destroy(struct sc_screen *screen) {
|
||||
screen_destroy(struct screen *screen) {
|
||||
#ifndef NDEBUG
|
||||
assert(!screen->open);
|
||||
#endif
|
||||
@@ -619,12 +559,12 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
SDL_DestroyTexture(screen->texture);
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
fps_counter_destroy(&screen->fps_counter);
|
||||
sc_video_buffer_destroy(&screen->vb);
|
||||
}
|
||||
|
||||
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 window_size = get_window_size(screen);
|
||||
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
|
||||
/ 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
@@ -652,7 +592,7 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
}
|
||||
|
||||
static void
|
||||
apply_pending_resize(struct sc_screen *screen) {
|
||||
apply_pending_resize(struct screen *screen) {
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
if (screen->resize_pending) {
|
||||
@@ -663,7 +603,7 @@ apply_pending_resize(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
||||
screen_set_rotation(struct screen *screen, unsigned rotation) {
|
||||
assert(rotation < 4);
|
||||
if (rotation == screen->rotation) {
|
||||
return;
|
||||
@@ -677,12 +617,12 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
||||
screen->rotation = 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
|
||||
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
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
// 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);
|
||||
set_content_size(screen, new_content_size);
|
||||
|
||||
sc_screen_update_content_rect(screen);
|
||||
screen_update_content_rect(screen);
|
||||
|
||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
||||
screen->frame_size.width, screen->frame_size.height);
|
||||
screen->texture = create_texture(screen);
|
||||
if (!screen->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
LOGC("Could not create texture: %s", SDL_GetError());
|
||||
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
|
||||
static void
|
||||
update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
||||
update_texture(struct screen *screen, const AVFrame *frame) {
|
||||
SDL_UpdateYUVTexture(screen->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
@@ -724,12 +664,12 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
screen_update_frame(struct screen *screen) {
|
||||
av_frame_unref(screen->frame);
|
||||
sc_video_buffer_consume(&screen->vb, 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};
|
||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||
@@ -737,23 +677,12 @@ sc_screen_update_frame(struct sc_screen *screen) {
|
||||
}
|
||||
update_texture(screen, frame);
|
||||
|
||||
if (!screen->has_frame) {
|
||||
screen->has_frame = true;
|
||||
// this is the very first frame, show the window
|
||||
sc_screen_show_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);
|
||||
screen_render(screen, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||
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");
|
||||
sc_screen_render(screen, true);
|
||||
screen_render(screen, true);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
screen_resize_to_fit(struct screen *screen) {
|
||||
if (screen->fullscreen || screen->maximized) {
|
||||
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 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
|
||||
assert(optimal_size.width <= window_size.width);
|
||||
@@ -794,7 +723,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
if (screen->fullscreen) {
|
||||
return;
|
||||
}
|
||||
@@ -810,34 +739,31 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
content_size.height);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
|
||||
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);
|
||||
|
||||
bool
|
||||
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
case EVENT_NEW_FRAME: {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
case EVENT_NEW_FRAME:
|
||||
if (!screen->has_frame) {
|
||||
screen->has_frame = true;
|
||||
// this is the very first frame, show the window
|
||||
screen_show_window(screen);
|
||||
}
|
||||
bool ok = screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
LOGW("Frame update failed\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
case SDL_WINDOWEVENT:
|
||||
if (!screen->has_frame) {
|
||||
// Do nothing
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
sc_screen_render(screen, true);
|
||||
screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
sc_screen_render(screen, true);
|
||||
screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
screen->maximized = true;
|
||||
@@ -853,79 +779,18 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
||||
}
|
||||
screen->maximized = false;
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
if (relative_mode) {
|
||||
sc_screen_set_mouse_capture(screen, false);
|
||||
}
|
||||
screen_render(screen, true);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
sc_input_manager_handle_event(&screen->im, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
unsigned rotation = screen->rotation;
|
||||
assert(rotation < 4);
|
||||
|
||||
@@ -961,14 +826,14 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
sc_screen_hidpi_scale_coords(screen, &x, &y);
|
||||
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
screen_hidpi_scale_coords(screen, &x, &y);
|
||||
return screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||
}
|
||||
|
||||
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
|
||||
int ww, wh, dw, dh;
|
||||
SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
|
||||
@@ -7,36 +7,21 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "fps_counter.h"
|
||||
#include "input_manager.h"
|
||||
#include "opengl.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
#include "video_buffer.h"
|
||||
|
||||
struct sc_screen {
|
||||
struct screen {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool open; // track the open/close state to assert correct behavior
|
||||
#endif
|
||||
|
||||
struct sc_input_manager im;
|
||||
struct sc_video_buffer vb;
|
||||
struct sc_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;
|
||||
struct fps_counter fps_counter;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
@@ -61,32 +46,18 @@ struct sc_screen {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct sc_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;
|
||||
|
||||
struct screen_params {
|
||||
const char *window_title;
|
||||
struct sc_size frame_size;
|
||||
bool always_on_top;
|
||||
|
||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
int16_t window_x;
|
||||
int16_t window_y;
|
||||
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
|
||||
bool window_borderless;
|
||||
|
||||
@@ -94,72 +65,71 @@ struct sc_screen_params {
|
||||
bool mipmaps;
|
||||
|
||||
bool fullscreen;
|
||||
bool start_fps_counter;
|
||||
|
||||
sc_tick buffering_time;
|
||||
};
|
||||
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
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
|
||||
// must be called before screen_join()
|
||||
void
|
||||
sc_screen_interrupt(struct sc_screen *screen);
|
||||
screen_interrupt(struct screen *screen);
|
||||
|
||||
// join any inner thread
|
||||
void
|
||||
sc_screen_join(struct sc_screen *screen);
|
||||
screen_join(struct screen *screen);
|
||||
|
||||
// destroy window, renderer and texture (if any)
|
||||
void
|
||||
sc_screen_destroy(struct sc_screen *screen);
|
||||
screen_destroy(struct screen *screen);
|
||||
|
||||
// hide the window
|
||||
//
|
||||
// It is used to hide the window immediately on closing without waiting for
|
||||
// screen_destroy()
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen);
|
||||
screen_hide_window(struct screen *screen);
|
||||
|
||||
// switch the fullscreen mode
|
||||
void
|
||||
sc_screen_switch_fullscreen(struct sc_screen *screen);
|
||||
screen_switch_fullscreen(struct screen *screen);
|
||||
|
||||
// resize window to optimal size (remove black borders)
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen);
|
||||
screen_resize_to_fit(struct screen *screen);
|
||||
|
||||
// resize window to 1:1 (pixel-perfect)
|
||||
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)
|
||||
void
|
||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
|
||||
screen_set_rotation(struct screen *screen, unsigned rotation);
|
||||
|
||||
// react to SDL events
|
||||
void
|
||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
|
||||
bool
|
||||
screen_handle_event(struct screen *screen, SDL_Event *event);
|
||||
|
||||
// convert point from window coordinates to frame coordinates
|
||||
// x and y are expressed in pixels
|
||||
struct sc_point
|
||||
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
// convert point from drawable coordinates to frame coordinates
|
||||
// x and y are expressed in pixels
|
||||
struct sc_point
|
||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
// Convert coordinates from window to drawable.
|
||||
// Events are expressed in window coordinates, but content is expressed in
|
||||
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
|
||||
// otherwise.
|
||||
void
|
||||
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
|
||||
|
||||
529
app/src/server.c
529
app/src/server.c
@@ -7,7 +7,7 @@
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include <SDL2/SDL_platform.h>
|
||||
|
||||
#include "adb/adb.h"
|
||||
#include "adb.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net_intr.h"
|
||||
@@ -34,7 +34,7 @@ get_server_path(void) {
|
||||
char *server_path = strdup(server_path_env);
|
||||
#endif
|
||||
if (!server_path) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
||||
@@ -45,7 +45,7 @@ get_server_path(void) {
|
||||
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
|
||||
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
|
||||
if (!server_path) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
@@ -65,11 +65,10 @@ get_server_path(void) {
|
||||
static void
|
||||
sc_server_params_destroy(struct sc_server_params *params) {
|
||||
// The server stores a copy of the params provided by the user
|
||||
free((char *) params->req_serial);
|
||||
free((char *) params->serial);
|
||||
free((char *) params->crop);
|
||||
free((char *) params->codec_options);
|
||||
free((char *) params->encoder_name);
|
||||
free((char *) params->tcpip_dst);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -89,11 +88,10 @@ sc_server_params_copy(struct sc_server_params *dst,
|
||||
} \
|
||||
}
|
||||
|
||||
COPY(req_serial);
|
||||
COPY(serial);
|
||||
COPY(crop);
|
||||
COPY(codec_options);
|
||||
COPY(encoder_name);
|
||||
COPY(tcpip_dst);
|
||||
#undef COPY
|
||||
|
||||
return true;
|
||||
@@ -114,9 +112,9 @@ push_server(struct sc_intr *intr, const char *serial) {
|
||||
free(server_path);
|
||||
return false;
|
||||
}
|
||||
bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
|
||||
sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH);
|
||||
free(server_path);
|
||||
return ok;
|
||||
return sc_process_check_success_intr(intr, pid, "adb push");
|
||||
}
|
||||
|
||||
static const char *
|
||||
@@ -138,42 +136,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 sc_pid
|
||||
execute_server(struct sc_server *server,
|
||||
const struct sc_server_params *params) {
|
||||
sc_pid pid = SC_PROCESS_NONE;
|
||||
|
||||
const char *serial = server->serial;
|
||||
assert(serial);
|
||||
|
||||
fprintf(stderr, "=== [%s]\n", sc_adb_get_installed_apk_version(&server->intr, serial, 0));
|
||||
|
||||
const char *cmd[128];
|
||||
unsigned count = 0;
|
||||
cmd[count++] = sc_adb_get_executable();
|
||||
cmd[count++] = "-s";
|
||||
cmd[count++] = serial;
|
||||
cmd[count++] = "shell";
|
||||
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
|
||||
cmd[count++] = "app_process";
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
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
|
||||
# define SERVER_DEBUGGER_PORT "5005"
|
||||
cmd[count++] =
|
||||
# ifdef SERVER_DEBUGGER_METHOD_NEW
|
||||
/* Android 9 and above */
|
||||
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
|
||||
@@ -182,83 +166,27 @@ execute_server(struct sc_server *server,
|
||||
/* Android 8 and below */
|
||||
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||
# endif
|
||||
SERVER_DEBUGGER_PORT;
|
||||
SERVER_DEBUGGER_PORT,
|
||||
#endif
|
||||
cmd[count++] = "/"; // unused
|
||||
cmd[count++] = "com.genymobile.scrcpy.Server";
|
||||
cmd[count++] = SCRCPY_VERSION;
|
||||
|
||||
unsigned dyn_idx = count; // from there, the strings are allocated
|
||||
#define ADD_PARAM(fmt, ...) { \
|
||||
char *p = (char *) &cmd[count]; \
|
||||
if (asprintf(&p, fmt, ## __VA_ARGS__) == -1) { \
|
||||
goto end; \
|
||||
} \
|
||||
cmd[count++] = p; \
|
||||
}
|
||||
|
||||
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
||||
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
|
||||
|
||||
if (params->max_size) {
|
||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||
}
|
||||
if (params->max_fps) {
|
||||
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
|
||||
}
|
||||
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
||||
params->lock_video_orientation);
|
||||
}
|
||||
if (server->tunnel.forward) {
|
||||
ADD_PARAM("tunnel_forward=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");
|
||||
}
|
||||
|
||||
#undef ADD_PARAM
|
||||
|
||||
cmd[count++] = NULL;
|
||||
|
||||
"/", // unused
|
||||
"com.genymobile.scrcpy.Server",
|
||||
SCRCPY_VERSION,
|
||||
log_level_to_server_string(params->log_level),
|
||||
max_size_string,
|
||||
bit_rate_string,
|
||||
max_fps_string,
|
||||
lock_video_orientation_string,
|
||||
server->tunnel.forward ? "true" : "false",
|
||||
params->crop ? params->crop : "-",
|
||||
"true", // always send frame meta (packet boundaries + timestamp)
|
||||
params->control ? "true" : "false",
|
||||
display_id_string,
|
||||
params->show_touches ? "true" : "false",
|
||||
params->stay_awake ? "true" : "false",
|
||||
params->codec_options ? params->codec_options : "-",
|
||||
params->encoder_name ? params->encoder_name : "-",
|
||||
params->power_off_on_close ? "true" : "false",
|
||||
};
|
||||
#ifdef SERVER_DEBUGGER
|
||||
LOGI("Server debugger waiting for a client on device port "
|
||||
SERVER_DEBUGGER_PORT "...");
|
||||
@@ -270,21 +198,12 @@ execute_server(struct sc_server *server,
|
||||
// Port: 5005
|
||||
// Then click on "Debug"
|
||||
#endif
|
||||
// Inherit both stdout and stderr (all server logs are printed to stdout)
|
||||
pid = sc_adb_execute(cmd, 0);
|
||||
|
||||
end:
|
||||
for (unsigned i = dyn_idx; i < count; ++i) {
|
||||
free((char *) cmd[i]);
|
||||
}
|
||||
|
||||
return pid;
|
||||
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
|
||||
}
|
||||
|
||||
static bool
|
||||
connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
|
||||
uint32_t tunnel_host, uint16_t tunnel_port) {
|
||||
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
|
||||
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -301,13 +220,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
|
||||
}
|
||||
|
||||
static sc_socket
|
||||
connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
|
||||
uint32_t host, uint16_t port) {
|
||||
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
|
||||
uint16_t port = server->tunnel.local_port;
|
||||
do {
|
||||
LOGD("Remaining connection attempts: %u", attempts);
|
||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||
sc_socket socket = net_socket();
|
||||
if (socket != SC_SOCKET_NONE) {
|
||||
bool ok = connect_and_read_byte(&server->intr, socket, host, port);
|
||||
bool ok = connect_and_read_byte(&server->intr, socket, port);
|
||||
if (ok) {
|
||||
// it worked!
|
||||
return socket;
|
||||
@@ -315,21 +234,23 @@ connect_to_server(struct sc_server *server, unsigned attempts, sc_tick delay,
|
||||
|
||||
net_close(socket);
|
||||
}
|
||||
|
||||
if (sc_intr_is_interrupted(&server->intr)) {
|
||||
// Stop immediately
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempts) {
|
||||
sc_mutex_lock(&server->mutex);
|
||||
sc_tick deadline = sc_tick_now() + delay;
|
||||
bool ok = sc_server_sleep(server, deadline);
|
||||
if (!ok) {
|
||||
bool timed_out = false;
|
||||
while (!server->stopped && !timed_out) {
|
||||
timed_out = !sc_cond_timedwait(&server->cond_stopped,
|
||||
&server->mutex, deadline);
|
||||
}
|
||||
bool stopped = server->stopped;
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
|
||||
if (stopped) {
|
||||
LOGI("Connection attempt stopped");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (--attempts);
|
||||
} while (--attempts > 0);
|
||||
return SC_SOCKET_NONE;
|
||||
}
|
||||
|
||||
@@ -338,18 +259,20 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
||||
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = sc_server_params_copy(&server->params, params);
|
||||
if (!ok) {
|
||||
LOG_OOM();
|
||||
LOGE("Could not copy server params");
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_mutex_init(&server->mutex);
|
||||
if (!ok) {
|
||||
LOGE("Could not create server mutex");
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&server->cond_stopped);
|
||||
if (!ok) {
|
||||
LOGE("Could not create server cond_stopped");
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
@@ -357,13 +280,13 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
||||
|
||||
ok = sc_intr_init(&server->intr);
|
||||
if (!ok) {
|
||||
LOGE("Could not create intr");
|
||||
sc_cond_destroy(&server->cond_stopped);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
sc_server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
server->serial = NULL;
|
||||
server->stopped = false;
|
||||
|
||||
server->video_socket = SC_SOCKET_NONE;
|
||||
@@ -408,10 +331,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
||||
|
||||
assert(tunnel->enabled);
|
||||
|
||||
const char *serial = server->serial;
|
||||
assert(serial);
|
||||
|
||||
bool control = server->params.control;
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
sc_socket video_socket = SC_SOCKET_NONE;
|
||||
sc_socket control_socket = SC_SOCKET_NONE;
|
||||
@@ -421,44 +341,27 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (control) {
|
||||
control_socket =
|
||||
net_accept_intr(&server->intr, tunnel->server_socket);
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
control_socket = net_accept_intr(&server->intr, tunnel->server_socket);
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
uint32_t tunnel_host = server->params.tunnel_host;
|
||||
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;
|
||||
uint32_t attempts = 100;
|
||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
||||
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
|
||||
tunnel_port);
|
||||
video_socket = connect_to_server(server, attempts, delay);
|
||||
if (video_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (control) {
|
||||
// we know that the device is listening, we don't need several
|
||||
// attempts
|
||||
control_socket = net_socket();
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
bool ok = net_connect_intr(&server->intr, control_socket,
|
||||
tunnel_host, tunnel_port);
|
||||
if (!ok) {
|
||||
goto fail;
|
||||
}
|
||||
// we know that the device is listening, we don't need several attempts
|
||||
control_socket = net_socket();
|
||||
if (control_socket == SC_SOCKET_NONE) {
|
||||
goto fail;
|
||||
}
|
||||
bool ok = net_connect_intr(&server->intr, control_socket,
|
||||
IPV4_LOCALHOST, tunnel->local_port);
|
||||
if (!ok) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +375,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
||||
}
|
||||
|
||||
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->control_socket = control_socket;
|
||||
@@ -492,10 +395,8 @@ fail:
|
||||
}
|
||||
}
|
||||
|
||||
if (tunnel->enabled) {
|
||||
// Always leave this function with tunnel disabled
|
||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
||||
}
|
||||
// Always leave this function with tunnel disabled
|
||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -515,254 +416,18 @@ sc_server_on_terminated(void *userdata) {
|
||||
LOGD("Server terminated");
|
||||
}
|
||||
|
||||
static bool
|
||||
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
|
||||
struct sc_intr *intr = &server->intr;
|
||||
|
||||
char *current_port =
|
||||
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
|
||||
if (!current_port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the device is listening on TCP on port 5555?
|
||||
bool enabled = !strcmp("5555", current_port);
|
||||
free(current_port);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
static bool
|
||||
wait_tcpip_mode_enabled(struct sc_server *server, 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
run_server(void *data) {
|
||||
struct sc_server *server = data;
|
||||
|
||||
const struct sc_server_params *params = &server->params;
|
||||
|
||||
// Execute "adb start-server" before "adb devices" so that daemon starting
|
||||
// output/errors is correctly printed in the console ("adb devices" output
|
||||
// is parsed, so it is not output)
|
||||
bool ok = sc_adb_start_server(&server->intr, 0);
|
||||
if (!ok) {
|
||||
LOGE("Could not start adb daemon");
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
// params->tcpip_dst implies params->tcpip
|
||||
assert(!params->tcpip_dst || params->tcpip);
|
||||
|
||||
// If tcpip_dst parameter is given, then it must connect to this address.
|
||||
// Therefore, the device is unknown, so serial is meaningless at this point.
|
||||
assert(!params->req_serial || !params->tcpip_dst);
|
||||
|
||||
// A device must be selected via a serial in all cases except when --tcpip=
|
||||
// is called with a parameter (in that case, the device may initially not
|
||||
// exist, and scrcpy will execute "adb connect").
|
||||
bool need_initial_serial = !params->tcpip_dst;
|
||||
|
||||
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);
|
||||
bool ok = push_server(&server->intr, params->serial);
|
||||
if (!ok) {
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
|
||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
|
||||
params->port_range, params->force_adb_forward);
|
||||
if (!ok) {
|
||||
goto error_connection_failed;
|
||||
@@ -771,7 +436,7 @@ run_server(void *data) {
|
||||
// server will connect to our server socket
|
||||
sc_pid pid = execute_server(server, params);
|
||||
if (pid == SC_PROCESS_NONE) {
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
@@ -783,7 +448,7 @@ run_server(void *data) {
|
||||
if (!ok) {
|
||||
sc_process_terminate(pid);
|
||||
sc_process_wait(pid, true); // ignore exit code
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
|
||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
||||
goto error_connection_failed;
|
||||
}
|
||||
|
||||
@@ -807,15 +472,6 @@ run_server(void *data) {
|
||||
}
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
|
||||
// Interrupt sockets to wake up socket blocking calls on the server
|
||||
assert(server->video_socket != SC_SOCKET_NONE);
|
||||
net_interrupt(server->video_socket);
|
||||
|
||||
if (server->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
|
||||
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
||||
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
||||
@@ -846,8 +502,7 @@ error_connection_failed:
|
||||
|
||||
bool
|
||||
sc_server_start(struct sc_server *server) {
|
||||
bool ok =
|
||||
sc_thread_create(&server->thread, run_server, "scrcpy-server", server);
|
||||
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
|
||||
if (!ok) {
|
||||
LOGE("Could not create server thread");
|
||||
return false;
|
||||
@@ -869,14 +524,6 @@ sc_server_stop(struct sc_server *server) {
|
||||
|
||||
void
|
||||
sc_server_destroy(struct sc_server *server) {
|
||||
if (server->video_socket != SC_SOCKET_NONE) {
|
||||
net_close(server->video_socket);
|
||||
}
|
||||
if (server->control_socket != SC_SOCKET_NONE) {
|
||||
net_close(server->control_socket);
|
||||
}
|
||||
|
||||
free(server->serial);
|
||||
sc_server_params_destroy(&server->params);
|
||||
sc_intr_destroy(&server->intr);
|
||||
sc_cond_destroy(&server->cond_stopped);
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "adb/adb_tunnel.h"
|
||||
#include "adb.h"
|
||||
#include "adb_tunnel.h"
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
#include "util/intr.h"
|
||||
@@ -22,14 +23,12 @@ struct sc_server_info {
|
||||
};
|
||||
|
||||
struct sc_server_params {
|
||||
const char *req_serial;
|
||||
const char *serial;
|
||||
enum sc_log_level log_level;
|
||||
const char *crop;
|
||||
const char *codec_options;
|
||||
const char *encoder_name;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
uint16_t max_fps;
|
||||
@@ -40,20 +39,11 @@ struct sc_server_params {
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
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;
|
||||
};
|
||||
|
||||
struct sc_server {
|
||||
// The internal allocated strings are copies owned by the server
|
||||
struct sc_server_params params;
|
||||
char *serial;
|
||||
|
||||
sc_thread thread;
|
||||
struct sc_server_info info; // initialized once connected
|
||||
|
||||
298
app/src/stream.c
Normal file
298
app/src/stream.c
Normal 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
50
app/src/stream.h
Normal 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
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_file_executable_exists(const char *file) {
|
||||
char *path = getenv("PATH");
|
||||
@@ -26,10 +23,7 @@ sc_file_executable_exists(const char *file) {
|
||||
size_t dir_len = strlen(dir);
|
||||
char *fullpath = malloc(dir_len + file_len + 2);
|
||||
if (!fullpath)
|
||||
{
|
||||
LOG_OOM();
|
||||
continue;
|
||||
}
|
||||
memcpy(fullpath, dir, dir_len);
|
||||
fullpath[dir_len] = '/';
|
||||
memcpy(fullpath + dir_len + 1, file, file_len + 1);
|
||||
|
||||
@@ -11,11 +11,8 @@
|
||||
#include "util/log.h"
|
||||
|
||||
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,
|
||||
int *pin, int *pout, int *perr) {
|
||||
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
|
||||
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
|
||||
|
||||
int in[2];
|
||||
int out[2];
|
||||
int err[2];
|
||||
@@ -93,39 +90,22 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
||||
}
|
||||
close(in[1]);
|
||||
}
|
||||
// Do not close stdin in the child process, this makes adb fail on Linux
|
||||
|
||||
if (pout) {
|
||||
if (out[1] != STDOUT_FILENO) {
|
||||
dup2(out[1], STDOUT_FILENO);
|
||||
close(out[1]);
|
||||
}
|
||||
close(out[0]);
|
||||
} else if (!inherit_stdout) {
|
||||
// Close stdout in the child process
|
||||
close(STDOUT_FILENO);
|
||||
}
|
||||
|
||||
if (perr) {
|
||||
if (err[1] != STDERR_FILENO) {
|
||||
dup2(err[1], STDERR_FILENO);
|
||||
close(err[1]);
|
||||
}
|
||||
close(err[0]);
|
||||
} else if (!inherit_stderr) {
|
||||
// Close stderr in the child process
|
||||
close(STDERR_FILENO);
|
||||
}
|
||||
|
||||
close(internal[0]);
|
||||
enum sc_process_result err;
|
||||
|
||||
// Somehow SDL masks many signals - undo them for other processes
|
||||
// https://github.com/libsdl-org/SDL/blob/release-2.0.18/src/thread/pthread/SDL_systhread.c#L167
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigprocmask(SIG_SETMASK, &mask, NULL);
|
||||
|
||||
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
|
||||
execvp(argv[0], (char *const *) argv);
|
||||
perror("exec");
|
||||
@@ -176,7 +156,7 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
||||
bool
|
||||
sc_process_terminate(pid_t pid) {
|
||||
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);
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ bool
|
||||
sc_file_is_regular(const char *path) {
|
||||
wchar_t *wide_path = sc_str_to_wchars(path);
|
||||
if (!wide_path) {
|
||||
LOG_OOM();
|
||||
LOGC("Could not allocate wide char string");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "util/process.h"
|
||||
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
@@ -24,14 +22,8 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||
}
|
||||
|
||||
enum sc_process_result
|
||||
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
sc_process_execute_p(const char *const argv[], HANDLE *handle,
|
||||
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
|
||||
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
|
||||
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
|
||||
|
||||
// Add 1 per non-NULL pointer
|
||||
unsigned handle_count = !!pin || !!pout || !!perr;
|
||||
|
||||
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
@@ -73,107 +65,45 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
}
|
||||
}
|
||||
|
||||
STARTUPINFOEXW si;
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.StartupInfo.cb = sizeof(si);
|
||||
HANDLE handles[3];
|
||||
|
||||
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
if (inherit_stdout) {
|
||||
si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
if (inherit_stderr) {
|
||||
si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
}
|
||||
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
|
||||
if (handle_count) {
|
||||
unsigned i = 0;
|
||||
si.cb = sizeof(si);
|
||||
if (pin || pout || perr) {
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
if (pin) {
|
||||
si.StartupInfo.hStdInput = stdin_read_handle;
|
||||
handles[i++] = si.StartupInfo.hStdInput;
|
||||
si.hStdInput = stdin_read_handle;
|
||||
}
|
||||
if (pout) {
|
||||
assert(!inherit_stdout);
|
||||
si.StartupInfo.hStdOutput = stdout_write_handle;
|
||||
handles[i++] = si.StartupInfo.hStdOutput;
|
||||
si.hStdOutput = stdout_write_handle;
|
||||
}
|
||||
if (perr) {
|
||||
assert(!inherit_stderr);
|
||||
si.StartupInfo.hStdError = stderr_write_handle;
|
||||
handles[i++] = si.StartupInfo.hStdError;
|
||||
si.hStdError = stderr_write_handle;
|
||||
}
|
||||
|
||||
SIZE_T size;
|
||||
// Call it once to know the required buffer size
|
||||
BOOL ok =
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &size)
|
||||
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER;
|
||||
if (!ok) {
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
lpAttributeList = malloc(size);
|
||||
if (!lpAttributeList) {
|
||||
LOG_OOM();
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
|
||||
if (!ok) {
|
||||
free(lpAttributeList);
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
|
||||
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||
handles, handle_count * sizeof(HANDLE),
|
||||
NULL, NULL);
|
||||
if (!ok) {
|
||||
goto error_free_attribute_list;
|
||||
}
|
||||
|
||||
si.lpAttributeList = lpAttributeList;
|
||||
}
|
||||
|
||||
char *cmd = malloc(CMD_MAX_LEN);
|
||||
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
|
||||
LOG_OOM();
|
||||
goto error_free_attribute_list;
|
||||
*handle = NULL;
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
wchar_t *wide = sc_str_to_wchars(cmd);
|
||||
free(cmd);
|
||||
if (!wide) {
|
||||
LOG_OOM();
|
||||
goto error_free_attribute_list;
|
||||
LOGC("Could not allocate wide char string");
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
BOOL bInheritHandles = handle_count > 0 || inherit_stdout || inherit_stderr;
|
||||
DWORD dwCreationFlags = 0;
|
||||
if (handle_count > 0) {
|
||||
dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
|
||||
}
|
||||
if (!inherit_stdout && !inherit_stderr) {
|
||||
// DETACHED_PROCESS to disable stdin, stdout and stderr
|
||||
dwCreationFlags |= DETACHED_PROCESS;
|
||||
}
|
||||
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
|
||||
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
|
||||
free(wide);
|
||||
if (!ok) {
|
||||
int err = GetLastError();
|
||||
LOGE("CreateProcessW() error %d", err);
|
||||
if (err == ERROR_FILE_NOT_FOUND) {
|
||||
if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si,
|
||||
&pi)) {
|
||||
free(wide);
|
||||
*handle = NULL;
|
||||
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||
ret = SC_PROCESS_ERROR_MISSING_BINARY;
|
||||
}
|
||||
goto error_free_attribute_list;
|
||||
}
|
||||
|
||||
if (lpAttributeList) {
|
||||
DeleteProcThreadAttributeList(lpAttributeList);
|
||||
free(lpAttributeList);
|
||||
goto error_close_stderr;
|
||||
}
|
||||
|
||||
// These handles are used by the child process, close them for this process
|
||||
@@ -187,15 +117,11 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
CloseHandle(stderr_write_handle);
|
||||
}
|
||||
|
||||
free(wide);
|
||||
*handle = pi.hProcess;
|
||||
|
||||
return SC_PROCESS_SUCCESS;
|
||||
|
||||
error_free_attribute_list:
|
||||
if (lpAttributeList) {
|
||||
DeleteProcThreadAttributeList(lpAttributeList);
|
||||
free(lpAttributeList);
|
||||
}
|
||||
error_close_stderr:
|
||||
if (perr) {
|
||||
CloseHandle(*perr);
|
||||
@@ -242,7 +168,7 @@ sc_process_close(HANDLE handle) {
|
||||
}
|
||||
|
||||
ssize_t
|
||||
sc_pipe_read(HANDLE pipe, char *data, size_t len) {
|
||||
sc_read_pipe(HANDLE pipe, char *data, size_t len) {
|
||||
DWORD r;
|
||||
if (!ReadFile(pipe, data, len, &r, NULL)) {
|
||||
return -1;
|
||||
@@ -251,7 +177,7 @@ sc_pipe_read(HANDLE pipe, char *data, size_t len) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_pipe_close(HANDLE pipe) {
|
||||
sc_close_pipe(HANDLE pipe) {
|
||||
if (!CloseHandle(pipe)) {
|
||||
LOGW("Cannot close pipe");
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
/**
|
||||
* Key processor trait.
|
||||
@@ -14,42 +14,16 @@
|
||||
* Component able to process and inject keys should implement this trait.
|
||||
*/
|
||||
struct sc_key_processor {
|
||||
/**
|
||||
* Set by the implementation to indicate that it must explicitly wait for
|
||||
* the clipboard to be set on the device before injecting Ctrl+v to avoid
|
||||
* race conditions. If it is set, the input_manager will pass a valid
|
||||
* ack_to_wait to process_key() in case of clipboard synchronization
|
||||
* resulting of the key event.
|
||||
*/
|
||||
bool async_paste;
|
||||
|
||||
const struct sc_key_processor_ops *ops;
|
||||
};
|
||||
|
||||
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
|
||||
(*process_key)(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event, uint64_t ack_to_wait);
|
||||
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event);
|
||||
|
||||
/**
|
||||
* Process an input text
|
||||
*
|
||||
* This function is optional.
|
||||
*/
|
||||
void
|
||||
(*process_text)(struct sc_key_processor *kp,
|
||||
const struct sc_text_event *event);
|
||||
const SDL_TextInputEvent *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
/**
|
||||
* Mouse processor trait.
|
||||
@@ -16,51 +16,24 @@
|
||||
*/
|
||||
struct sc_mouse_processor {
|
||||
const struct sc_mouse_processor_ops *ops;
|
||||
|
||||
/**
|
||||
* If set, the mouse processor works in relative mode (the absolute
|
||||
* position is irrelevant). In particular, it indicates that the mouse
|
||||
* pointer must be "captured" by the UI.
|
||||
*/
|
||||
bool relative_mode;
|
||||
};
|
||||
|
||||
struct sc_mouse_processor_ops {
|
||||
/**
|
||||
* Process a mouse motion event
|
||||
*
|
||||
* This function is mandatory.
|
||||
*/
|
||||
void
|
||||
(*process_mouse_motion)(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event);
|
||||
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
|
||||
(*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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ¶ms);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// usb_device not needed anymore
|
||||
sc_usb_device_destroy(&usb_device);
|
||||
usb_device_initialized = false;
|
||||
|
||||
ret = event_loop(s);
|
||||
LOGD("quit...");
|
||||
|
||||
end:
|
||||
if (aoa_started) {
|
||||
sc_aoa_stop(&s->aoa);
|
||||
}
|
||||
sc_usb_stop(&s->usb);
|
||||
|
||||
if (mouse) {
|
||||
sc_hid_mouse_destroy(&s->mouse);
|
||||
}
|
||||
if (keyboard) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard);
|
||||
}
|
||||
|
||||
if (aoa_initialized) {
|
||||
sc_aoa_join(&s->aoa);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
}
|
||||
|
||||
sc_usb_join(&s->usb);
|
||||
|
||||
if (usb_connected) {
|
||||
sc_usb_disconnect(&s->usb);
|
||||
}
|
||||
|
||||
if (usb_device_initialized) {
|
||||
sc_usb_device_destroy(&usb_device);
|
||||
}
|
||||
|
||||
sc_usb_destroy(&s->usb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user