Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f0e577461 | ||
|
|
c05054c52d | ||
|
|
5a6a5b4901 | ||
|
|
2a23ee6894 | ||
|
|
156d907a82 | ||
|
|
88a3713574 | ||
|
|
99acc3e910 | ||
|
|
0be11fa2e2 | ||
|
|
353e440ace | ||
|
|
2f6d7b371d |
32
BUILD.md
32
BUILD.md
@@ -14,8 +14,7 @@ First, you need to install the required packages:
|
|||||||
# for Debian/Ubuntu
|
# for Debian/Ubuntu
|
||||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
|
||||||
libusb-1.0-0 libusb-1.0-0-dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then clone the repo and execute the installation script
|
Then clone the repo and execute the installation script
|
||||||
@@ -46,7 +45,7 @@ sudo ninja -Cbuild-auto uninstall
|
|||||||
### `master`
|
### `master`
|
||||||
|
|
||||||
The `master` branch concerns the latest release, and is the home page of the
|
The `master` branch concerns the latest release, and is the home page of the
|
||||||
project on GitHub.
|
project on Github.
|
||||||
|
|
||||||
|
|
||||||
### `dev`
|
### `dev`
|
||||||
@@ -93,8 +92,8 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
|
|||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev\
|
||||||
libusb-1.0-0-dev
|
libusb-dev
|
||||||
|
|
||||||
# server build dependencies
|
# server build dependencies
|
||||||
sudo apt install openjdk-11-jdk
|
sudo apt install openjdk-11-jdk
|
||||||
@@ -260,7 +259,7 @@ set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
|
|||||||
Then, build:
|
Then, build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype=release --strip -Db_lto=true
|
meson x --buildtype release --strip -Db_lto=true
|
||||||
ninja -Cx # DO NOT RUN AS ROOT
|
ninja -Cx # DO NOT RUN AS ROOT
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -272,16 +271,16 @@ install` must be run as root)._
|
|||||||
|
|
||||||
#### Option 2: Use prebuilt server
|
#### Option 2: Use prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.24`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.19`][direct-scrcpy-server]
|
||||||
<sub>SHA-256: `ae74a81ea79c0dc7250e586627c278c0a9a8c5de46c9fb5c38c167fb1a36f056`</sub>
|
_(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_
|
||||||
|
|
||||||
[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.19/scrcpy-server-v1.19
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
meson x --buildtype=release --strip -Db_lto=true \
|
meson x --buildtype release --strip -Db_lto=true \
|
||||||
-Dprebuilt_server=/path/to/scrcpy-server
|
-Dprebuilt_server=/path/to/scrcpy-server
|
||||||
ninja -Cx # DO NOT RUN AS ROOT
|
ninja -Cx # DO NOT RUN AS ROOT
|
||||||
```
|
```
|
||||||
@@ -305,16 +304,13 @@ After a successful build, you can install _scrcpy_ on the system:
|
|||||||
sudo ninja -Cx install # without sudo on Windows
|
sudo ninja -Cx install # without sudo on Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs several files:
|
This installs three files:
|
||||||
|
|
||||||
- `/usr/local/bin/scrcpy` (main app)
|
- `/usr/local/bin/scrcpy`
|
||||||
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
|
- `/usr/local/share/scrcpy/scrcpy-server`
|
||||||
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
|
- `/usr/local/share/man/man1/scrcpy.1`
|
||||||
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
|
|
||||||
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
|
|
||||||
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)
|
|
||||||
|
|
||||||
You can then [run](README.md#run) `scrcpy`.
|
You can then [run](README.md#run) _scrcpy_.
|
||||||
|
|
||||||
### Uninstall
|
### Uninstall
|
||||||
|
|
||||||
|
|||||||
18
FAQ.it.md
18
FAQ.it.md
@@ -140,24 +140,6 @@ Potresti anche dover configurare il [comportamento di ridimensionamento][scaling
|
|||||||
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||||
|
|
||||||
|
|
||||||
### Problema con Wayland
|
|
||||||
|
|
||||||
Per impostazione predefinita, SDL utilizza x11 su Linux. Il [video driver] può essere cambiato attraversio la variabile d'ambiente `SDL_VIDEODRIVER`:
|
|
||||||
|
|
||||||
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export SDL_VIDEODRIVER=wayland
|
|
||||||
scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
Su alcune distribuzioni (almeno Fedora), il pacchetto `libdecor` deve essere installato manualmente.
|
|
||||||
|
|
||||||
Vedi le issues [#2554] e [#2559].
|
|
||||||
|
|
||||||
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
|
|
||||||
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
|
|
||||||
|
|
||||||
|
|
||||||
### Crash del compositore KWin
|
### Crash del compositore KWin
|
||||||
|
|
||||||
|
|||||||
131
FAQ.md
131
FAQ.md
@@ -4,16 +4,23 @@
|
|||||||
|
|
||||||
Here are the common reported problems and their status.
|
Here are the common reported problems and their status.
|
||||||
|
|
||||||
If you encounter any error, the first step is to upgrade to the latest version.
|
|
||||||
|
|
||||||
|
|
||||||
## `adb` issues
|
## `adb` issues
|
||||||
|
|
||||||
`scrcpy` execute `adb` commands to initialize the connection with the device. If
|
`scrcpy` execute `adb` commands to initialize the connection with the device. If
|
||||||
`adb` fails, then scrcpy will not work.
|
`adb` fails, then scrcpy will not work.
|
||||||
|
|
||||||
|
In that case, it will print this error:
|
||||||
|
|
||||||
|
> ERROR: "adb push" returned with value 1
|
||||||
|
|
||||||
This is typically not a bug in _scrcpy_, but a problem in your environment.
|
This is typically not a bug in _scrcpy_, but a problem in your environment.
|
||||||
|
|
||||||
|
To find out the cause, execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb devices
|
||||||
|
```
|
||||||
|
|
||||||
### `adb` not found
|
### `adb` not found
|
||||||
|
|
||||||
@@ -23,63 +30,38 @@ On Windows, the current directory is in your `PATH`, and `adb.exe` is included
|
|||||||
in the release, so it should work out-of-the-box.
|
in the release, so it should work out-of-the-box.
|
||||||
|
|
||||||
|
|
||||||
|
### Device unauthorized
|
||||||
|
|
||||||
|
Check [stackoverflow][device-unauthorized].
|
||||||
|
|
||||||
|
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
||||||
|
|
||||||
|
|
||||||
### Device not detected
|
### Device not detected
|
||||||
|
|
||||||
> ERROR: Could not find any ADB device
|
> adb: error: failed to get feature set: no devices/emulators found
|
||||||
|
|
||||||
Check that you correctly enabled [adb debugging][enable-adb].
|
Check that you correctly enabled [adb debugging][enable-adb].
|
||||||
|
|
||||||
Your device must be detected by `adb`:
|
If your device is not detected, you may need some [drivers] (on Windows).
|
||||||
|
|
||||||
```
|
|
||||||
adb devices
|
|
||||||
```
|
|
||||||
|
|
||||||
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
|
|
||||||
|
|
||||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||||
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
|
|
||||||
|
|
||||||
|
|
||||||
### Device unauthorized
|
|
||||||
|
|
||||||
> ERROR: Device is unauthorized:
|
|
||||||
> ERROR: --> (usb) 0123456789abcdef unauthorized
|
|
||||||
> ERROR: A popup should open on the device to request authorization.
|
|
||||||
|
|
||||||
When connecting, a popup should open on the device. You must authorize USB
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
If it does not open, check [stackoverflow][device-unauthorized].
|
|
||||||
|
|
||||||
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
|
||||||
|
|
||||||
|
|
||||||
### Several devices connected
|
### Several devices connected
|
||||||
|
|
||||||
If several devices are connected, you will encounter this error:
|
If several devices are connected, you will encounter this error:
|
||||||
|
|
||||||
ERROR: Multiple (2) ADB devices:
|
> adb: error: failed to get feature set: more than one device/emulator
|
||||||
ERROR: --> (usb) 0123456789abcdef device Nexus_5
|
|
||||||
ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
|
|
||||||
ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
|
|
||||||
|
|
||||||
In that case, you can either provide the identifier of the device you want to
|
the identifier of the device you want to mirror must be provided:
|
||||||
mirror:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy -s 0123456789abcdef
|
scrcpy -s 01234567890abcdef
|
||||||
```
|
```
|
||||||
|
|
||||||
Or request the single USB (or TCP/IP) device:
|
Note that if your device is connected over TCP/IP, you'll get this message:
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy -d # USB device
|
|
||||||
scrcpy -e # TCP/IP device
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that if your device is connected over TCP/IP, you might get this message:
|
|
||||||
|
|
||||||
> adb: error: more than one device/emulator
|
> adb: error: more than one device/emulator
|
||||||
> ERROR: "adb reverse" returned with value 1
|
> ERROR: "adb reverse" returned with value 1
|
||||||
@@ -136,17 +118,13 @@ In developer options, enable:
|
|||||||
|
|
||||||
### Special characters do not work
|
### Special characters do not work
|
||||||
|
|
||||||
The default text injection method is [limited to ASCII characters][text-input].
|
Injecting text input is [limited to ASCII characters][text-input]. A trick
|
||||||
A trick allows to also inject some [accented characters][accented-characters],
|
allows to also inject some [accented characters][accented-characters], but
|
||||||
but that's all. See [#37].
|
that's all. See [#37].
|
||||||
|
|
||||||
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
|
|
||||||
keyboard][hid] (HID).
|
|
||||||
|
|
||||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||||
[hid]: README.md#physical-keyboard-simulation-hid
|
|
||||||
|
|
||||||
|
|
||||||
## Client issues
|
## Client issues
|
||||||
@@ -158,24 +136,22 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
|
|||||||
|
|
||||||
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
||||||
|
|
||||||
This problem should be fixed in scrcpy v1.22: **update to the latest version**.
|
To improve downscaling quality, trilinear filtering is enabled automatically
|
||||||
|
if the renderer is OpenGL and if it supports mipmapping.
|
||||||
|
|
||||||
On older versions, you must configure the [scaling behavior]:
|
On Windows, you might want to force OpenGL:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --render-driver=opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also need to configure the [scaling behavior]:
|
||||||
|
|
||||||
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
|
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
|
||||||
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
||||||
|
|
||||||
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||||
|
|
||||||
Also, to improve downscaling quality, trilinear filtering is enabled
|
|
||||||
automatically if the renderer is OpenGL and if it supports mipmapping.
|
|
||||||
|
|
||||||
On Windows, you might want to force OpenGL to enable mipmapping:
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --render-driver=opengl
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Issue with Wayland
|
### Issue with Wayland
|
||||||
|
|
||||||
@@ -239,44 +215,13 @@ scrcpy -m 1024
|
|||||||
scrcpy -m 800
|
scrcpy -m 800
|
||||||
```
|
```
|
||||||
|
|
||||||
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
|
|
||||||
before failing. This behavior can be disabled with `--no-downsize-on-error`.
|
|
||||||
|
|
||||||
You could also try another [encoder](README.md#encoder).
|
You could also try another [encoder](README.md#encoder).
|
||||||
|
|
||||||
|
|
||||||
If you encounter this exception on Android 12, then just upgrade to scrcpy >=
|
|
||||||
1.18 (see [#2129]):
|
|
||||||
|
|
||||||
```
|
|
||||||
> ERROR: Exception on thread Thread[main,5,main]
|
|
||||||
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
|
|
||||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
|
|
||||||
...
|
|
||||||
Caused by: java.lang.reflect.InvocationTargetException
|
|
||||||
at java.lang.reflect.Method.invoke(Native Method)
|
|
||||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
|
|
||||||
... 7 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
|
|
||||||
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
|
|
||||||
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
|
|
||||||
... 9 more
|
|
||||||
```
|
|
||||||
|
|
||||||
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
|
|
||||||
|
|
||||||
|
|
||||||
## Command line on Windows
|
## Command line on Windows
|
||||||
|
|
||||||
Since v1.22, a "shortcut" has been added to directly open a terminal in the
|
Some Windows users are not familiar with the command line. Here is how to open a
|
||||||
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
|
terminal and run `scrcpy` with arguments:
|
||||||
command. For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --record file.mkv
|
|
||||||
```
|
|
||||||
|
|
||||||
You could also open a terminal and go to the scrcpy folder manually:
|
|
||||||
|
|
||||||
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
|
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
|
||||||
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.
|
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.
|
||||||
@@ -313,6 +258,6 @@ to add some arguments.
|
|||||||
|
|
||||||
This FAQ is available in other languages:
|
This FAQ is available in other languages:
|
||||||
|
|
||||||
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
|
- [Italiano (Italiano, `it`) - v1.17](FAQ.it.md)
|
||||||
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
||||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md)
|
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
_Only the original [FAQ.md](FAQ.md) is guaranteed to be up-to-date._
|
只有原版的[FAQ](FAQ.md)会保持更新。
|
||||||
|
本文根据[d6aaa5]翻译。
|
||||||
|
|
||||||
_只有原版的 [FAQ.md](FAQ.md)是保证最新的。_
|
[d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md
|
||||||
|
|
||||||
Current version is based on [28054cd]
|
|
||||||
|
|
||||||
本文根据[28054cd]进行翻译。
|
|
||||||
|
|
||||||
[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md
|
|
||||||
|
|
||||||
# 常见问题
|
# 常见问题
|
||||||
|
|
||||||
@@ -14,11 +9,11 @@ Current version is based on [28054cd]
|
|||||||
|
|
||||||
## `adb` 相关问题
|
## `adb` 相关问题
|
||||||
|
|
||||||
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果 `adb` 执行失败了, scrcpy 就无法工作。
|
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果`adb` 执行失败了, scrcpy 就无法工作。
|
||||||
|
|
||||||
在这种情况中,将会输出这个错误:
|
在这种情况中,将会输出这个错误:
|
||||||
|
|
||||||
> ERROR: "adb get-serialno" returned with value 1
|
> ERROR: "adb push" returned with value 1
|
||||||
|
|
||||||
这通常不是 _scrcpy_ 的bug,而是你的环境的问题。
|
这通常不是 _scrcpy_ 的bug,而是你的环境的问题。
|
||||||
|
|
||||||
@@ -38,37 +33,28 @@ adb devices
|
|||||||
|
|
||||||
### 设备未授权
|
### 设备未授权
|
||||||
|
|
||||||
|
参见这里 [stackoverflow][device-unauthorized].
|
||||||
> error: device unauthorized.
|
|
||||||
> This adb server's $ADB_VENDOR_KEYS is not set
|
|
||||||
> Try 'adb kill-server' if that seems wrong.
|
|
||||||
> Otherwise check for a confirmation dialog on your device.
|
|
||||||
|
|
||||||
连接时,在设备上应该会打开一个弹出窗口。 您必须授权 USB 调试。
|
|
||||||
|
|
||||||
如果没有打开,参见[stackoverflow][device-unauthorized].
|
|
||||||
|
|
||||||
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
||||||
|
|
||||||
|
|
||||||
### 未检测到设备
|
### 未检测到设备
|
||||||
|
|
||||||
> error: no devices/emulators found
|
> adb: error: failed to get feature set: no devices/emulators found
|
||||||
|
|
||||||
确认已经正确启用 [adb debugging][enable-adb].
|
确认已经正确启用 [adb debugging][enable-adb].
|
||||||
|
|
||||||
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver].
|
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上).
|
||||||
|
|
||||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||||
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
|
|
||||||
|
|
||||||
|
|
||||||
### 已连接多个设备
|
### 已连接多个设备
|
||||||
|
|
||||||
如果连接了多个设备,您将遇到以下错误:
|
如果连接了多个设备,您将遇到以下错误:
|
||||||
|
|
||||||
> error: more than one device/emulator
|
> adb: error: failed to get feature set: more than one device/emulator
|
||||||
|
|
||||||
必须提供要镜像的设备的标识符:
|
必须提供要镜像的设备的标识符:
|
||||||
|
|
||||||
@@ -104,19 +90,19 @@ scrcpy
|
|||||||
### 设备断开连接
|
### 设备断开连接
|
||||||
|
|
||||||
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
|
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
|
||||||
|
请尝试使用另一条USB线或者电脑上的另一个USB接口。
|
||||||
请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。
|
请参看 [#281] 和 [#283]。
|
||||||
|
|
||||||
[#281]: https://github.com/Genymobile/scrcpy/issues/281
|
[#281]: https://github.com/Genymobile/scrcpy/issues/281
|
||||||
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
||||||
|
|
||||||
|
|
||||||
## 控制相关问题
|
## 控制相关问题
|
||||||
|
|
||||||
### 鼠标和键盘不起作用
|
### 鼠标和键盘不起作用
|
||||||
|
|
||||||
|
|
||||||
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
|
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
|
||||||
|
|
||||||
在开发者选项中,打开:
|
在开发者选项中,打开:
|
||||||
|
|
||||||
> **USB调试 (安全设置)**
|
> **USB调试 (安全设置)**
|
||||||
@@ -129,12 +115,10 @@ scrcpy
|
|||||||
|
|
||||||
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。
|
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。
|
||||||
|
|
||||||
自 Linux 上的 scrcpy v1.20 之后,可以模拟[物理键盘][hid] (HID)。
|
|
||||||
|
|
||||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||||
[hid]: README.md#physical-keyboard-simulation-hid
|
|
||||||
|
|
||||||
|
|
||||||
## 客户端相关问题
|
## 客户端相关问题
|
||||||
@@ -145,6 +129,7 @@ scrcpy
|
|||||||
|
|
||||||
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
||||||
|
|
||||||
|
|
||||||
为了提升降尺度的质量,如果渲染器是OpenGL并且支持mip映射,就会自动开启三线性过滤。
|
为了提升降尺度的质量,如果渲染器是OpenGL并且支持mip映射,就会自动开启三线性过滤。
|
||||||
|
|
||||||
在Windows上,你可能希望强制使用OpenGL:
|
在Windows上,你可能希望强制使用OpenGL:
|
||||||
@@ -192,7 +177,6 @@ scrcpy
|
|||||||
## 崩溃
|
## 崩溃
|
||||||
|
|
||||||
### 异常
|
### 异常
|
||||||
|
|
||||||
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
|
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
|
||||||
|
|
||||||
> ```
|
> ```
|
||||||
@@ -220,40 +204,12 @@ scrcpy -m 1024
|
|||||||
scrcpy -m 800
|
scrcpy -m 800
|
||||||
```
|
```
|
||||||
|
|
||||||
自 scrcpy v1.22以来,scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。
|
|
||||||
|
|
||||||
你也可以尝试另一种 [编码器](README.md#encoder)。
|
你也可以尝试另一种 [编码器](README.md#encoder)。
|
||||||
|
|
||||||
|
|
||||||
如果您在 Android 12 上遇到此异常,则只需升级到 scrcpy >= 1.18 (见 [#2129]):
|
|
||||||
|
|
||||||
```
|
|
||||||
> ERROR: Exception on thread Thread[main,5,main]
|
|
||||||
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
|
|
||||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
|
|
||||||
...
|
|
||||||
Caused by: java.lang.reflect.InvocationTargetException
|
|
||||||
at java.lang.reflect.Method.invoke(Native Method)
|
|
||||||
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
|
|
||||||
... 7 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
|
|
||||||
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
|
|
||||||
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
|
|
||||||
... 9 more
|
|
||||||
```
|
|
||||||
|
|
||||||
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
|
|
||||||
|
|
||||||
|
|
||||||
## Windows命令行
|
## Windows命令行
|
||||||
|
|
||||||
从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如:
|
一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`:
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --record file.mkv
|
|
||||||
```
|
|
||||||
|
|
||||||
您也可以打开终端并手动转到 scrcpy 文件夹:
|
|
||||||
|
|
||||||
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
|
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
|
||||||
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
|
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
|
||||||
@@ -277,7 +233,7 @@ scrcpy --record file.mkv
|
|||||||
scrcpy --prefer-text --turn-screen-off --stay-awake
|
scrcpy --prefer-text --turn-screen-off --stay-awake
|
||||||
```
|
```
|
||||||
|
|
||||||
然后只需双击刚刚创建的文件。
|
然后双击刚刚创建的文件。
|
||||||
|
|
||||||
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。
|
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
|||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
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
|
#### Wireless
|
||||||
|
|
||||||
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan `adb` dapat [terhubung] ke perangkat melalui TCP / IP:
|
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP:
|
||||||
|
|
||||||
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
|
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
|
||||||
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
|
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
|
||||||
@@ -281,7 +281,7 @@ Dari terminal lain:
|
|||||||
scrcpy
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan `-R`):
|
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb kill-server # matikan server adb lokal di 5037
|
adb kill-server # matikan server adb lokal di 5037
|
||||||
@@ -579,7 +579,7 @@ Lihat juga [Masalah #14].
|
|||||||
|
|
||||||
Dalam daftar berikut, <kbd>MOD</kbd> adalah pengubah pintasan. Secara default, ini (kiri) <kbd>Alt</kbd> atau (kiri) <kbd>Super</kbd>.
|
Dalam daftar berikut, <kbd>MOD</kbd> adalah pengubah pintasan. Secara default, ini (kiri) <kbd>Alt</kbd> atau (kiri) <kbd>Super</kbd>.
|
||||||
|
|
||||||
Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` dan `rsuper`. Sebagai contoh:
|
Ini dapat diubah menggunakan `--shortcut-mod`. Kunci yang memungkinkan adalah `lctrl`,`rctrl`, `lalt`,` ralt`, `lsuper` dan` rsuper`. Sebagai contoh:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# gunakan RCtrl untuk jalan pintas
|
# gunakan RCtrl untuk jalan pintas
|
||||||
@@ -672,7 +672,7 @@ Baca [halaman pengembang].
|
|||||||
## Lisensi
|
## Lisensi
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
443
README.it.md
443
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.17)
|
||||||
|
|
||||||
# scrcpy (v1.23)
|
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
|
||||||
|
|
||||||
_si pronuncia "**scr**een **c**o**py**"_
|
|
||||||
|
|
||||||
[Leggi in altre lingue](#traduzioni)
|
|
||||||
|
|
||||||
Questa applicazione fornisce la visualizzazione e il controllo di dispositivi Android collegati via USB (o [via TCP/IP](#tcpip-wireless)). Non richiede alcun accesso _root_.
|
|
||||||
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Si concentra su:
|
Si concentra su:
|
||||||
|
|
||||||
- **leggerezza**: nativo, mostra solo lo schermo del dispositivo
|
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
|
||||||
- **prestazioni**: 30~120fps, in funzione del dispositivo
|
- **prestazioni** (30~60fps)
|
||||||
- **qualità**: 1920×1080 o superiore
|
- **qualità** (1920×1080 o superiore)
|
||||||
- **bassa latenza**: [35~70ms][lowlatency]
|
- **bassa latenza** ([35~70ms][lowlatency])
|
||||||
- **tempo di avvio basso**: ~ 1secondo per visualizzare la prima immagine
|
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
|
||||||
- **non invadenza**: nulla rimane installato sul dispositivo
|
- **non invadenza** (nulla viene lasciato installato sul dispositivo)
|
||||||
- **vantaggi per l'utente**: nessun account, nessuna pubblicità, non è richiesta alcuna connessione a internet
|
|
||||||
- **libertà**: software libero e a codice aperto (_free and open source_)
|
|
||||||
|
|
||||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
Le sue caratteristiche includono:
|
|
||||||
- [registrazione](#registrazione)
|
|
||||||
- mirroring con [schermo del dispositivo spento](#spegnere-lo-schermo)
|
|
||||||
- [copia-incolla](#copia-incolla) in entrambe le direzioni
|
|
||||||
- [qualità configurabile](#configurazione-di-acquisizione)
|
|
||||||
- schermo del dispositivo [come webcam (V4L2)](#v4l2loopback) (solo per Linux)
|
|
||||||
- [simulazione della tastiera fisica (HID)](#simulazione-della-tastiera-fisica-HID)
|
|
||||||
- [simulazione mouse fisico (HID)](#simulazione-del-mouse-fisico-HID)
|
|
||||||
- [modalità OTG](#otg)
|
|
||||||
- e altro ancora...
|
|
||||||
|
|
||||||
|
|
||||||
## Requisiti
|
## Requisiti
|
||||||
|
|
||||||
@@ -68,18 +49,12 @@ Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_si
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
Su Debian e Ubuntu:
|
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install scrcpy
|
apt install scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
Su Arch Linux:
|
|
||||||
|
|
||||||
```
|
|
||||||
pacman -S scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
|
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
|
||||||
|
|
||||||
[snap-link]: https://snapstats.org/snaps/scrcpy
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
@@ -91,6 +66,10 @@ Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link].
|
|||||||
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
|
Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
|
Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
@@ -163,7 +142,7 @@ Collega un dispositivo Android ed esegui:
|
|||||||
scrcpy
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
Scrcpy accetta argomenti da riga di comando, elencati con:
|
Scrcpy accetta argomenti da riga di comando, essi sono listati con:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --help
|
scrcpy --help
|
||||||
@@ -207,14 +186,6 @@ scrcpy --max-fps 15
|
|||||||
|
|
||||||
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
|
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
|
||||||
|
|
||||||
L'attuale frame rate di acquisizione può essere stampato sulla console:
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --print-fps
|
|
||||||
```
|
|
||||||
|
|
||||||
Può anche essere abilitato o disabilitato in qualsiasi momento con <kbd>MOD</kbd>+<kbd>i</kbd>.
|
|
||||||
|
|
||||||
#### Ritaglio
|
#### Ritaglio
|
||||||
|
|
||||||
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
|
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
|
||||||
@@ -234,11 +205,10 @@ Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il
|
|||||||
Per bloccare l'orientamento della trasmissione:
|
Per bloccare l'orientamento della trasmissione:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # orientamento iniziale (corrente)
|
scrcpy --lock-video-orientation 0 # orientamento naturale
|
||||||
scrcpy --lock-video-orientation=0 # orientamento naturale
|
scrcpy --lock-video-orientation 1 # 90° antiorario
|
||||||
scrcpy --lock-video-orientation=1 # 90° antiorario
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
scrcpy --lock-video-orientation=2 # 180°
|
scrcpy --lock-video-orientation 3 # 90° orario
|
||||||
scrcpy --lock-video-orientation=3 # 90° orario
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Questo influisce sull'orientamento della registrazione.
|
Questo influisce sull'orientamento della registrazione.
|
||||||
@@ -261,9 +231,7 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cattura
|
### Registrazione
|
||||||
|
|
||||||
#### Registrazione
|
|
||||||
|
|
||||||
È possibile registrare lo schermo durante la trasmissione:
|
È possibile registrare lo schermo durante la trasmissione:
|
||||||
|
|
||||||
@@ -285,137 +253,38 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
#### v4l2loopback
|
|
||||||
|
|
||||||
Su Linux è possibile inviare il flusso video ad un dispositivo v4l2 loopback, cosicché un dispositivo Android possa essere aperto come una webcam da qualsiasi strumento compatibile con v4l2.
|
|
||||||
|
|
||||||
Il modulo `v4l2loopback` deve essere installato:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install v4l2loopback-dkms
|
|
||||||
```
|
|
||||||
|
|
||||||
Per creare un dispositvo v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo modprobe v4l2loopback
|
|
||||||
```
|
|
||||||
|
|
||||||
Questo creerà un nuovo dispositivo video in `/dev/videoN` dove `N` è un intero (più [opzioni](https://github.com/umlaeute/v4l2loopback#options) sono disponibili per crere più dispositivi o dispositivi con ID specifici).
|
|
||||||
|
|
||||||
Per elencare i dispositvi attivati:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# necessita del pacchetto v4l-utils
|
|
||||||
v4l2-ctl --list-devices
|
|
||||||
|
|
||||||
# semplice ma potrebbe essere sufficiente
|
|
||||||
ls /dev/video*
|
|
||||||
```
|
|
||||||
|
|
||||||
Per avviare scrcpy utilizzando un v4l2 sink:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # disabilita la finestra di trasmissione
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # versione corta
|
|
||||||
```
|
|
||||||
|
|
||||||
(sostituisci `N` con l'ID del dispositivo, controlla con `ls /dev/video*`)
|
|
||||||
|
|
||||||
Una volta abilitato, puoi aprire il tuo flusso video con uno strumento compatibile con v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ffplay -i /dev/videoN
|
|
||||||
vlc v4l2:///dev/videoN # VLC potrebbe aggiungere del ritardo per il buffer
|
|
||||||
```
|
|
||||||
|
|
||||||
Per esempio potresti catturare il video in [OBS].
|
|
||||||
|
|
||||||
[OBS]: https://obsproject.com/
|
|
||||||
|
|
||||||
|
|
||||||
#### Buffering
|
|
||||||
|
|
||||||
È possibile aggiungere del buffer. Questo aumenta la latenza ma riduce il jitter (vedi [#2464]).
|
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
L'opzione è disponibile per il buffer della visualizzazione:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
|
|
||||||
```
|
|
||||||
|
|
||||||
e per il V4L2 sink:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-buffer=500 # aggiungi 500 ms di buffer per il v4l2 sink
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Connessione
|
### Connessione
|
||||||
|
|
||||||
#### TCP/IP (wireless)
|
#### Wireless
|
||||||
|
|
||||||
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] a un dispositivo mediante TCP/IP. Il dispositivo deve essere collegato alla stessa rete del computer.
|
|
||||||
|
|
||||||
##### Automatico
|
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP:
|
||||||
|
|
||||||
Un'opzione `--tcpip` permette di configurare automaticamente la connessione. Ci sono due varianti.
|
1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
|
||||||
|
2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando:
|
||||||
Se il dispositivo (accessibile a 192.168.1.1 in questo esempio) ascolta già su una porta (tipicamente 5555) per le connessioni adb in entrata, allora esegui:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip=192.168.1.1 # la porta predefinita è 5555
|
|
||||||
scrcpy --tcpip=192.168.1.1:5555
|
|
||||||
```
|
|
||||||
|
|
||||||
Se la modalità TCP/IP di adb è disabilitata sul dispositivo (o se non si conosce l'indirizzo IP indirizzo), collegare il dispositivo tramite USB, quindi eseguire:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip # senza argomenti
|
|
||||||
```
|
|
||||||
|
|
||||||
Il comando troverà automaticamente l'indirizzo IP del dispositivo, abiliterà la modalità TCP/IP, quindi connettersi al dispositivo prima di iniziare.
|
|
||||||
|
|
||||||
##### Manuale
|
|
||||||
|
|
||||||
In alternativa, è possibile abilitare la connessione TCP/IP manualmente usando `adb`:
|
|
||||||
|
|
||||||
1. Inserisci il dispositivo in una porta USB del tuo computer.
|
|
||||||
2. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
|
|
||||||
3. Ottieni l'indirizzo IP del tuo dispositivo, in Impostazioni → Informazioni sul telefono → Stato, o
|
|
||||||
eseguendo questo comando:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb shell ip route | awk '{print $9}'
|
adb shell ip route | awk '{print $9}'
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
|
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
|
||||||
5. Scollega il tuo dispositivo.
|
4. Scollega il tuo dispositivo.
|
||||||
6. Connettiti al tuo dispositivo: `adb connect DEVICE_IP:5555` _(sostituisci `DEVICE_IP`
|
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
|
||||||
con l'indirizzo IP del dispositivo che hai trovato)_.
|
6. Esegui `scrcpy` come al solito.
|
||||||
7. Esegui `scrcpy` come al solito.
|
|
||||||
|
|
||||||
Da Android 11, una [opzione di debug wireless][adb-wireless] permette di evitare di dover collegare fisicamente il dispositivo direttamente al computer.
|
Potrebbe essere utile diminuire il bit-rate e la definizione
|
||||||
|
|
||||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
|
|
||||||
|
|
||||||
Se la connessione cade casualmente, esegui il comando `scrcpy` per riconnetterti. Se il comando dice che non ci sono dispositivi/emulatori trovati, prova ad eseguire `adb connect DEVICE_IP:5555` di nuovo, e poi `scrcpy` come al solito. Se dice ancora che non ne ha trovato nessuno, prova ad eseguire `adb disconnect` e poi esegui di nuovo questi due comandi.
|
|
||||||
|
|
||||||
Potrebbe essere utile diminuire il bit-rate e la definizione:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M --max-size 800
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
scrcpy -b2M -m800 # versione breve
|
scrcpy -b2M -m800 # versione breve
|
||||||
```
|
```
|
||||||
|
|
||||||
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||||
|
|
||||||
|
|
||||||
#### Multi dispositivo
|
#### Multi dispositivo
|
||||||
|
|
||||||
Se in `adb devices` sono elencati più dispositivi, è necessario specificare il _seriale_:
|
Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --serial 0123456789abcdef
|
scrcpy --serial 0123456789abcdef
|
||||||
@@ -429,18 +298,6 @@ scrcpy --serial 192.168.0.1:5555
|
|||||||
scrcpy -s 192.168.0.1:5555 # versione breve
|
scrcpy -s 192.168.0.1:5555 # versione breve
|
||||||
```
|
```
|
||||||
|
|
||||||
Se solo un dispositivo è collegato via USB o TCP/IP, è possibile selezionarlo automaticamente:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Select the only device connected via USB
|
|
||||||
scrcpy -d # like adb -d
|
|
||||||
scrcpy --select-usb # long version
|
|
||||||
|
|
||||||
# Select the only device connected via TCP/IP
|
|
||||||
scrcpy -e # like adb -e
|
|
||||||
scrcpy --select-tcpip # long version
|
|
||||||
```
|
|
||||||
|
|
||||||
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
|
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
|
||||||
|
|
||||||
|
|
||||||
@@ -454,77 +311,37 @@ autoadb scrcpy -s '{}'
|
|||||||
|
|
||||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
#### Tunnels
|
#### Tunnel SSH
|
||||||
|
|
||||||
Per connettersi a un dispositivo remoto, è possibile collegare un client `adb` locale a un server remoto `adb` (purché usino la stessa versione del protocollo _adb_). ).
|
Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_):
|
||||||
|
|
||||||
##### Server ADB remoto
|
|
||||||
|
|
||||||
Per connettersi a un server ADB remoto, fate ascoltare il server su tutte le interfacce:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb kill-server
|
adb kill-server # termina il server adb locale su 5037
|
||||||
adb -a nodaemon server start
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
# tienilo aperto
|
# tieni questo aperto
|
||||||
```
|
```
|
||||||
|
|
||||||
**Attenzione: tutte le comunicazioni tra i client e il server ADB non sono criptate.**
|
Da un altro terminale:
|
||||||
|
|
||||||
Supponi che questo server sia accessibile a 192.168.1.2. Poi, da un altro terminale, esegui scrcpy:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
|
||||||
scrcpy --tunnel-host=192.168.1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
Per impostazione predefinita, scrcpy utilizza la porta locale utilizzata per il tunnel `adb forward` (tipicamente `27183`, vedi `--port`). È anche possibile forzare una diversa porta del tunnel (può essere utile in situazioni più complesse, quando sono coinvolti più reindirizzamenti):
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --tunnel-port=1234
|
|
||||||
```
|
|
||||||
|
|
||||||
##### SSH tunnel
|
|
||||||
|
|
||||||
Per comunicare con un server ADB remoto in modo sicuro, è preferibile utilizzare un tunnel SSH.
|
|
||||||
|
|
||||||
Per prima cosa, assicurati che il server ADB sia in esecuzione sul computer remoto:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb start-server
|
|
||||||
```
|
|
||||||
|
|
||||||
Poi, crea un tunnel SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# local 5038 --> remote 5037
|
|
||||||
# local 27183 <-- remote 27183
|
|
||||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
|
||||||
# keep this open
|
|
||||||
```
|
|
||||||
|
|
||||||
Da un altro terminale, esegui scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
|
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# local 5038 --> remote 5037
|
adb kill-server # termina il server adb locale su 5037
|
||||||
# local 27183 --> remote 27183
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
|
||||||
# tieni questo aperto
|
# tieni questo aperto
|
||||||
```
|
```
|
||||||
|
|
||||||
Da un altro terminale, esegui scrcpy:
|
Da un altro terminale:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy --force-adb-forward
|
scrcpy --force-adb-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
|
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -662,15 +479,16 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw
|
scrcpy -Sw
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Spegnimento alla chiusura
|
#### Renderizzare i fotogrammi scaduti
|
||||||
|
|
||||||
Per spegnere lo schermo del dispositivo quando si chiude scrcpy:
|
Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti.
|
||||||
|
|
||||||
|
Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --power-off-on-close
|
scrcpy --render-expired-frames
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Mostrare i tocchi
|
#### Mostrare i tocchi
|
||||||
|
|
||||||
Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico).
|
Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico).
|
||||||
@@ -715,22 +533,20 @@ Qualsiasi scorciatoia <kbd>Ctrl</kbd> viene inoltrata al dispositivo. In partico
|
|||||||
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
|
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
|
||||||
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
||||||
|
|
||||||
Questo solitamente funziona come ci si aspetta.
|
Questo solitamente funziona nella maniera più comune.
|
||||||
|
|
||||||
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
|
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
|
||||||
|
|
||||||
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
|
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
|
||||||
- <kbd>MOD</kbd>+<kbd>c</kbd> invia `COPY`
|
- <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
|
||||||
- <kbd>MOD</kbd>+<kbd>x</kbd> invia `CUT`
|
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
|
||||||
- <kbd>MOD</kbd>+<kbd>v</kbd> invia `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
- <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
||||||
|
|
||||||
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'invio del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può compromettere il contenuto non ASCII.
|
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII.
|
||||||
|
|
||||||
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
|
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
|
||||||
|
|
||||||
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi inviino il testo degli appunti del computer come una sequenza di eventi di pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
Per disabilitare la sincronizzazione automatica degli appunti, usa `--no-clipboard-autosync`.
|
|
||||||
|
|
||||||
#### Pizzica per zoomare (pinch-to-zoom)
|
#### Pizzica per zoomare (pinch-to-zoom)
|
||||||
|
|
||||||
@@ -738,98 +554,16 @@ Per simulare il "pizzica per zoomare": <kbd>Ctrl</kbd>+_click e trascina_.
|
|||||||
|
|
||||||
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
|
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
|
||||||
|
|
||||||
Concretamente, scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
|
Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
|
||||||
|
|
||||||
#### Simulazione della tastiera fisica (HID)
|
|
||||||
|
|
||||||
Per impostazione predefinita, scrcpy utilizza l'invio dei tasti o del testo di Android: funziona ovunque, ma è limitato all'ASCII.
|
|
||||||
|
|
||||||
In alternativa scrcpy può simulare una tastiera fisica USB su Android per fornire una migliore esperienza di input (utilizzando [USB HID over AOAv2][hid-aoav2]): la tastiera virtuale è disabilitata e funziona per tutti i caratteri e IME.
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
Tuttavia, funziona solo se il dispositivo è collegato via USB.
|
|
||||||
|
|
||||||
Nota: su Windows, può funzionare solo in [odalità OTG](#otg), non durante il mirroring (non è possibile aprire un dispositivo USB se è già aperto da un altro processo come il daemon adb).
|
|
||||||
|
|
||||||
Per abilitare questa modalità:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # versione breve
|
|
||||||
```
|
|
||||||
|
|
||||||
Se fallisce per qualche motivo (per esempio perché il dispositivo non è connesso via USB), ritorna automaticamente alla modalità predefinita (con un log nella console). Questo permette di usare le stesse opzioni della linea di comando quando si è connessi via USB e TCP/IP.
|
|
||||||
|
|
||||||
In questa modalità, gli eventi i pressione originali (scancodes) sono inviati al dispositivo, indipendentemente dalla mappatura dei tasti dell'host. Pertanto, se il layout della tua tastiera non corrisponde, deve essere configurato sul dispositivo Android, in Impostazioni → Sistema → Lingue e input → [Tastiera fisica] (in inglese).
|
|
||||||
|
|
||||||
Questa pagina di impostazioni può essere avviata direttamente:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
|
||||||
```
|
|
||||||
|
|
||||||
Tuttavia, l'opzione è disponibile solo quando la tastiera HID è abilitata (o quando una tastiera fisica è collegata).
|
|
||||||
|
|
||||||
[Tastiera fisica]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
#### Simulazione del mouse fisico (HID)
|
|
||||||
|
|
||||||
In modo simile alla simulazione della tastiera fisica, è possibile simulare un mouse fisico. Allo stesso modo funziona solo se il dispositivo è connesso via USB.
|
|
||||||
|
|
||||||
Per impostazione predefinita, scrcpy utilizza l'invio degli eventi del mouse di Android, utilizzando coordinate assolute. Simulando un mouse fisico, un puntatore del mouse appare sul dispositivo Android e vengono inviati i movimenti relativi del mouse, i click e gli scorrimenti.
|
|
||||||
|
|
||||||
Per abilitare questa modalità:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-mouse
|
|
||||||
scrcpy -M # versione breve
|
|
||||||
```
|
|
||||||
|
|
||||||
Si potrebbe anche aggiungere `--forward-all-clicks` a [inoltra tutti i pulsanti del mouse][forward_all_clicks].
|
|
||||||
|
|
||||||
[forward_all_clicks]: #click-destro-e-click-centrale
|
|
||||||
|
|
||||||
|
|
||||||
Quando questa modalità è attivata, il mouse del computer viene "catturato" (il puntatore del mouse scompare dal computer e appare invece sul dispositivo Android).
|
#### Preferenze di iniezione del testo
|
||||||
|
|
||||||
I tasti speciali di cattura, <kbd>Alt</kbd> o <kbd>Super</kbd>, commutano (disabilitano o abilitano) la cattura del mouse. Usa uno di essi per ridare il controllo del mouse al computer.
|
|
||||||
|
|
||||||
|
|
||||||
#### OTG
|
|
||||||
|
|
||||||
È possibile eseguire _scrcpy_ con la sola simulazione della tastiera fisica e del mouse (HID), come se la tastiera e il mouse del computer fossero collegati direttamente al dispositivo tramite un cavo OTG.
|
|
||||||
|
|
||||||
In questa modalità, _adb_ (debug USB) non è necessario e il mirroring è disabilitato.
|
|
||||||
|
|
||||||
Per attivare la modallità OTG:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg
|
|
||||||
# Passa la seriale se sono disponibili diversi dispositivi USB
|
|
||||||
scrcpy --otg -s 0123456789abcdef
|
|
||||||
```
|
|
||||||
|
|
||||||
È possibile abilitare solo la tastiera HID o il mouse HID:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg --hid-keyboard # solo la tastiera
|
|
||||||
scrcpy --otg --hid-mouse # solo mouse
|
|
||||||
scrcpy --otg --hid-keyboard --hid-mouse # tastiera e mouse
|
|
||||||
# per comodità, abilita entrambi per default
|
|
||||||
scrcpy --otg # tastiera e mouse
|
|
||||||
```
|
|
||||||
|
|
||||||
Come `--hid-keyboard` e `--hid-mouse`, funziona solo se il dispositivo è collegato via USB.
|
|
||||||
|
|
||||||
|
|
||||||
#### Preferenze di invio del testo
|
|
||||||
|
|
||||||
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
|
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
|
||||||
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
|
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
|
||||||
- _eventi di testo_, segnalano che del testo è stato inserito.
|
- _eventi di testo_, segnalano che del testo è stato inserito.
|
||||||
|
|
||||||
In maniera predefinita le lettere sono inviate usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
|
In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
|
||||||
|
|
||||||
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
|
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
|
||||||
|
|
||||||
@@ -839,21 +573,13 @@ scrcpy --prefer-text
|
|||||||
|
|
||||||
(ma questo romperà il normale funzionamento della tastiera nei giochi)
|
(ma questo romperà il normale funzionamento della tastiera nei giochi)
|
||||||
|
|
||||||
Al contrario, si potrebbe forzare per inviare sempre eventi di pressione grezzi:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --raw-key-events
|
|
||||||
```
|
|
||||||
|
|
||||||
Queste opzioni non hanno effetto sulla tastiera HID (tutti gli eventi di pressione sono inviati come scancodes in questa modalità).
|
|
||||||
|
|
||||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
#### Ripetizione di tasti
|
#### Ripetizione di tasti
|
||||||
|
|
||||||
In maniera predefinita, tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
|
In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
|
||||||
|
|
||||||
Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
||||||
|
|
||||||
@@ -861,12 +587,9 @@ Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
|||||||
scrcpy --no-key-repeat
|
scrcpy --no-key-repeat
|
||||||
```
|
```
|
||||||
|
|
||||||
Questa opzione non ha effetto sulla tastiera HID (la ripetizione dei tasti è gestita da Android direttamente in questa modalità).
|
|
||||||
|
|
||||||
|
|
||||||
#### Click destro e click centrale
|
#### Click destro e click centrale
|
||||||
|
|
||||||
In maniera predefinita, click destro aziona BACK (indietro) o POWER on (accensione) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
|
In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --forward-all-clicks
|
scrcpy --forward-all-clicks
|
||||||
@@ -884,14 +607,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console.
|
|||||||
|
|
||||||
#### Trasferimento di file verso il dispositivo
|
#### Trasferimento di file verso il dispositivo
|
||||||
|
|
||||||
Per trasferire un file in `/sdcard/Download` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
|
Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
|
||||||
|
|
||||||
Non c'è alcuna risposta visiva, un log è stampato nella console.
|
Non c'è alcuna risposta visiva, un log è stampato nella console.
|
||||||
|
|
||||||
La cartella di destinazione può essere cambiata all'avvio:
|
La cartella di destinazione può essere cambiata all'avvio:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Movies/
|
scrcpy --push-target=/sdcard/Download/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -919,7 +642,7 @@ scrcpy --shortcut-mod=rctrl
|
|||||||
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||||
```
|
```
|
||||||
|
|
||||||
_<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
_<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
||||||
|
|
||||||
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows
|
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows
|
||||||
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
|
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
|
||||||
@@ -930,11 +653,11 @@ _<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kb
|
|||||||
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
|
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
|
||||||
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
||||||
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click sinistro¹_
|
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click¹_
|
||||||
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
|
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
|
||||||
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
|
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
|
||||||
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
|
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
| Premi il tasto `MENU` (sblocca lo schermo)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
|
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
|
||||||
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
||||||
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
@@ -942,29 +665,18 @@ _<kbd>[Super]</kbd> è solitamente il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kb
|
|||||||
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
|
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
|
| Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Copia negli appunti⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
| Taglia negli appunti⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Sincronizza gli appunti e incolla³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Sincronizza gli appunti e incolla⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Invia il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
|
||||||
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
||||||
| Trascina file APK | Installa APK dal computer
|
|
||||||
| Trascina file non-APK | [Trasferisci file verso il dispositivo](#push-file-to-device)
|
|
||||||
|
|
||||||
_¹Doppio click sui bordi neri per rimuoverli._
|
_¹Doppio click sui bordi neri per rimuoverli._
|
||||||
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
||||||
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._
|
_³Solo in Android >= 7._
|
||||||
_⁴Per le app native react in sviluppo, `MENU` attiva il menu di sviluppo._
|
|
||||||
_⁵Solo in Android >= 7._
|
|
||||||
|
|
||||||
Le scorciatoie con pulsanti ripetuti sono eseguite rilasciando e premendo il pulsante una seconda volta. Per esempio, per eseguire "Espandi il pannello delle impostazioni":
|
|
||||||
|
|
||||||
1. Premi e tieni premuto <kbd>MOD</kbd>.
|
|
||||||
2. Poi premi due volte <kbd>n</kbd>.
|
|
||||||
3. Infine rilascia <kbd>MOD</kbd>.
|
|
||||||
|
|
||||||
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
|
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
|
||||||
|
|
||||||
@@ -1007,7 +719,7 @@ Leggi la [pagina per sviluppatori].
|
|||||||
## Licenza (in inglese)
|
## Licenza (in inglese)
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -1028,14 +740,3 @@ Leggi la [pagina per sviluppatori].
|
|||||||
|
|
||||||
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
|
|
||||||
## Contatti
|
|
||||||
|
|
||||||
Se incontri un bug, per favore leggi prima le [FAQ](FAQ.it.md), poi apri una [issue].
|
|
||||||
|
|
||||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
|
||||||
|
|
||||||
Per domande generali o discussioni, puoi anche usare:
|
|
||||||
|
|
||||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
|
||||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
|
||||||
|
|||||||
134
README.jp.md
134
README.jp.md
@@ -1,6 +1,6 @@
|
|||||||
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
||||||
|
|
||||||
# scrcpy (v1.19)
|
# scrcpy (v1.17)
|
||||||
|
|
||||||
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux_ 、 _Windows_ そして _macOS_ 上で動作します。
|
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux_ 、 _Windows_ そして _macOS_ 上で動作します。
|
||||||
|
|
||||||
@@ -103,22 +103,19 @@ scoop install adb # まだ入手していない場合
|
|||||||
brew install scrcpy
|
brew install scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
`PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。
|
`PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install android-platform-tools
|
# Homebrew >= 2.6.0
|
||||||
|
brew install --cask android-platform-tools
|
||||||
|
|
||||||
|
# Homebrew < 2.6.0
|
||||||
|
brew cask install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
`adb`は[MacPorts]からでもインストールできます。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo port install scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
[MacPorts]: https://www.macports.org/
|
|
||||||
|
|
||||||
また、[アプリケーションをビルド][BUILD]することも可能です。
|
また、[アプリケーションをビルド][BUILD]することも可能です。
|
||||||
|
|
||||||
|
|
||||||
## 実行
|
## 実行
|
||||||
|
|
||||||
Androidデバイスを接続し、実行:
|
Androidデバイスを接続し、実行:
|
||||||
@@ -187,11 +184,10 @@ scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440
|
|||||||
ミラーリングの向きをロックするには:
|
ミラーリングの向きをロックするには:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # 現在の向き
|
scrcpy --lock-video-orientation 0 # 自然な向き
|
||||||
scrcpy --lock-video-orientation=0 # 自然な向き
|
scrcpy --lock-video-orientation 1 # 90°反時計回り
|
||||||
scrcpy --lock-video-orientation=1 # 90°反時計回り
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
scrcpy --lock-video-orientation=2 # 180°
|
scrcpy --lock-video-orientation 3 # 90°時計回り
|
||||||
scrcpy --lock-video-orientation=3 # 90°時計回り
|
|
||||||
```
|
```
|
||||||
|
|
||||||
この設定は録画の向きに影響します。
|
この設定は録画の向きに影響します。
|
||||||
@@ -214,9 +210,7 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### キャプチャ
|
### 録画
|
||||||
|
|
||||||
#### 録画
|
|
||||||
|
|
||||||
ミラーリング中に画面の録画をすることが可能です:
|
ミラーリング中に画面の録画をすることが可能です:
|
||||||
|
|
||||||
@@ -239,77 +233,6 @@ scrcpy -Nr file.mkv
|
|||||||
|
|
||||||
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
#### v4l2loopback
|
|
||||||
|
|
||||||
Linuxでは、ビデオストリームをv4l2ループバックデバイスに送信することができます。
|
|
||||||
v4l2loopbackのデバイスにビデオストリームを送信することで、Androidデバイスをウェブカメラのようにv4l2対応ツールで開くこともできます。
|
|
||||||
|
|
||||||
`v4l2loopback` モジュールのインストールが必要です。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install v4l2loopback-dkms
|
|
||||||
```
|
|
||||||
|
|
||||||
v4l2デバイスを作成する。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo modprobe v4l2loopback
|
|
||||||
```
|
|
||||||
|
|
||||||
これにより、新しいビデオデバイスが `/dev/videoN` に作成されます。(`N` は整数)
|
|
||||||
(複数のデバイスや特定のIDのデバイスを作成するために、より多くの[オプション](https://github.com/umlaeute/v4l2loopback#options)が利用可能です。
|
|
||||||
多くの[オプション]()が利用可能で複数のデバイスや特定のIDのデバイスを作成できます。
|
|
||||||
|
|
||||||
|
|
||||||
有効なデバイスを一覧表示する:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# v4l-utilsパッケージが必要
|
|
||||||
v4l2-ctl --list-devices
|
|
||||||
|
|
||||||
# シンプルですが十分これで確認できます
|
|
||||||
ls /dev/video*
|
|
||||||
```
|
|
||||||
|
|
||||||
v4l2シンクを使用してscrcpyを起動する。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # ミラーリングウィンドウを無効化する
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # 短縮版
|
|
||||||
```
|
|
||||||
|
|
||||||
(`N` をデバイス ID に置き換えて、`ls /dev/video*` で確認してください)
|
|
||||||
有効にすると、v4l2対応のツールでビデオストリームを開けます。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ffplay -i /dev/videoN
|
|
||||||
vlc v4l2:///dev/videoN # VLCではバッファリングの遅延が発生する場合があります
|
|
||||||
```
|
|
||||||
|
|
||||||
例えばですが [OBS]の中にこの映像を取り込めことができます。
|
|
||||||
|
|
||||||
[OBS]: https://obsproject.com/
|
|
||||||
|
|
||||||
|
|
||||||
#### Buffering
|
|
||||||
|
|
||||||
バッファリングを追加することも可能です。これによりレイテンシーは増加しますが、ジッターは減少します。(参照
|
|
||||||
[#2464])
|
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
このオプションでディスプレイバッファリングを設定できます。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --display-buffer=50 # ディスプレイに50msのバッファリングを追加する
|
|
||||||
```
|
|
||||||
|
|
||||||
V4L2の場合はこちらのオプションで設定できます。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
|
|
||||||
```
|
|
||||||
|
|
||||||
### 接続
|
### 接続
|
||||||
|
|
||||||
@@ -534,6 +457,16 @@ scrcpy -Sw
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### 期限切れフレームをレンダリングする
|
||||||
|
|
||||||
|
初期状態では、待ち時間を最小限にするために、_scrcpy_ は最後にデコードされたフレームをレンダリングし、前のフレームを削除します。
|
||||||
|
|
||||||
|
全フレームのレンダリングを強制するには(待ち時間が長くなる可能性があります):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --render-expired-frames
|
||||||
|
```
|
||||||
|
|
||||||
#### タッチを表示
|
#### タッチを表示
|
||||||
|
|
||||||
プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。
|
プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。
|
||||||
@@ -653,14 +586,14 @@ APKをインストールするには、(`.apk`で終わる)APKファイルを _s
|
|||||||
|
|
||||||
#### デバイスにファイルを送る
|
#### デバイスにファイルを送る
|
||||||
|
|
||||||
デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。
|
デバイスの`/sdcard/`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。
|
||||||
|
|
||||||
見た目のフィードバックはありません。コンソールにログが出力されます。
|
見た目のフィードバックはありません。コンソールにログが出力されます。
|
||||||
|
|
||||||
転送先ディレクトリを起動時に変更することができます:
|
転送先ディレクトリを起動時に変更することができます:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Movies/
|
scrcpy --push-target /sdcard/foo/bar/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -701,7 +634,7 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
|
|||||||
| ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_
|
| ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_
|
||||||
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
|
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
|
||||||
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
|
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
|
||||||
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4クリック³_
|
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
|
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
|
||||||
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
|
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
|
||||||
@@ -710,8 +643,7 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
|
|||||||
| デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| 通知パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5ボタンクリック³_
|
| 通知パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
| 設定パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _5ダブルクリック³_
|
|
||||||
| 通知パネルを折りたたむ | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| 通知パネルを折りたたむ | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
@@ -722,17 +654,11 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
|
|||||||
|
|
||||||
_¹黒い境界線を削除するため、境界線上でダブルクリック_
|
_¹黒い境界線を削除するため、境界線上でダブルクリック_
|
||||||
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
|
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
|
||||||
_³4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._
|
_³Android 7以上のみ._
|
||||||
_⁴Android 7以上のみ._
|
|
||||||
|
|
||||||
キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。
|
|
||||||
|
|
||||||
1. <kbd>MOD</kbd> キーを押し、押したままにする.
|
|
||||||
2. その後に <kbd>n</kbd>キーを2回押す.
|
|
||||||
3. 最後に <kbd>MOD</kbd>キーを離す.
|
|
||||||
|
|
||||||
全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。
|
全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。
|
||||||
|
|
||||||
|
|
||||||
## カスタムパス
|
## カスタムパス
|
||||||
|
|
||||||
特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します:
|
特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します:
|
||||||
@@ -776,7 +702,7 @@ _⁴Android 7以上のみ._
|
|||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
|
|||||||
## 라이선스
|
## 라이선스
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
495
README.md
495
README.md
@@ -1,51 +1,35 @@
|
|||||||
# scrcpy (v1.24)
|
# scrcpy (v1.19)
|
||||||
|
|
||||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
|
||||||
|
|
||||||
_pronounced "**scr**een **c**o**py**"_
|
|
||||||
|
|
||||||
[Read in another language](#translations)
|
[Read in another language](#translations)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected via
|
This application provides display and control of Android devices connected on
|
||||||
USB or [over TCP/IP](#tcpip-wireless). It does not require any _root_ access.
|
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||||
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It focuses on:
|
It focuses on:
|
||||||
|
|
||||||
- **lightness**: native, displays only the device screen
|
- **lightness** (native, displays only the device screen)
|
||||||
- **performance**: 30~120fps, depending on the device
|
- **performance** (30~60fps)
|
||||||
- **quality**: 1920×1080 or above
|
- **quality** (1920×1080 or above)
|
||||||
- **low latency**: [35~70ms][lowlatency]
|
- **low latency** ([35~70ms][lowlatency])
|
||||||
- **low startup time**: ~1 second to display the first image
|
- **low startup time** (~1 second to display the first image)
|
||||||
- **non-intrusiveness**: nothing is left installed on the Android device
|
- **non-intrusiveness** (nothing is left installed on the device)
|
||||||
- **user benefits**: no account, no ads, no internet required
|
|
||||||
- **freedom**: free and open source software
|
|
||||||
|
|
||||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
Its features include:
|
|
||||||
- [recording](#recording)
|
|
||||||
- mirroring with [Android device screen off](#turn-screen-off)
|
|
||||||
- [copy-paste](#copy-paste) in both directions
|
|
||||||
- [configurable quality](#capture-configuration)
|
|
||||||
- Android device [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
|
|
||||||
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
|
|
||||||
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
|
|
||||||
- [OTG mode](#otg)
|
|
||||||
- and more…
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
The Android device requires at least API 21 (Android 5.0).
|
The Android device requires at least API 21 (Android 5.0).
|
||||||
|
|
||||||
Make sure you [enable adb debugging][enable-adb] on your device(s).
|
Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
||||||
|
|
||||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
|
||||||
On some devices, you also need to enable [an additional option][control] to
|
On some devices, you also need to enable [an additional option][control] to
|
||||||
control it using a keyboard and mouse.
|
control it using keyboard and mouse.
|
||||||
|
|
||||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
@@ -68,18 +52,12 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple])
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
On Debian and Ubuntu:
|
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install scrcpy
|
apt install scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
On Arch Linux:
|
|
||||||
|
|
||||||
```
|
|
||||||
pacman -S scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
A [Snap] package is available: [`scrcpy`][snap-link].
|
A [Snap] package is available: [`scrcpy`][snap-link].
|
||||||
|
|
||||||
[snap-link]: https://snapstats.org/snaps/scrcpy
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
@@ -91,25 +69,29 @@ For Fedora, a [COPR] package is available: [`scrcpy`][copr-link].
|
|||||||
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
|
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
You can also [build the app manually][BUILD] ([simplified
|
You could also [build the app manually][BUILD] ([simplified
|
||||||
process][BUILD_simple]).
|
process][BUILD_simple]).
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
For Windows, a prebuilt archive with all the dependencies (including `adb`) is
|
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||||
available:
|
(including `adb`) is available:
|
||||||
|
|
||||||
- [`scrcpy-win64-v1.24.zip`][direct-win64]
|
- [`scrcpy-win64-v1.19.zip`][direct-win64]
|
||||||
<sub>SHA-256: `6ccb64cba0a3e75715e85a188daeb4f306a1985f8ce123eba92ba74fc9b27367`</sub>
|
_(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_
|
||||||
|
|
||||||
[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.19/scrcpy-win64-v1.19.zip
|
||||||
|
|
||||||
It is also available in [Chocolatey]:
|
It is also available in [Chocolatey]:
|
||||||
|
|
||||||
@@ -148,7 +130,7 @@ You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
|||||||
brew install android-platform-tools
|
brew install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
It's also available in [MacPorts], which sets up `adb` for you:
|
It's also available in [MacPorts], which sets up adb for you:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo port install scrcpy
|
sudo port install scrcpy
|
||||||
@@ -162,7 +144,7 @@ You can also [build the app manually][BUILD].
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Plug an Android device into your computer, and execute:
|
Plug an Android device, and execute:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy
|
scrcpy
|
||||||
@@ -180,7 +162,7 @@ scrcpy --help
|
|||||||
|
|
||||||
#### Reduce size
|
#### Reduce size
|
||||||
|
|
||||||
Sometimes, it is useful to mirror an Android device at a lower resolution to
|
Sometimes, it is useful to mirror an Android device at a lower definition to
|
||||||
increase performance.
|
increase performance.
|
||||||
|
|
||||||
To limit both the width and height to some value (e.g. 1024):
|
To limit both the width and height to some value (e.g. 1024):
|
||||||
@@ -190,8 +172,8 @@ scrcpy --max-size 1024
|
|||||||
scrcpy -m 1024 # short version
|
scrcpy -m 1024 # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
The other dimension is computed so that the Android device aspect ratio is
|
The other dimension is computed to that the device aspect ratio is preserved.
|
||||||
preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
|
That way, a device in 1920×1080 will be mirrored at 1024×576.
|
||||||
|
|
||||||
|
|
||||||
#### Change bit-rate
|
#### Change bit-rate
|
||||||
@@ -213,20 +195,11 @@ scrcpy --max-fps 15
|
|||||||
|
|
||||||
This is officially supported since Android 10, but may work on earlier versions.
|
This is officially supported since Android 10, but may work on earlier versions.
|
||||||
|
|
||||||
The actual capture framerate may be printed to the console:
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --print-fps
|
|
||||||
```
|
|
||||||
|
|
||||||
It may also be enabled or disabled at any time with <kbd>MOD</kbd>+<kbd>i</kbd>.
|
|
||||||
|
|
||||||
|
|
||||||
#### Crop
|
#### Crop
|
||||||
|
|
||||||
The device screen may be cropped to mirror only part of the screen.
|
The device screen may be cropped to mirror only part of the screen.
|
||||||
|
|
||||||
This is useful, for example, to mirror only one eye of the Oculus Go:
|
This is useful for example to mirror only one eye of the Oculus Go:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||||
@@ -234,9 +207,33 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
|||||||
|
|
||||||
If `--max-size` is also specified, resizing is applied after cropping.
|
If `--max-size` is also specified, resizing is applied after cropping.
|
||||||
|
|
||||||
|
#### USB HID over AOAv2
|
||||||
|
|
||||||
|
Scrcpy can simulate a USB physical keyboard on Android to provide better input
|
||||||
|
experience, you need to connect your device via USB, not wireless.
|
||||||
|
|
||||||
|
However, due to some limitation of libusb and WinUSB driver, you cannot use HID
|
||||||
|
over AOAv2 on Windows.
|
||||||
|
|
||||||
|
Currently a USB serial number is needed to use HID over AOAv2.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX # don't use HID
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode inject # don't use HID
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Serial number can be found by `adb get-serialno`.
|
||||||
|
|
||||||
|
If you are a non-QWERTY keyboard user and using HID mode, please remember to set
|
||||||
|
correct physical keyboard layout manually in Android settings, because scrcpy
|
||||||
|
just forwards scancodes to Android device and Android system is responsible for
|
||||||
|
converting scancodes to correct keycode on Android device (your system does this
|
||||||
|
on your PC).
|
||||||
|
|
||||||
#### Lock video orientation
|
#### Lock video orientation
|
||||||
|
|
||||||
|
|
||||||
To lock the orientation of the mirroring:
|
To lock the orientation of the mirroring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -261,7 +258,7 @@ crash. It is possible to select a different encoder:
|
|||||||
scrcpy --encoder OMX.qcom.video.encoder.avc
|
scrcpy --encoder OMX.qcom.video.encoder.avc
|
||||||
```
|
```
|
||||||
|
|
||||||
To list the available encoders, you can pass an invalid encoder name; the
|
To list the available encoders, you could pass an invalid encoder name, the
|
||||||
error will give the available encoders:
|
error will give the available encoders:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -325,7 +322,7 @@ v4l2-ctl --list-devices
|
|||||||
ls /dev/video*
|
ls /dev/video*
|
||||||
```
|
```
|
||||||
|
|
||||||
To start `scrcpy` using a v4l2 sink:
|
To start scrcpy using a v4l2 sink:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
@@ -333,7 +330,7 @@ scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
|
|||||||
scrcpy --v4l2-sink=/dev/videoN -N # short version
|
scrcpy --v4l2-sink=/dev/videoN -N # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
(replace `N` with the device ID, check with `ls /dev/video*`)
|
(replace `N` by the device ID, check with `ls /dev/video*`)
|
||||||
|
|
||||||
Once enabled, you can open your video stream with a v4l2-capable tool:
|
Once enabled, you can open your video stream with a v4l2-capable tool:
|
||||||
|
|
||||||
@@ -349,10 +346,8 @@ For example, you could capture the video within [OBS].
|
|||||||
|
|
||||||
#### Buffering
|
#### Buffering
|
||||||
|
|
||||||
It is possible to add buffering. This increases latency, but reduces jitter (see
|
It is possible to add buffering. This increases latency but reduces jitter (see
|
||||||
[#2464]).
|
#2464).
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
The option is available for display buffering:
|
The option is available for display buffering:
|
||||||
|
|
||||||
@@ -369,66 +364,25 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
|
|||||||
|
|
||||||
### Connection
|
### Connection
|
||||||
|
|
||||||
#### TCP/IP (wireless)
|
#### Wireless
|
||||||
|
|
||||||
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
|
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
|
||||||
device over TCP/IP. The device must be connected on the same network as the
|
device over TCP/IP:
|
||||||
computer.
|
|
||||||
|
|
||||||
##### Automatic
|
1. Connect the device to the same Wi-Fi as your computer.
|
||||||
|
2. Get your device IP address, in Settings → About phone → Status, or by
|
||||||
An option `--tcpip` allows to configure the connection automatically. There are
|
|
||||||
two variants.
|
|
||||||
|
|
||||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
|
||||||
port (typically 5555) for incoming _adb_ connections, then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
|
||||||
scrcpy --tcpip=192.168.1.1:5555
|
|
||||||
```
|
|
||||||
|
|
||||||
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
|
|
||||||
address), connect the device over USB, then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip # without arguments
|
|
||||||
```
|
|
||||||
|
|
||||||
It will automatically find the device IP address and adb port, enable TCP/IP
|
|
||||||
mode if necessary, then connect to the device before starting.
|
|
||||||
|
|
||||||
##### Manual
|
|
||||||
|
|
||||||
Alternatively, it is possible to enable the TCP/IP connection manually using
|
|
||||||
`adb`:
|
|
||||||
|
|
||||||
1. Plug the device into a USB port on your computer.
|
|
||||||
2. Connect the device to the same Wi-Fi network as your computer.
|
|
||||||
3. Get your device IP address, in Settings → About phone → Status, or by
|
|
||||||
executing this command:
|
executing this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb shell ip route | awk '{print $9}'
|
adb shell ip route | awk '{print $9}'
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
|
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
|
||||||
5. Unplug your device.
|
4. Unplug your device.
|
||||||
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
|
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
|
||||||
with the device IP address you found)_.
|
6. Run `scrcpy` as usual.
|
||||||
7. Run `scrcpy` as usual.
|
|
||||||
|
|
||||||
Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass
|
It may be useful to decrease the bit-rate and the definition:
|
||||||
having to physically connect your device directly to your computer.
|
|
||||||
|
|
||||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
|
|
||||||
|
|
||||||
If the connection randomly drops, run your `scrcpy` command to reconnect. If it
|
|
||||||
says there are no devices/emulators found, try running `adb connect
|
|
||||||
DEVICE_IP:5555` again, and then `scrcpy` as usual. If it still says there are
|
|
||||||
none found, try running `adb disconnect`, and then run those two commands again.
|
|
||||||
|
|
||||||
It may be useful to decrease the bit-rate and the resolution:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M --max-size 800
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
@@ -440,16 +394,13 @@ scrcpy -b2M -m800 # short version
|
|||||||
|
|
||||||
#### Multi-devices
|
#### Multi-devices
|
||||||
|
|
||||||
If several devices are listed in `adb devices`, you can specify the _serial_:
|
If several devices are listed in `adb devices`, you must specify the _serial_:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --serial 0123456789abcdef
|
scrcpy --serial 0123456789abcdef
|
||||||
scrcpy -s 0123456789abcdef # short version
|
scrcpy -s 0123456789abcdef # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
The serial may also be provided via the environment variable `ANDROID_SERIAL`
|
|
||||||
(also used by `adb`).
|
|
||||||
|
|
||||||
If the device is connected over TCP/IP:
|
If the device is connected over TCP/IP:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -457,19 +408,6 @@ scrcpy --serial 192.168.0.1:5555
|
|||||||
scrcpy -s 192.168.0.1:5555 # short version
|
scrcpy -s 192.168.0.1:5555 # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
If only one device is connected via either USB or TCP/IP, it is possible to
|
|
||||||
select it automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Select the only device connected via USB
|
|
||||||
scrcpy -d # like adb -d
|
|
||||||
scrcpy --select-usb # long version
|
|
||||||
|
|
||||||
# Select the only device connected via TCP/IP
|
|
||||||
scrcpy -e # like adb -e
|
|
||||||
scrcpy --select-tcpip # long version
|
|
||||||
```
|
|
||||||
|
|
||||||
You can start several instances of _scrcpy_ for several devices.
|
You can start several instances of _scrcpy_ for several devices.
|
||||||
|
|
||||||
#### Autostart on device connection
|
#### Autostart on device connection
|
||||||
@@ -482,67 +420,21 @@ autoadb scrcpy -s '{}'
|
|||||||
|
|
||||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
#### Tunnels
|
#### SSH tunnel
|
||||||
|
|
||||||
To connect to a remote device, it is possible to connect a local `adb` client to
|
To connect to a remote device, it is possible to connect a local `adb` client to
|
||||||
a remote `adb` server (provided they use the same version of the _adb_
|
a remote `adb` server (provided they use the same version of the _adb_
|
||||||
protocol).
|
protocol):
|
||||||
|
|
||||||
##### Remote ADB server
|
|
||||||
|
|
||||||
To connect to a remote _adb server_, make the server listen on all interfaces:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb kill-server
|
adb kill-server # kill the local adb server on 5037
|
||||||
adb -a nodaemon server start
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
# keep this open
|
# keep this open
|
||||||
```
|
```
|
||||||
|
|
||||||
**Warning: all communications between clients and the _adb server_ are
|
From another terminal:
|
||||||
unencrypted.**
|
|
||||||
|
|
||||||
Suppose that this server is accessible at 192.168.1.2. Then, from another
|
|
||||||
terminal, run `scrcpy`:
|
|
||||||
|
|
||||||
```bash
|
```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
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -550,16 +442,14 @@ To avoid enabling remote port forwarding, you could force a forward connection
|
|||||||
instead (notice the `-L` instead of `-R`):
|
instead (notice the `-L` instead of `-R`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# local 5038 --> remote 5037
|
adb kill-server # kill the local adb server on 5037
|
||||||
# local 27183 --> remote 27183
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
|
||||||
# keep this open
|
# keep this open
|
||||||
```
|
```
|
||||||
|
|
||||||
From another terminal, run `scrcpy`:
|
From another terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy --force-adb-forward
|
scrcpy --force-adb-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -598,7 +488,7 @@ scrcpy --window-borderless
|
|||||||
|
|
||||||
#### Always on top
|
#### Always on top
|
||||||
|
|
||||||
To keep the _scrcpy_ window always on top:
|
To keep the scrcpy window always on top:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --always-on-top
|
scrcpy --always-on-top
|
||||||
@@ -623,7 +513,7 @@ The window may be rotated:
|
|||||||
scrcpy --rotation 1
|
scrcpy --rotation 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Possible values:
|
Possibles values are:
|
||||||
- `0`: no rotation
|
- `0`: no rotation
|
||||||
- `1`: 90 degrees counterclockwise
|
- `1`: 90 degrees counterclockwise
|
||||||
- `2`: 180 degrees
|
- `2`: 180 degrees
|
||||||
@@ -672,19 +562,19 @@ adb shell dumpsys display # search "mDisplayId=" in the output
|
|||||||
```
|
```
|
||||||
|
|
||||||
The secondary display may only be controlled if the device runs at least Android
|
The secondary display may only be controlled if the device runs at least Android
|
||||||
10 (otherwise it is mirrored as read-only).
|
10 (otherwise it is mirrored in read-only).
|
||||||
|
|
||||||
|
|
||||||
#### Stay awake
|
#### Stay awake
|
||||||
|
|
||||||
To prevent the device from sleeping after a delay when the device is plugged in:
|
To prevent the device to sleep after some delay when the device is plugged in:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --stay-awake
|
scrcpy --stay-awake
|
||||||
scrcpy -w
|
scrcpy -w
|
||||||
```
|
```
|
||||||
|
|
||||||
The initial state is restored when _scrcpy_ is closed.
|
The initial state is restored when scrcpy is closed.
|
||||||
|
|
||||||
|
|
||||||
#### Turn screen off
|
#### Turn screen off
|
||||||
@@ -702,10 +592,9 @@ Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
|
|||||||
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
||||||
|
|
||||||
On Android, the `POWER` button always turns the screen on. For convenience, if
|
On Android, the `POWER` button always turns the screen on. For convenience, if
|
||||||
`POWER` is sent via _scrcpy_ (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>),
|
`POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
|
||||||
it will force to turn the screen off after a small delay (on a best effort
|
will force to turn the screen off after a small delay (on a best effort basis).
|
||||||
basis). The physical `POWER` button will still cause the screen to be turned
|
The physical `POWER` button will still cause the screen to be turned on.
|
||||||
on.
|
|
||||||
|
|
||||||
It can also be useful to prevent the device from sleeping:
|
It can also be useful to prevent the device from sleeping:
|
||||||
|
|
||||||
@@ -714,24 +603,6 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw
|
scrcpy -Sw
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Power off on close
|
|
||||||
|
|
||||||
To turn the device screen off when closing _scrcpy_:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --power-off-on-close
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Power on on start
|
|
||||||
|
|
||||||
By default, on start, the device is powered on.
|
|
||||||
|
|
||||||
To prevent this behavior:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --no-power-on
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Show touches
|
#### Show touches
|
||||||
|
|
||||||
@@ -748,13 +619,12 @@ scrcpy --show-touches
|
|||||||
scrcpy -t
|
scrcpy -t
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that it only shows _physical_ touches (by a finger on the device).
|
Note that it only shows _physical_ touches (with the finger on the device).
|
||||||
|
|
||||||
|
|
||||||
#### Disable screensaver
|
#### Disable screensaver
|
||||||
|
|
||||||
By default, _scrcpy_ does not prevent the screensaver from running on the
|
By default, scrcpy does not prevent the screensaver to run on the computer.
|
||||||
computer.
|
|
||||||
|
|
||||||
To disable it:
|
To disable it:
|
||||||
|
|
||||||
@@ -796,144 +666,38 @@ To copy, cut and paste in such cases (but only supported on Android >= 7):
|
|||||||
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
|
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
|
||||||
clipboard synchronization)
|
clipboard synchronization)
|
||||||
|
|
||||||
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> injects the computer
|
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
|
||||||
clipboard text as a sequence of key events. This is useful when the component
|
computer clipboard text as a sequence of key events. This is useful when the
|
||||||
does not accept text pasting (for example in _Termux_), but it can break
|
component does not accept text pasting (for example in _Termux_), but it can
|
||||||
non-ASCII content.
|
break non-ASCII content.
|
||||||
|
|
||||||
**WARNING:** Pasting the computer clipboard to the device (either via
|
**WARNING:** Pasting the computer clipboard to the device (either via
|
||||||
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
|
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
|
||||||
into the Android clipboard. As a consequence, any Android application could read
|
into the device clipboard. As a consequence, any Android application could read
|
||||||
its content. You should avoid pasting sensitive content (like passwords) that
|
its content. You should avoid to paste sensitive content (like passwords) that
|
||||||
way.
|
way.
|
||||||
|
|
||||||
Some Android devices do not behave as expected when setting the device clipboard
|
Some devices do not behave as expected when setting the device clipboard
|
||||||
programmatically. An option `--legacy-paste` is provided to change the behavior
|
programmatically. An option `--legacy-paste` is provided to change the behavior
|
||||||
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
|
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
|
||||||
also inject the computer clipboard text as a sequence of key events (the same
|
also inject the computer clipboard text as a sequence of key events (the same
|
||||||
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
To disable automatic clipboard synchronization, use
|
|
||||||
`--no-clipboard-autosync`.
|
|
||||||
|
|
||||||
#### Pinch-to-zoom
|
#### Pinch-to-zoom
|
||||||
|
|
||||||
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||||
|
|
||||||
More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
|
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
|
||||||
Until the left-click button is released, all mouse movements scale and rotate
|
the left-click button is released, all mouse movements scale and rotate the
|
||||||
the content (if supported by the app) relative to the center of the screen.
|
content (if supported by the app) relative to the center of the screen.
|
||||||
|
|
||||||
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
Concretely, scrcpy generates additional touch events from a "virtual finger" at
|
||||||
at a location inverted through the center of the screen.
|
a location inverted through the center of the screen.
|
||||||
|
|
||||||
#### Physical keyboard simulation (HID)
|
|
||||||
|
|
||||||
By default, _scrcpy_ uses Android key or text injection: it works everywhere,
|
|
||||||
but is limited to ASCII.
|
|
||||||
|
|
||||||
Alternatively, `scrcpy` can simulate a physical USB keyboard on Android to
|
|
||||||
provide a better input experience (using [USB HID over AOAv2][hid-aoav2]): the
|
|
||||||
virtual keyboard is disabled and it works for all characters and IME.
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
However, it only works if the device is connected via USB.
|
|
||||||
|
|
||||||
Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it
|
|
||||||
is not possible to open a USB device if it is already open by another process
|
|
||||||
like the _adb daemon_).
|
|
||||||
|
|
||||||
To enable this mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
If it fails for some reason (for example because the device is not connected via
|
|
||||||
USB), it automatically fallbacks to the default mode (with a log in the
|
|
||||||
console). This allows using the same command line options when connected over
|
|
||||||
USB and TCP/IP.
|
|
||||||
|
|
||||||
In this mode, raw key events (scancodes) are sent to the device, independently
|
|
||||||
of the host key mapping. Therefore, if your keyboard layout does not match, it
|
|
||||||
must be configured on the Android device, in Settings → System → Languages and
|
|
||||||
input → [Physical keyboard].
|
|
||||||
|
|
||||||
This settings page can be started directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
|
||||||
```
|
|
||||||
|
|
||||||
However, the option is only available when the HID keyboard is enabled (or when
|
|
||||||
a physical keyboard is connected).
|
|
||||||
|
|
||||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
#### Physical mouse simulation (HID)
|
|
||||||
|
|
||||||
Similarly to the physical keyboard simulation, it is possible to simulate a
|
|
||||||
physical mouse. Likewise, it only works if the device is connected by USB.
|
|
||||||
|
|
||||||
By default, _scrcpy_ uses Android mouse events injection with absolute
|
|
||||||
coordinates. By simulating a physical mouse, a mouse pointer appears on the
|
|
||||||
Android device, and relative mouse motion, clicks and scrolls are injected.
|
|
||||||
|
|
||||||
To enable this mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-mouse
|
|
||||||
scrcpy -M # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also add `--forward-all-clicks` to [forward all mouse
|
|
||||||
buttons][forward_all_clicks].
|
|
||||||
|
|
||||||
[forward_all_clicks]: #right-click-and-middle-click
|
|
||||||
|
|
||||||
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
|
|
||||||
disappears from the computer and appears on the Android device instead).
|
|
||||||
|
|
||||||
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
|
||||||
(disable or enable) the mouse capture. Use one of them to give the control of
|
|
||||||
the mouse back to the computer.
|
|
||||||
|
|
||||||
|
|
||||||
#### OTG
|
|
||||||
|
|
||||||
It is possible to run _scrcpy_ with only physical keyboard and mouse simulation
|
|
||||||
(HID), as if the computer keyboard and mouse were plugged directly to the device
|
|
||||||
via an OTG cable.
|
|
||||||
|
|
||||||
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
|
|
||||||
|
|
||||||
To enable OTG mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg
|
|
||||||
# Pass the serial if several USB devices are available
|
|
||||||
scrcpy --otg -s 0123456789abcdef
|
|
||||||
```
|
|
||||||
|
|
||||||
It is possible to enable only HID keyboard or HID mouse:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg --hid-keyboard # keyboard only
|
|
||||||
scrcpy --otg --hid-mouse # mouse only
|
|
||||||
scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse
|
|
||||||
# for convenience, enable both by default
|
|
||||||
scrcpy --otg # keyboard and mouse
|
|
||||||
```
|
|
||||||
|
|
||||||
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
|
|
||||||
connected by USB.
|
|
||||||
|
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
Two kinds of [events][textevents] are generated when typing text:
|
There are two kinds of [events][textevents] generated when typing text:
|
||||||
- _key events_, signaling that a key is pressed or released;
|
- _key events_, signaling that a key is pressed or released;
|
||||||
- _text events_, signaling that a text has been entered.
|
- _text events_, signaling that a text has been entered.
|
||||||
|
|
||||||
@@ -949,15 +713,6 @@ scrcpy --prefer-text
|
|||||||
|
|
||||||
(but this will break keyboard behavior in games)
|
(but this will break keyboard behavior in games)
|
||||||
|
|
||||||
On the contrary, you could force to always inject raw key events:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --raw-key-events
|
|
||||||
```
|
|
||||||
|
|
||||||
These options have no effect on HID keyboard (all key events are sent as
|
|
||||||
scancodes in this mode).
|
|
||||||
|
|
||||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
@@ -973,9 +728,6 @@ To avoid forwarding repeated key events:
|
|||||||
scrcpy --no-key-repeat
|
scrcpy --no-key-repeat
|
||||||
```
|
```
|
||||||
|
|
||||||
This option has no effect on HID keyboard (key repeat is handled by Android
|
|
||||||
directly in this mode).
|
|
||||||
|
|
||||||
|
|
||||||
#### Right-click and middle-click
|
#### Right-click and middle-click
|
||||||
|
|
||||||
@@ -1051,7 +803,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
||||||
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
|
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
|
||||||
| Click on `MENU` (unlock screen)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
||||||
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
@@ -1060,24 +812,21 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
||||||
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
||||||
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Copy to clipboard⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Cut to clipboard⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Synchronize clipboards and paste⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
| Drag & drop APK file | Install APK from computer
|
|
||||||
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
|
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
_³4th and 5th mouse buttons, if your mouse has them._
|
_³4th and 5th mouse buttons, if your mouse has them._
|
||||||
_⁴For react-native apps in development, `MENU` triggers development menu._
|
_⁴Only on Android >= 7._
|
||||||
_⁵Only on Android >= 7._
|
|
||||||
|
|
||||||
Shortcuts with repeated keys are executed by releasing and pressing the key a
|
Shortcuts with repeated keys are executted by releasing and pressing the key a
|
||||||
second time. For example, to execute "Expand settings panel":
|
second time. For example, to execute "Expand settings panel":
|
||||||
|
|
||||||
1. Press and keep pressing <kbd>MOD</kbd>.
|
1. Press and keep pressing <kbd>MOD</kbd>.
|
||||||
@@ -1090,7 +839,7 @@ handled by the active application.
|
|||||||
|
|
||||||
## Custom paths
|
## Custom paths
|
||||||
|
|
||||||
To use a specific `adb` binary, configure its path in the environment variable
|
To use a specific _adb_ binary, configure its path in the environment variable
|
||||||
`ADB`:
|
`ADB`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -1100,10 +849,10 @@ ADB=/path/to/adb scrcpy
|
|||||||
To override the path of the `scrcpy-server` file, configure its path in
|
To override the path of the `scrcpy-server` file, configure its path in
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
To override the icon, configure its path in `SCRCPY_ICON_PATH`.
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|
||||||
|
|
||||||
## Why the name _scrcpy_?
|
## Why _scrcpy_?
|
||||||
|
|
||||||
A colleague challenged me to find a name as unpronounceable as [gnirehtet].
|
A colleague challenged me to find a name as unpronounceable as [gnirehtet].
|
||||||
|
|
||||||
@@ -1120,9 +869,7 @@ See [BUILD].
|
|||||||
|
|
||||||
## Common issues
|
## Common issues
|
||||||
|
|
||||||
See the [FAQ].
|
See the [FAQ](FAQ.md).
|
||||||
|
|
||||||
[FAQ]: FAQ.md
|
|
||||||
|
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
@@ -1135,7 +882,7 @@ Read the [developers page].
|
|||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -1157,29 +904,17 @@ Read the [developers page].
|
|||||||
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
If you encounter a bug, please read the [FAQ] first, then open an [issue].
|
|
||||||
|
|
||||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
|
||||||
|
|
||||||
For general questions or discussions, you can also use:
|
|
||||||
|
|
||||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
|
||||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
This README is available in other languages:
|
This README is available in other languages:
|
||||||
|
|
||||||
- [Deutsch (German, `de`) - v1.22](README.de.md)
|
|
||||||
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
||||||
- [Italiano (Italiano, `it`) - v1.23](README.it.md)
|
- [Italiano (Italiano, `it`) - v1.17](README.it.md)
|
||||||
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
|
- [日本語 (Japanese, `jp`) - v1.17](README.jp.md)
|
||||||
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
|
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
|
||||||
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
|
- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md)
|
||||||
- [Español (Spanish, `sp`) - v1.21](README.sp.md)
|
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
|
||||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md)
|
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
|
||||||
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
||||||
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)
|
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)
|
||||||
|
|
||||||
|
|||||||
176
README.pt-br.md
176
README.pt-br.md
@@ -1,6 +1,6 @@
|
|||||||
_Apenas o [README](README.md) original é garantido estar atualizado._
|
_Apenas o [README](README.md) original é garantido estar atualizado._
|
||||||
|
|
||||||
# scrcpy (v1.19)
|
# scrcpy (v1.17)
|
||||||
|
|
||||||
Esta aplicação fornece exibição e controle de dispositivos Android conectados via
|
Esta aplicação fornece exibição e controle de dispositivos Android conectados via
|
||||||
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
|
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
|
||||||
@@ -38,18 +38,6 @@ controlá-lo usando teclado e mouse.
|
|||||||
|
|
||||||
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
### Sumário
|
|
||||||
|
|
||||||
- Linux: `apt install scrcpy`
|
|
||||||
- Windows: [baixar][direct-win64]
|
|
||||||
- macOS: `brew install scrcpy`
|
|
||||||
|
|
||||||
Compilar pelos arquivos fontes: [BUILD] ([processo simplificado][BUILD_simple])
|
|
||||||
|
|
||||||
[BUILD]: BUILD.md
|
|
||||||
[BUILD_simple]: BUILD.md#simple
|
|
||||||
|
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
|
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
|
||||||
@@ -79,7 +67,9 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
|
|||||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]).
|
Você também pode [compilar o app manualmente][BUILD] (não se preocupe, não é tão
|
||||||
|
difícil).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
@@ -123,18 +113,13 @@ brew install scrcpy
|
|||||||
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
|
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install android-platform-tools
|
# Homebrew >= 2.6.0
|
||||||
|
brew install --cask android-platform-tools
|
||||||
|
|
||||||
|
# Homebrew < 2.6.0
|
||||||
|
brew cask install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
Está também disponivel em [MacPorts], que prepara o adb para você:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo port install scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
[MacPorts]: https://www.macports.org/
|
|
||||||
|
|
||||||
|
|
||||||
Você também pode [compilar o app manualmente][BUILD].
|
Você também pode [compilar o app manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
@@ -210,11 +195,10 @@ Se `--max-size` também for especificado, o redimensionamento é aplicado após
|
|||||||
Para travar a orientação do espelhamento:
|
Para travar a orientação do espelhamento:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # orientação inicial (Atual)
|
scrcpy --lock-video-orientation 0 # orientação natural
|
||||||
scrcpy --lock-video-orientation=0 # orientação natural
|
scrcpy --lock-video-orientation 1 # 90° sentido anti-horário
|
||||||
scrcpy --lock-video-orientation=1 # 90° sentido anti-horário
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
scrcpy --lock-video-orientation=2 # 180°
|
scrcpy --lock-video-orientation 3 # 90° sentido horário
|
||||||
scrcpy --lock-video-orientation=3 # 90° sentido horário
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Isso afeta a orientação de gravação.
|
Isso afeta a orientação de gravação.
|
||||||
@@ -238,9 +222,7 @@ erro dará os encoders disponíveis:
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Captura
|
### Gravando
|
||||||
|
|
||||||
#### Gravando
|
|
||||||
|
|
||||||
É possível gravar a tela enquanto ocorre o espelhamento:
|
É possível gravar a tela enquanto ocorre o espelhamento:
|
||||||
|
|
||||||
@@ -264,79 +246,6 @@ pacotes][packet delay variation] não impacta o arquivo gravado.
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
#### v4l2loopback
|
|
||||||
|
|
||||||
Em Linux, é possível enviar a transmissão do video para um disposiivo v4l2 loopback, assim
|
|
||||||
o dispositivo Android pode ser aberto como uma webcam por qualquer ferramneta capaz de v4l2
|
|
||||||
|
|
||||||
The module `v4l2loopback` must be installed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install v4l2loopback-dkms
|
|
||||||
```
|
|
||||||
|
|
||||||
Para criar um dispositivo v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo modprobe v4l2loopback
|
|
||||||
```
|
|
||||||
|
|
||||||
Isso criara um novo dispositivo de vídeo em `/dev/videoN`, onde `N` é uma integer
|
|
||||||
(mais [opções](https://github.com/umlaeute/v4l2loopback#options) estão disponiveis
|
|
||||||
para criar varios dispositivos ou dispositivos com IDs específicas).
|
|
||||||
|
|
||||||
Para listar os dispositivos disponíveis:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# requer o pacote v4l-utils
|
|
||||||
v4l2-ctl --list-devices
|
|
||||||
|
|
||||||
# simples, mas pode ser suficiente
|
|
||||||
ls /dev/video*
|
|
||||||
```
|
|
||||||
|
|
||||||
Para iniciar o scrcpy usando o coletor v4l2 (sink):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # desativa a janela espelhada
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # versão curta
|
|
||||||
```
|
|
||||||
|
|
||||||
(troque `N` pelo ID do dipositivo, verifique com `ls /dev/video*`)
|
|
||||||
|
|
||||||
Uma vez ativado, você pode abrir suas trasmissões de videos com uma ferramenta capaz de v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ffplay -i /dev/videoN
|
|
||||||
vlc v4l2:///dev/videoN # VLC pode adicionar um pouco de atraso de buffering
|
|
||||||
```
|
|
||||||
|
|
||||||
Por exemplo, você pode capturar o video dentro do [OBS].
|
|
||||||
|
|
||||||
[OBS]: https://obsproject.com/
|
|
||||||
|
|
||||||
|
|
||||||
#### Buffering
|
|
||||||
|
|
||||||
É possivel adicionar buffering. Isso aumenta a latência, mas reduz a tenção (jitter) (veja
|
|
||||||
[#2464]).
|
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
A opção éta disponivel para buffering de exibição:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --display-buffer=50 # adiciona 50 ms de buffering para a exibição
|
|
||||||
```
|
|
||||||
|
|
||||||
e coletor V4L2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-buffer=500 # adiciona 500 ms de buffering para coletor V4L2
|
|
||||||
```
|
|
||||||
|
|
||||||
,
|
|
||||||
### Conexão
|
### Conexão
|
||||||
|
|
||||||
#### Sem fio
|
#### Sem fio
|
||||||
@@ -579,6 +488,18 @@ scrcpy -Sw
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Renderizar frames expirados
|
||||||
|
|
||||||
|
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado
|
||||||
|
disponível, e descarta o anterior.
|
||||||
|
|
||||||
|
Para forçar a renderização de todos os frames (com o custo de um possível aumento de
|
||||||
|
latência), use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --render-expired-frames
|
||||||
|
```
|
||||||
|
|
||||||
#### Mostrar toques
|
#### Mostrar toques
|
||||||
|
|
||||||
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
|
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
|
||||||
@@ -726,7 +647,7 @@ Não existe feedback visual, um log é imprimido no console.
|
|||||||
|
|
||||||
#### Enviar arquivo para dispositivo
|
#### Enviar arquivo para dispositivo
|
||||||
|
|
||||||
Para enviar um arquivo para `/sdcard/Download/` no dispositivo, arraste e solte um arquivo (não-APK) para a
|
Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a
|
||||||
janela do _scrcpy_.
|
janela do _scrcpy_.
|
||||||
|
|
||||||
Não existe feedback visual, um log é imprimido no console.
|
Não existe feedback visual, um log é imprimido no console.
|
||||||
@@ -773,12 +694,12 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
|
|||||||
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
|
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
|
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
|
||||||
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
|
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
|
||||||
| Redimensionar janela para 1:1 (pixel-perfeito) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Redimensionar janela para 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo-esquerdo¹_
|
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo¹_
|
||||||
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
|
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
|
||||||
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
|
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
|
||||||
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Clique-do-4.°³_
|
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
| Clicar em `MENU` (desbloquear tela) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Clicar em `MENU` (desbloquear tela | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
|
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
|
||||||
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
|
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
|
||||||
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
@@ -786,27 +707,18 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
|
|||||||
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_
|
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
| Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_
|
| Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Copiar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
| Recortar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Sincronizar áreas de transferência e colar⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
|
||||||
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_Clicar-e-mover_
|
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_clicar-e-mover_
|
||||||
|
|
||||||
_¹Clique-duplo-esquerdo na borda preta para remove-la._
|
_¹Clique-duplo em bordas pretas para removê-las._
|
||||||
_²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._
|
_²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._
|
||||||
_³4.° and 5.° botões do mouse, caso o mouse possua._
|
_³Apenas em Android >= 7._
|
||||||
_⁴Apenas em Android >= 7._
|
|
||||||
|
|
||||||
Atalhos com teclas reptidas são executados soltando e precionando a tecla
|
|
||||||
uma segunda vez. Por exemplo, para executar "Expandir painel de Configurção":
|
|
||||||
|
|
||||||
1. Mantenha pressionado <kbd>MOD</kbd>.
|
|
||||||
2. Depois click duas vezes <kbd>n</kbd>.
|
|
||||||
3. Finalmente, solte <kbd>MOD</kbd>.
|
|
||||||
|
|
||||||
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
|
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
|
||||||
tratados pela aplicação ativa.
|
tratados pela aplicação ativa.
|
||||||
@@ -817,9 +729,7 @@ tratados pela aplicação ativa.
|
|||||||
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
|
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
|
||||||
`ADB`:
|
`ADB`:
|
||||||
|
|
||||||
```bash
|
ADB=/caminho/para/adb scrcpy
|
||||||
ADB=/caminho/para/adb scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
@@ -841,6 +751,8 @@ Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet
|
|||||||
|
|
||||||
Veja [BUILD].
|
Veja [BUILD].
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
|
||||||
|
|
||||||
## Problemas comuns
|
## Problemas comuns
|
||||||
|
|
||||||
@@ -857,7 +769,7 @@ Leia a [página dos desenvolvedores][developers page].
|
|||||||
## Licença
|
## Licença
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
371
README.sp.md
371
README.sp.md
@@ -1,36 +1,24 @@
|
|||||||
Solo se garantiza que el archivo [README](README.md) original esté actualizado.
|
Solo se garantiza que el archivo [README](README.md) original esté actualizado.
|
||||||
|
|
||||||
# scrcpy (v1.21)
|
# scrcpy (v1.17)
|
||||||
|
|
||||||
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
Esta aplicación proporciona imagen y control de un dispositivo Android conectado
|
||||||
|
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
|
||||||
Esta aplicación proporciona control e imagen de un dispositivo Android conectado
|
|
||||||
por USB (o [por TCP/IP](#conexión)). No requiere acceso _root_.
|
|
||||||
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
|
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Se enfoca en:
|
Sus características principales son:
|
||||||
- **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo
|
|
||||||
- **rendimiento**: 30~120fps, dependiendo del dispositivo
|
- **ligero** (nativo, solo muestra la imagen del dispositivo)
|
||||||
- **calidad**: 1920×1080 o superior
|
- **desempeño** (30~60fps)
|
||||||
- **baja latencia**: [35~70ms][lowlatency]
|
- **calidad** (1920×1080 o superior)
|
||||||
- **inicio rápido**: ~1 segundo para mostrar la primera imagen
|
- **baja latencia** ([35~70ms][lowlatency])
|
||||||
- **no intrusivo**: no deja nada instalado en el dispositivo
|
- **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
|
||||||
- **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet
|
- **no intrusivo** (no se deja nada instalado en el dispositivo)
|
||||||
- **libertad**: software gratis y de código abierto
|
|
||||||
|
|
||||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
Con la aplicación puede:
|
|
||||||
- [grabar la pantalla](#capturas-y-grabaciones)
|
|
||||||
- duplicar la imagen con [la pantalla apagada](#apagar-la-pantalla)
|
|
||||||
- [copiar y pegar](#copiar-y-pegar) en ambos sentidos
|
|
||||||
- [configurar la calidad](#configuración-de-captura)
|
|
||||||
- usar la pantalla del dispositivo [como webcam (V4L2)](#v4l2loopback) (solo en Linux)
|
|
||||||
- [emular un teclado físico (HID)](#emular-teclado-físico-hid)
|
|
||||||
(solo en Linux)
|
|
||||||
- y mucho más…
|
|
||||||
|
|
||||||
## Requisitos
|
## Requisitos
|
||||||
|
|
||||||
@@ -63,7 +51,7 @@ Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
En Debian y Ubuntu:
|
En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install scrcpy
|
apt install scrcpy
|
||||||
@@ -137,7 +125,7 @@ Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
|
|||||||
brew install android-platform-tools
|
brew install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
También está disponible en [MacPorts], que configura el adb automáticamente:
|
También está disponible en [MacPorts], que configurará el adb automáticamente:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo port install scrcpy
|
sudo port install scrcpy
|
||||||
@@ -165,7 +153,7 @@ scrcpy --help
|
|||||||
|
|
||||||
## Características
|
## Características
|
||||||
|
|
||||||
### Configuración de captura
|
### Capturar configuración
|
||||||
|
|
||||||
#### Reducir la definición
|
#### Reducir la definición
|
||||||
|
|
||||||
@@ -220,11 +208,10 @@ Si `--max-size` también está especificado, el cambio de tamaño es aplicado de
|
|||||||
Para fijar la rotación de la transmisión:
|
Para fijar la rotación de la transmisión:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # orientación inicial
|
scrcpy --lock-video-orientation 0 # orientación normal
|
||||||
scrcpy --lock-video-orientation=0 # orientación normal
|
scrcpy --lock-video-orientation 1 # 90° contrarreloj
|
||||||
scrcpy --lock-video-orientation=1 # 90° contrarreloj
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
scrcpy --lock-video-orientation=2 # 180°
|
scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
|
||||||
scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Esto afecta la rotación de la grabación.
|
Esto afecta la rotación de la grabación.
|
||||||
@@ -246,10 +233,7 @@ Para listar los codificadores disponibles, puedes pasar un nombre de codificador
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Capturas y grabaciones
|
### Grabación
|
||||||
|
|
||||||
|
|
||||||
#### Grabación
|
|
||||||
|
|
||||||
Es posible grabar la pantalla mientras se transmite:
|
Es posible grabar la pantalla mientras se transmite:
|
||||||
|
|
||||||
@@ -266,117 +250,17 @@ scrcpy -Nr file.mkv
|
|||||||
# interrumpe la grabación con Ctrl+C
|
# interrumpe la grabación con Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
Los "skipped frames" son grabados, incluso si no se mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
|
"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
|
||||||
variation]" no impacta el archivo grabado.
|
variation]" no impacta el archivo grabado.
|
||||||
|
|
||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
#### v4l2loopback
|
|
||||||
|
|
||||||
En Linux se puede mandar el stream del video a un dispositivo loopback v4l2, por
|
|
||||||
lo que se puede abrir el dispositivo Android como una webcam con cualquier
|
|
||||||
programa compatible con v4l2.
|
|
||||||
|
|
||||||
Se debe instalar el modulo `v4l2loopback`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install v4l2loopback-dkms
|
|
||||||
```
|
|
||||||
|
|
||||||
Para crear un dispositivo v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo modprobe v4l2loopback
|
|
||||||
```
|
|
||||||
|
|
||||||
Esto va a crear un nuevo dispositivo de video en `/dev/videoN`, donde `N` es un número
|
|
||||||
(hay más [opciones](https://github.com/umlaeute/v4l2loopback#options) disponibles
|
|
||||||
para crear múltiples dispositivos o usar un ID en específico).
|
|
||||||
|
|
||||||
Para ver los dispositivos disponibles:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# requiere el paquete v4l-utils
|
|
||||||
v4l2-ctl --list-devices
|
|
||||||
# simple pero generalmente suficiente
|
|
||||||
ls /dev/video*
|
|
||||||
```
|
|
||||||
|
|
||||||
Para iniciar scrcpy usando una fuente v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # deshabilita la transmisión de imagen
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # más corto
|
|
||||||
```
|
|
||||||
|
|
||||||
(reemplace `N` con el ID del dispositivo, compruebe con `ls /dev/video*`)
|
|
||||||
|
|
||||||
Una vez habilitado, podés abrir el stream del video con una herramienta compatible con v4l2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ffplay -i /dev/videoN
|
|
||||||
vlc v4l2:///dev/videoN # VLC puede agregar un delay por buffering
|
|
||||||
```
|
|
||||||
|
|
||||||
Por ejemplo, podrías capturar el video usando [OBS].
|
|
||||||
|
|
||||||
[OBS]: https://obsproject.com/
|
|
||||||
|
|
||||||
|
|
||||||
#### Buffering
|
|
||||||
|
|
||||||
Es posible agregar buffering al video. Esto reduce el ruido en la imagen ("jitter")
|
|
||||||
pero aumenta la latencia (vea [#2464]).
|
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
La opción de buffering está disponible para la transmisión de imagen:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --display-buffer=50 # agrega 50 ms de buffering a la imagen
|
|
||||||
```
|
|
||||||
|
|
||||||
y las fuentes V4L2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-buffer=500 # agrega 500 ms de buffering a la fuente v4l2
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Conexión
|
### Conexión
|
||||||
|
|
||||||
#### TCP/IP (Inalámbrica)
|
#### Inalámbrica
|
||||||
|
|
||||||
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP.
|
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP:
|
||||||
El dispositivo debe estar conectado a la misma red que la computadora:
|
|
||||||
|
|
||||||
##### Automático
|
|
||||||
|
|
||||||
La opción `--tcpip` permite configurar la conexión automáticamente. Hay 2 variables.
|
|
||||||
|
|
||||||
Si el dispositivo (accesible en 192.168.1.1 para este ejemplo) ya está escuchando
|
|
||||||
en un puerto (generalmente 5555) esperando una conexión adb entrante, entonces corré:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip=192.168.1.1 # el puerto default es 5555
|
|
||||||
scrcpy --tcpip=192.168.1.1:5555
|
|
||||||
```
|
|
||||||
|
|
||||||
Si el dispositivo no tiene habilitado el modo adb TCP/IP (o si no sabés la dirección IP),
|
|
||||||
entonces conectá el dispositivo por USB y corré:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip # sin argumentos
|
|
||||||
```
|
|
||||||
|
|
||||||
El programa buscará automáticamente la IP del dispositivo, habilitará el modo TCP/IP, y
|
|
||||||
se conectará al dispositivo antes de comenzar a transmitir la imagen.
|
|
||||||
|
|
||||||
##### Manual
|
|
||||||
|
|
||||||
Como alternativa, se puede habilitar la conexión TCP/IP manualmente usando `adb`:
|
|
||||||
|
|
||||||
1. Conecta el dispositivo al mismo Wi-Fi que tu computadora.
|
1. Conecta el dispositivo al mismo Wi-Fi que tu computadora.
|
||||||
2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
|
2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
|
||||||
@@ -418,7 +302,7 @@ scrcpy -s 192.168.0.1:5555 # versión breve
|
|||||||
|
|
||||||
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
|
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
|
||||||
|
|
||||||
#### Iniciar automáticamente al detectar dispositivo
|
#### Autoiniciar al detectar dispositivo
|
||||||
|
|
||||||
Puedes utilizar [AutoAdb]:
|
Puedes utilizar [AutoAdb]:
|
||||||
|
|
||||||
@@ -428,82 +312,37 @@ autoadb scrcpy -s '{}'
|
|||||||
|
|
||||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
#### Túneles
|
#### Túnel SSH
|
||||||
|
|
||||||
Para conectarse a un dispositivo remoto, es posible conectar un cliente local `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_).
|
Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_):
|
||||||
|
|
||||||
##### Servidor ADB remoto
|
|
||||||
|
|
||||||
Para conectarse a un servidor ADB remoto, haz que el servidor escuche en todas las interfaces:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb kill-server
|
adb kill-server # cierra el servidor local adb en 5037
|
||||||
adb -a nodaemon server start
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
# conserva este servidor abierto
|
# conserva este servidor abierto
|
||||||
```
|
```
|
||||||
|
|
||||||
**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.**
|
Desde otra terminal:
|
||||||
|
|
||||||
Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra
|
|
||||||
terminal, corré scrcpy:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
|
||||||
scrcpy --tunnel-host=192.168.1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
Por default, scrcpy usa el puerto local que se usó para establecer el tunel
|
|
||||||
`adb forward` (típicamente `27183`, vea `--port`). También es posible forzar un
|
|
||||||
puerto diferente (puede resultar útil en situaciones más complejas, donde haya
|
|
||||||
múltiples redirecciones):
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --tunnel-port=1234
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
##### Túnel SSH
|
|
||||||
|
|
||||||
Para comunicarse con un servidor ADB remoto de forma segura, es preferible usar un túnel SSH.
|
|
||||||
|
|
||||||
Primero, asegurate que el servidor ADB está corriendo en la computadora remota:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb start-server
|
|
||||||
```
|
|
||||||
|
|
||||||
Después, establecé el túnel SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# local 5038 --> remoto 5037
|
|
||||||
# local 27183 <-- remoto 27183
|
|
||||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
|
||||||
# conserva este servidor abierto
|
|
||||||
```
|
|
||||||
|
|
||||||
Desde otra terminal, corré scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
|
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# local 5038 --> remoto 5037
|
adb kill-server # cierra el servidor local adb en 5037
|
||||||
# local 27183 --> remoto 27183
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
|
||||||
# conserva este servidor abierto
|
# conserva este servidor abierto
|
||||||
```
|
```
|
||||||
|
|
||||||
Desde otra terminal, corré scrcpy:
|
Desde otra terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy --force-adb-forward
|
scrcpy --force-adb-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
|
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -563,7 +402,7 @@ Se puede rotar la ventana:
|
|||||||
scrcpy --rotation 1
|
scrcpy --rotation 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Los posibles valores son:
|
Los valores posibles son:
|
||||||
- `0`: sin rotación
|
- `0`: sin rotación
|
||||||
- `1`: 90 grados contrarreloj
|
- `1`: 90 grados contrarreloj
|
||||||
- `2`: 180 grados
|
- `2`: 180 grados
|
||||||
@@ -577,7 +416,7 @@ Nótese que _scrcpy_ maneja 3 diferentes rotaciones:
|
|||||||
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
|
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
|
||||||
|
|
||||||
|
|
||||||
### Otras opciones
|
### Otras opciones menores
|
||||||
|
|
||||||
#### Solo lectura ("Read-only")
|
#### Solo lectura ("Read-only")
|
||||||
|
|
||||||
@@ -640,12 +479,14 @@ scrcpy -Sw # versión breve
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Apagar al cerrar la aplicación
|
#### Renderizar frames vencidos
|
||||||
|
|
||||||
Para apagar la pantalla del dispositivo al cerrar scrcpy:
|
Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior.
|
||||||
|
|
||||||
|
Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --power-off-on-close
|
scrcpy --render-expired-frames
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Mostrar clicks
|
#### Mostrar clicks
|
||||||
@@ -707,8 +548,6 @@ Además, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite inyectar el texto
|
|||||||
|
|
||||||
Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`.
|
|
||||||
|
|
||||||
#### Pellizcar para zoom
|
#### Pellizcar para zoom
|
||||||
|
|
||||||
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
|
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
|
||||||
@@ -717,48 +556,6 @@ Más precisamente, mantén <kbd>Ctrl</kbd> mientras presionas botón izquierdo.
|
|||||||
|
|
||||||
Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla.
|
Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla.
|
||||||
|
|
||||||
#### Emular teclado físico (HID)
|
|
||||||
|
|
||||||
Por default, scrcpy usa el sistema de Android para la injección de teclas o texto:
|
|
||||||
funciona en todas partes, pero está limitado a ASCII.
|
|
||||||
|
|
||||||
En Linux, scrcpy puede emular un teclado USB físico en Android para proveer
|
|
||||||
una mejor experiencia al enviar _inputs_ (usando [USB HID vía AOAv2][hid-aoav2]):
|
|
||||||
deshabilita el teclado virtual y funciona para todos los caracteres y IME.
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
Sin embargo, solo funciona si el dispositivo está conectado por USB, y por ahora
|
|
||||||
solo funciona en Linux.
|
|
||||||
|
|
||||||
Para habilitar este modo:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # más corto
|
|
||||||
```
|
|
||||||
|
|
||||||
Si por alguna razón falla (por ejemplo si el dispositivo no está conectado vía
|
|
||||||
USB), automáticamente vuelve al modo default (un mensaje se escribirá en la consola).
|
|
||||||
Se puede usar los mismos argumentos en la línea de comandos tanto si se conecta con
|
|
||||||
USB o vía TCP/IP.
|
|
||||||
|
|
||||||
En este modo, los _raw key events_ (_scancodes_) se envían al dispositivo, independientemente
|
|
||||||
del mapeo del teclado en el host. Por eso, si el diseño de tu teclado no concuerda, debe ser
|
|
||||||
configurado en el dispositivo Android, en Ajustes → Sistema → Idioma y Entrada de Texto
|
|
||||||
→ [Teclado Físico].
|
|
||||||
|
|
||||||
Se puede iniciar automáticamente en esta página de ajustes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
|
||||||
```
|
|
||||||
|
|
||||||
Sin embargo, la opción solo está disponible cuando el teclado HID está activo
|
|
||||||
(o cuando se conecta un teclado físico).
|
|
||||||
|
|
||||||
[Teclado Físico]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
|
|
||||||
#### Preferencias de inyección de texto
|
#### Preferencias de inyección de texto
|
||||||
|
|
||||||
@@ -776,23 +573,13 @@ scrcpy --prefer-text
|
|||||||
|
|
||||||
(Pero esto romperá el comportamiento del teclado en los juegos)
|
(Pero esto romperá el comportamiento del teclado en los juegos)
|
||||||
|
|
||||||
Por el contrario, se puede forzar scrcpy para siempre injectar _raw key events_:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --raw-key-events
|
|
||||||
```
|
|
||||||
|
|
||||||
Estas opciones no tienen efecto en los teclados HID (todos los _key events_ son enviados como
|
|
||||||
_scancodes_ en este modo).
|
|
||||||
|
|
||||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
#### Repetir tecla
|
#### Repetir tecla
|
||||||
|
|
||||||
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede
|
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
|
||||||
causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
|
|
||||||
|
|
||||||
Para evitar enviar _key events_ repetidos:
|
Para evitar enviar _key events_ repetidos:
|
||||||
|
|
||||||
@@ -800,9 +587,6 @@ Para evitar enviar _key events_ repetidos:
|
|||||||
scrcpy --no-key-repeat
|
scrcpy --no-key-repeat
|
||||||
```
|
```
|
||||||
|
|
||||||
Estas opciones no tienen efecto en los teclados HID (Android maneja directamente
|
|
||||||
las repeticiones de teclas en este modo)
|
|
||||||
|
|
||||||
|
|
||||||
#### Botón derecho y botón del medio
|
#### Botón derecho y botón del medio
|
||||||
|
|
||||||
@@ -824,15 +608,14 @@ No hay respuesta visual, un mensaje se escribirá en la consola.
|
|||||||
|
|
||||||
#### Enviar archivos al dispositivo
|
#### Enviar archivos al dispositivo
|
||||||
|
|
||||||
Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte
|
Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
|
||||||
un archivo (no APK) a la ventana de _scrcpy_.
|
|
||||||
|
|
||||||
No hay ninguna respuesta visual, un mensaje se escribirá en la consola.
|
No hay respuesta visual, un mensaje se escribirá en la consola.
|
||||||
|
|
||||||
El directorio de destino puede ser modificado al iniciar:
|
El directorio de destino puede ser modificado al iniciar:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Movies/
|
scrcpy --push-target=/sdcard/Download/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -864,48 +647,36 @@ _<kbd>[Super]</kbd> es generalmente la tecla <kbd>Windows</kbd> o <kbd>Cmd</kbd>
|
|||||||
|
|
||||||
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
||||||
|
|
||||||
| Acción | Atajo
|
| Acción | Atajo
|
||||||
| ------------------------------------------- |:-----------------------------
|
| ------------------------------------------- |:-----------------------------
|
||||||
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
|
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
|
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
|
||||||
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
|
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
|
||||||
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click izquierdo¹_
|
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click¹_
|
||||||
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click medio_
|
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
|
||||||
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click derecho²_
|
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
|
||||||
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Cuarto botón³_
|
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
| Click en `MENÚ` (desbloquear pantalla)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
|
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
|
||||||
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
|
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
|
||||||
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
| Encendido | _Botón derecho²_
|
| Encendido | _Botón derecho²_
|
||||||
| Apagar pantalla (manteniendo la transmisión) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Quinto botón³_
|
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
| Abrir panel de configuración | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doble quinto botón³_
|
| Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Cerrar paneles | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Copiar al portapapeles⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
| Cortar al portapapeles⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Synchronizar portapapeles y pegar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Synchronizar portapapeles y pegar⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
|
||||||
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
|
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
|
||||||
| Arrastrar y soltar un archivo (APK) | Instalar APK desde la computadora
|
|
||||||
| Arrastrar y soltar un archivo (no APK) | [Mover archivo al dispositivo](#enviar-archivos-al-dispositivo)
|
|
||||||
|
|
||||||
_¹Doble click en los bordes negros para eliminarlos._
|
_¹Doble click en los bordes negros para eliminarlos._
|
||||||
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
|
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
|
||||||
_³Cuarto y quinto botón del mouse, si tu mouse los tiene._
|
_³Solo en Android >= 7._
|
||||||
_⁴Para las apps react-native en desarrollo, `MENU` activa el menú de desarrollo._
|
|
||||||
_⁵Solo en Android >= 7._
|
|
||||||
|
|
||||||
Los shortcuts con teclas repetidas se ejecutan soltando y volviendo a apretar la tecla
|
|
||||||
por segunda vez. Por ejemplo, para ejecutar "Abrir panel de configuración":
|
|
||||||
|
|
||||||
1. Apretá y mantené apretado <kbd>MOD</kbd>.
|
|
||||||
2. Después apretá dos veces la tecla <kbd>n</kbd>.
|
|
||||||
3. Por último, soltá la tecla <kbd>MOD</kbd>.
|
|
||||||
|
|
||||||
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
|
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
|
||||||
|
|
||||||
@@ -920,8 +691,6 @@ ADB=/path/to/adb scrcpy
|
|||||||
|
|
||||||
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`.
|
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
Para sobreescribir el ícono, configure el path en `SCRCPY_ICON_PATH`.
|
|
||||||
|
|
||||||
|
|
||||||
## ¿Por qué _scrcpy_?
|
## ¿Por qué _scrcpy_?
|
||||||
|
|
||||||
@@ -951,7 +720,7 @@ Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
|
|||||||
## Licencia
|
## Licencia
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -801,7 +801,7 @@ Bakınız [FAQ](FAQ.md).
|
|||||||
## Lisans
|
## Lisans
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,46 +1,28 @@
|
|||||||
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
_Only the original [README](README.md) is guaranteed to be up-to-date._
|
||||||
|
|
||||||
_只有原版的 [README](README.md)是保证最新的。_
|
只有原版的[README](README.md)会保持最新。
|
||||||
|
|
||||||
Current version is based on [f4c7044]
|
本文根据[ed130e05]进行翻译。
|
||||||
|
|
||||||
本文根据[f4c7044]进行翻译。
|
[ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md
|
||||||
|
|
||||||
[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md
|
# scrcpy (v1.17)
|
||||||
|
|
||||||
# scrcpy (v1.22)
|
|
||||||
|
|
||||||
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
|
||||||
|
|
||||||
_发音为 "**scr**een **c**o**py**"_
|
|
||||||
|
|
||||||
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。
|
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows_ 和 _macOS_。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
本应用专注于:
|
它专注于:
|
||||||
|
|
||||||
- **轻量**: 原生,仅显示设备屏幕
|
- **轻量** (原生,仅显示设备屏幕)
|
||||||
- **性能**: 30~120fps,取决于设备
|
- **性能** (30~60fps)
|
||||||
- **质量**: 分辨率可达 1920×1080 或更高
|
- **质量** (分辨率可达 1920×1080 或更高)
|
||||||
- **低延迟**: [35~70ms][lowlatency]
|
- **低延迟** ([35~70ms][lowlatency])
|
||||||
- **快速启动**: 最快 1 秒内即可显示第一帧
|
- **快速启动** (最快 1 秒内即可显示第一帧)
|
||||||
- **无侵入性**: 不会在设备上遗留任何程序
|
- **无侵入性** (不会在设备上遗留任何程序)
|
||||||
- **用户利益**: 无需帐号,无广告,无需联网
|
|
||||||
- **自由**: 自由和开源软件
|
|
||||||
|
|
||||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
功能:
|
|
||||||
- [屏幕录制](#屏幕录制)
|
|
||||||
- 镜像时[关闭设备屏幕](#关闭设备屏幕)
|
|
||||||
- 双向[复制粘贴](#复制粘贴)
|
|
||||||
- [可配置显示质量](#采集设置)
|
|
||||||
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
|
|
||||||
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
|
|
||||||
- [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux)
|
|
||||||
- [OTG模式](#otg) (仅限 Linux)
|
|
||||||
- 更多 ……
|
|
||||||
|
|
||||||
## 系统要求
|
## 系统要求
|
||||||
|
|
||||||
@@ -59,31 +41,14 @@ _发音为 "**scr**een **c**o**py**"_
|
|||||||
|
|
||||||
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
### 概要
|
|
||||||
|
|
||||||
- Linux: `apt install scrcpy`
|
|
||||||
- Windows: [下载][direct-win64]
|
|
||||||
- macOS: `brew install scrcpy`
|
|
||||||
|
|
||||||
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
|
|
||||||
|
|
||||||
[BUILD]: BUILD.md
|
|
||||||
[BUILD_simple]: BUILD.md#simple
|
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
在 Debian 和 Ubuntu 上:
|
在 Debian (目前仅支持 _testing_ 和 _sid_ 分支) 和Ubuntu (20.04) 上:
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install scrcpy
|
apt install scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
在 Arch Linux 上:
|
|
||||||
|
|
||||||
```
|
|
||||||
pacman -S scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
|
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
|
||||||
|
|
||||||
[snap-link]: https://snapstats.org/snaps/scrcpy
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
@@ -95,17 +60,23 @@ pacman -S scrcpy
|
|||||||
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
|
对 Arch Linux 我们提供 [AUR] 包: [`scrcpy`][aur-link]。
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
|
对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
|
||||||
|
|
||||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。
|
您也可以[自行构建][BUILD] (不必担心,这并不困难)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
在 Windows 上,为简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
|
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
|
||||||
|
|
||||||
- [README](README.md#windows)
|
- [README](README.md#windows)
|
||||||
|
|
||||||
@@ -143,17 +114,13 @@ brew install scrcpy
|
|||||||
你还需要在 `PATH` 内有 `adb`。如果还没有:
|
你还需要在 `PATH` 内有 `adb`。如果还没有:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install android-platform-tools
|
# Homebrew >= 2.6.0
|
||||||
|
brew install --cask android-platform-tools
|
||||||
|
|
||||||
|
# Homebrew < 2.6.0
|
||||||
|
brew cask install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
或者通过 [MacPorts],该方法同时设置好 adb:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo port install scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
[MacPorts]: https://www.macports.org/
|
|
||||||
|
|
||||||
您也可以[自行构建][BUILD]。
|
您也可以[自行构建][BUILD]。
|
||||||
|
|
||||||
|
|
||||||
@@ -173,7 +140,7 @@ scrcpy --help
|
|||||||
|
|
||||||
## 功能介绍
|
## 功能介绍
|
||||||
|
|
||||||
### 采集设置
|
### 捕获设置
|
||||||
|
|
||||||
#### 降低分辨率
|
#### 降低分辨率
|
||||||
|
|
||||||
@@ -191,7 +158,7 @@ scrcpy -m 1024 # 简写
|
|||||||
|
|
||||||
#### 修改码率
|
#### 修改码率
|
||||||
|
|
||||||
默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps):
|
默认码率是 8Mbps。要改变视频的码率 (例如改为 2Mbps):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M
|
scrcpy --bit-rate 2M
|
||||||
@@ -200,7 +167,7 @@ scrcpy -b 2M # 简写
|
|||||||
|
|
||||||
#### 限制帧率
|
#### 限制帧率
|
||||||
|
|
||||||
要限制采集的帧率:
|
要限制捕获的帧率:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --max-fps 15
|
scrcpy --max-fps 15
|
||||||
@@ -227,11 +194,10 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
|
|||||||
要锁定镜像画面的方向:
|
要锁定镜像画面的方向:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # 初始(目前)方向
|
scrcpy --lock-video-orientation 0 # 自然方向
|
||||||
scrcpy --lock-video-orientation=0 # 自然方向
|
scrcpy --lock-video-orientation 1 # 逆时针旋转 90°
|
||||||
scrcpy --lock-video-orientation=1 # 逆时针旋转 90°
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
scrcpy --lock-video-orientation=2 # 180°
|
scrcpy --lock-video-orientation 3 # 顺时针旋转 90°
|
||||||
scrcpy --lock-video-orientation=3 # 顺时针旋转 90°
|
|
||||||
```
|
```
|
||||||
|
|
||||||
只影响录制的方向。
|
只影响录制的方向。
|
||||||
@@ -253,9 +219,7 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### 采集
|
### 屏幕录制
|
||||||
|
|
||||||
#### 屏幕录制
|
|
||||||
|
|
||||||
可以在镜像的同时录制视频:
|
可以在镜像的同时录制视频:
|
||||||
|
|
||||||
@@ -277,117 +241,24 @@ scrcpy -Nr file.mkv
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
#### v4l2loopback
|
|
||||||
|
|
||||||
在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。
|
|
||||||
|
|
||||||
需安装 `v4l2loopback` 模块:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install v4l2loopback-dkms
|
|
||||||
```
|
|
||||||
|
|
||||||
创建一个 v4l2 设备:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo modprobe v4l2loopback
|
|
||||||
```
|
|
||||||
|
|
||||||
这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。
|
|
||||||
|
|
||||||
列出已启用的设备:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 需要 v4l-utils 包
|
|
||||||
v4l2-ctl --list-devices
|
|
||||||
|
|
||||||
# 简单但或许足够
|
|
||||||
ls /dev/video*
|
|
||||||
```
|
|
||||||
|
|
||||||
使用一个 v4l2 漏开启 scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像
|
|
||||||
scrcpy --v4l2-sink=/dev/videoN -N # 简写
|
|
||||||
```
|
|
||||||
|
|
||||||
(将 `N` 替换为设备 ID,使用 `ls /dev/video*` 命令查看)
|
|
||||||
|
|
||||||
启用之后,可以使用 v4l2 工具打开视频流:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ffplay -i /dev/videoN
|
|
||||||
vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟
|
|
||||||
```
|
|
||||||
|
|
||||||
例如,可以在 [OBS] 中采集视频。
|
|
||||||
|
|
||||||
[OBS]: https://obsproject.com/
|
|
||||||
|
|
||||||
|
|
||||||
#### 缓冲
|
|
||||||
|
|
||||||
可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。
|
|
||||||
|
|
||||||
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
|
||||||
|
|
||||||
对于显示缓冲:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲
|
|
||||||
```
|
|
||||||
|
|
||||||
对于 V4L2 漏:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 连接
|
### 连接
|
||||||
|
|
||||||
#### TCP/IP (无线)
|
#### 无线
|
||||||
|
|
||||||
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。
|
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备:
|
||||||
|
|
||||||
##### 自动配置
|
|
||||||
|
|
||||||
参数 `--tcpip` 允许自动配置连接。这里有两种方式。
|
|
||||||
|
|
||||||
对于传入的 adb 连接,如果设备(在这个例子中以192.168.1.1为可用地址)已经监听了一个端口(通常是5555),运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip=192.168.1.1 # 默认端口是5555
|
|
||||||
scrcpy --tcpip=192.168.1.1:5555
|
|
||||||
```
|
|
||||||
|
|
||||||
如果adb TCP/IP(无线) 模式在某些设备上不被启用(或者你不知道IP地址),用USB连接设备,然后运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --tcpip # 无需其他参数
|
|
||||||
```
|
|
||||||
|
|
||||||
这将会自动寻找设备IP地址,启用TCP/IP模式,然后在启动之前连接到设备。
|
|
||||||
|
|
||||||
##### 手动配置
|
|
||||||
|
|
||||||
或者,可以通过 `adb` 使用手动启用 TCP/IP 连接:
|
|
||||||
|
|
||||||
1. 将设备和电脑连接至同一 Wi-Fi。
|
1. 将设备和电脑连接至同一 Wi-Fi。
|
||||||
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
|
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb shell ip route | awk '{print $9}'
|
adb shell ip route | awk '{print $9}'
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 启用设备的网络 adb 功能:`adb tcpip 5555`。
|
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
|
||||||
4. 断开设备的 USB 连接。
|
4. 断开设备的 USB 连接。
|
||||||
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。
|
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_.
|
||||||
6. 正常运行 `scrcpy`。
|
6. 正常运行 `scrcpy`。
|
||||||
|
|
||||||
降低比特率和分辨率可能很有用:
|
可能需要降低码率和分辨率:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --bit-rate 2M --max-size 800
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
@@ -425,80 +296,38 @@ autoadb scrcpy -s '{}'
|
|||||||
|
|
||||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
#### 隧道
|
#### SSH 隧道
|
||||||
|
|
||||||
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)。
|
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同):
|
||||||
|
|
||||||
##### 远程ADB服务器
|
|
||||||
|
|
||||||
要连接到一个远程ADB服务器,让服务器在所有接口上监听:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb kill-server
|
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
|
||||||
adb -a nodaemon server start
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
# 保持该窗口开启
|
# 保持该窗口开启
|
||||||
```
|
```
|
||||||
|
|
||||||
**警告:所有客户端与ADB服务器的交流都是未加密的。**
|
在另一个终端:
|
||||||
|
|
||||||
假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
|
||||||
scrcpy --tunnel-host=192.168.1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
默认情况下,scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用):
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --tunnel-port=1234
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
##### SSH 隧道
|
|
||||||
|
|
||||||
为了安全地与远程ADB服务器通信,最好使用SSH隧道。
|
|
||||||
|
|
||||||
首先,确保ADB服务器正在远程计算机上运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb start-server
|
|
||||||
```
|
|
||||||
|
|
||||||
然后,建立一个SSH隧道:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 本地 5038 --> 远程 5037
|
|
||||||
# 本地 27183 <-- 远程 27183
|
|
||||||
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
|
||||||
# 保持该窗口开启
|
|
||||||
```
|
|
||||||
|
|
||||||
在另一个终端上,运行scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy
|
scrcpy
|
||||||
```
|
```
|
||||||
|
|
||||||
若要不使用远程端口转发,可以强制使用正向连接(注意是 `-L` 而不是 `-R` ):
|
若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` 和 `-R` 的区别):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 本地 5038 --> 远程 5037
|
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
|
||||||
# 本地 27183 <-- 远程 27183
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
|
||||||
# 保持该窗口开启
|
# 保持该窗口开启
|
||||||
```
|
```
|
||||||
|
|
||||||
在另一个终端上,运行scrcpy:
|
在另一个终端:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ADB_SERVER_SOCKET=tcp:localhost:5038
|
|
||||||
scrcpy --force-adb-forward
|
scrcpy --force-adb-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
类似地,对于无线连接,可能需要降低画面质量:
|
类似无线网络连接,可能需要降低画面质量:
|
||||||
|
|
||||||
```
|
```
|
||||||
scrcpy -b2M -m800 --max-fps 15
|
scrcpy -b2M -m800 --max-fps 15
|
||||||
@@ -511,7 +340,7 @@ scrcpy -b2M -m800 --max-fps 15
|
|||||||
窗口的标题默认为设备型号。可以通过如下命令修改:
|
窗口的标题默认为设备型号。可以通过如下命令修改:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --window-title "我的设备"
|
scrcpy --window-title 'My device'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 位置和大小
|
#### 位置和大小
|
||||||
@@ -524,7 +353,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
|||||||
|
|
||||||
#### 无边框
|
#### 无边框
|
||||||
|
|
||||||
禁用窗口边框:
|
关闭边框:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --window-borderless
|
scrcpy --window-borderless
|
||||||
@@ -540,7 +369,7 @@ scrcpy --always-on-top
|
|||||||
|
|
||||||
#### 全屏
|
#### 全屏
|
||||||
|
|
||||||
您可以通过如下命令直接全屏启动 scrcpy:
|
您可以通过如下命令直接全屏启动scrcpy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --fullscreen
|
scrcpy --fullscreen
|
||||||
@@ -565,7 +394,7 @@ scrcpy --rotation 1
|
|||||||
|
|
||||||
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
|
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
|
||||||
|
|
||||||
需要注意的是, _scrcpy_ 中有三类旋转方向:
|
需要注意的是, _scrcpy_ 有三个不同的方向:
|
||||||
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
|
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
|
||||||
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
|
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
|
||||||
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
|
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
|
||||||
@@ -575,7 +404,7 @@ scrcpy --rotation 1
|
|||||||
|
|
||||||
#### 只读
|
#### 只读
|
||||||
|
|
||||||
禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放):
|
禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-control
|
scrcpy --no-control
|
||||||
@@ -601,14 +430,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
|
|||||||
|
|
||||||
#### 保持常亮
|
#### 保持常亮
|
||||||
|
|
||||||
阻止设备在连接时一段时间后休眠:
|
阻止设备在连接时休眠:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --stay-awake
|
scrcpy --stay-awake
|
||||||
scrcpy -w
|
scrcpy -w
|
||||||
```
|
```
|
||||||
|
|
||||||
scrcpy 关闭时会恢复设备原来的设置。
|
程序关闭时会恢复设备原来的设置。
|
||||||
|
|
||||||
|
|
||||||
#### 关闭设备屏幕
|
#### 关闭设备屏幕
|
||||||
@@ -622,7 +451,7 @@ scrcpy -S
|
|||||||
|
|
||||||
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。
|
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。
|
||||||
|
|
||||||
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>。
|
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
||||||
|
|
||||||
在Android上,`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
|
在Android上,`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
|
||||||
|
|
||||||
@@ -633,17 +462,20 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw
|
scrcpy -Sw
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 退出时息屏
|
|
||||||
|
|
||||||
scrcpy 退出时关闭设备屏幕:
|
#### 渲染过期帧
|
||||||
|
|
||||||
|
默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。
|
||||||
|
|
||||||
|
强制渲染所有帧 (可能导致延迟变高):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --power-off-on-close
|
scrcpy --render-expired-frames
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 显示触摸
|
#### 显示触摸
|
||||||
|
|
||||||
在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。
|
在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)。
|
||||||
|
|
||||||
Android 在 _开发者选项_ 中提供了这项功能。
|
Android 在 _开发者选项_ 中提供了这项功能。
|
||||||
|
|
||||||
@@ -700,91 +532,18 @@ scrcpy --disable-screensaver
|
|||||||
|
|
||||||
一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd> 和 <kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。
|
一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd> 和 <kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。
|
||||||
|
|
||||||
要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。
|
|
||||||
|
|
||||||
#### 双指缩放
|
#### 双指缩放
|
||||||
|
|
||||||
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按下并拖动鼠标_。
|
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
|
||||||
|
|
||||||
在按住 <kbd>Ctrl</kbd> 时按下鼠标左键,直到松开鼠标左键前,移动鼠标会使屏幕内容相对于屏幕中心进行缩放或旋转 (如果应用支持)。
|
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
|
||||||
|
|
||||||
具体来说,_scrcpy_ 会在鼠标位置,以及鼠标以屏幕中心镜像的位置分别生成触摸事件。
|
实际上,_scrcpy_ 会在以屏幕中心对称的位置上生成由“虚拟手指”发出的额外触摸事件。
|
||||||
|
|
||||||
#### 物理键盘模拟 (HID)
|
|
||||||
|
|
||||||
默认情况下,scrcpy 使用安卓按键或文本注入,这在任何情况都可以使用,但仅限于ASCII字符。
|
|
||||||
|
|
||||||
在 Linux 上,scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
不过,这种方法仅支持 USB 连接以及 Linux平台。
|
|
||||||
|
|
||||||
启用 HID 模式:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # 简写
|
|
||||||
```
|
|
||||||
|
|
||||||
如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。
|
|
||||||
|
|
||||||
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
|
|
||||||
|
|
||||||
[实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
#### 物理鼠标模拟 (HID)
|
|
||||||
|
|
||||||
与物理键盘模拟类似,可以模拟一个物理鼠标。 同样,它仅在设备通过 USB 连接时才有效,并且目前仅在 Linux 上受支持。
|
|
||||||
|
|
||||||
默认情况下,scrcpy 使用 Android 鼠标事件注入,使用绝对坐标。 通过模拟物理鼠标,在Android设备上出现鼠标指针,并注入鼠标相对运动、点击和滚动。
|
|
||||||
|
|
||||||
启用此模式:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-mouse
|
|
||||||
scrcpy -M # 简写
|
|
||||||
```
|
|
||||||
|
|
||||||
您还可以将 `--forward-all-clicks` 添加到 [转发所有点击][forward_all_clicks].
|
|
||||||
|
|
||||||
[forward_all_clicks]: #右键和中键
|
|
||||||
|
|
||||||
启用此模式后,计算机鼠标将被“捕获”(鼠标指针从计算机上消失并出现在 Android 设备上)。
|
|
||||||
|
|
||||||
特殊的捕获键,<kbd>Alt</kbd> 或 <kbd>Super</kbd>,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。
|
|
||||||
|
|
||||||
|
|
||||||
#### OTG
|
#### 文字注入偏好
|
||||||
|
|
||||||
可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_,就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。
|
打字的时候,系统会产生两种[事件][textevents]:
|
||||||
|
|
||||||
在这个模式下,_adb_ (USB 调试)是不必要的,且镜像被禁用。
|
|
||||||
|
|
||||||
启用 OTG 模式:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg
|
|
||||||
# 如果有多个 USB 设备可用,则通过序列号选择
|
|
||||||
scrcpy --otg -s 0123456789abcdef
|
|
||||||
```
|
|
||||||
|
|
||||||
只开启 HID 键盘 或 HID 鼠标 是可行的:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg --hid-keyboard # 只开启 HID 键盘
|
|
||||||
scrcpy --otg --hid-mouse # 只开启 HID 鼠标
|
|
||||||
scrcpy --otg --hid-keyboard --hid-mouse # 开启 HID 键盘 和 HID 鼠标
|
|
||||||
# 为了方便,默认两者都开启
|
|
||||||
scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
|
|
||||||
```
|
|
||||||
|
|
||||||
像 `--hid-keyboard` 和 `--hid-mouse` 一样,它只在设备通过 USB 连接时才有效,且目前仅在 Linux 上支持。
|
|
||||||
|
|
||||||
|
|
||||||
#### 文本注入偏好
|
|
||||||
|
|
||||||
输入文字的时候,系统会产生两种[事件][textevents]:
|
|
||||||
- _按键事件_ ,代表一个按键被按下或松开。
|
- _按键事件_ ,代表一个按键被按下或松开。
|
||||||
- _文本事件_ ,代表一个字符被输入。
|
- _文本事件_ ,代表一个字符被输入。
|
||||||
|
|
||||||
@@ -796,15 +555,7 @@ scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
|
|||||||
scrcpy --prefer-text
|
scrcpy --prefer-text
|
||||||
```
|
```
|
||||||
|
|
||||||
(但这会导致键盘在游戏中工作不正常)
|
(这会导致键盘在游戏中工作不正常)
|
||||||
|
|
||||||
相反,您可以强制始终注入原始按键事件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --raw-key-events
|
|
||||||
```
|
|
||||||
|
|
||||||
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
|
|
||||||
|
|
||||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
@@ -812,7 +563,7 @@ scrcpy --raw-key-events
|
|||||||
|
|
||||||
#### 按键重复
|
#### 按键重复
|
||||||
|
|
||||||
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。
|
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。
|
||||||
|
|
||||||
避免转发重复按键事件:
|
避免转发重复按键事件:
|
||||||
|
|
||||||
@@ -820,11 +571,10 @@ scrcpy --raw-key-events
|
|||||||
scrcpy --no-key-repeat
|
scrcpy --no-key-repeat
|
||||||
```
|
```
|
||||||
|
|
||||||
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
|
|
||||||
|
|
||||||
#### 右键和中键
|
#### 右键和中键
|
||||||
|
|
||||||
默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
|
默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --forward-all-clicks
|
scrcpy --forward-all-clicks
|
||||||
@@ -837,27 +587,27 @@ scrcpy --forward-all-clicks
|
|||||||
|
|
||||||
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
|
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
|
||||||
|
|
||||||
不会有视觉反馈,终端会输出一条日志。
|
该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。
|
||||||
|
|
||||||
|
|
||||||
#### 将文件推送至设备
|
#### 将文件推送至设备
|
||||||
|
|
||||||
要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
|
要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
|
||||||
|
|
||||||
不会有视觉反馈,终端会输出一条日志。
|
该操作没有可见的响应,只会在控制台输出日志。
|
||||||
|
|
||||||
在启动时可以修改目标目录:
|
在启动时可以修改目标目录:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Movies/
|
scrcpy --push-target /sdcard/foo/bar/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### 音频转发
|
### 音频转发
|
||||||
|
|
||||||
_Scrcpy_ 不支持音频。请使用 [sndcpy]。
|
_Scrcpy_ 不支持音频。请使用 [sndcpy].
|
||||||
|
|
||||||
另见 [issue #14]。
|
另外请阅读 [issue #14]。
|
||||||
|
|
||||||
[sndcpy]: https://github.com/rom1v/sndcpy
|
[sndcpy]: https://github.com/rom1v/sndcpy
|
||||||
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
@@ -882,47 +632,36 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
|
|||||||
|
|
||||||
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
||||||
|
|
||||||
| 操作 | 快捷键
|
| 操作 | 快捷键 |
|
||||||
| --------------------------------- | :-------------------------------------------
|
| --------------------------------- | :------------------------------------------- |
|
||||||
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd>
|
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd> |
|
||||||
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_
|
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ |
|
||||||
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_
|
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ |
|
||||||
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd> |
|
||||||
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击左键¹_
|
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击¹_ |
|
||||||
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
|
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _鼠标中键_ |
|
||||||
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
|
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _鼠标右键²_ |
|
||||||
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
|
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> |
|
||||||
| 点按 `菜单` (解锁屏幕)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> |
|
||||||
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_
|
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ |
|
||||||
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_
|
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ |
|
||||||
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> |
|
||||||
| 打开屏幕 | _鼠标右键²_
|
| 打开屏幕 | _鼠标右键²_ |
|
||||||
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> |
|
||||||
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
|
||||||
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> |
|
||||||
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
|
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> |
|
||||||
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
|
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
|
||||||
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd> |
|
||||||
| 复制到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd> |
|
||||||
| 剪切到剪贴板⁵ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| 同步剪贴板并粘贴³ | <kbd>MOD</kbd>+<kbd>v</kbd> |
|
||||||
| 同步剪贴板并粘贴⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
|
||||||
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| 打开/关闭FPS显示 (在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> |
|
||||||
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_ |
|
||||||
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
|
|
||||||
| 拖放 APK 文件 | 从电脑安装 APK 文件
|
|
||||||
| 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device)
|
|
||||||
|
|
||||||
_¹双击黑边可以去除黑边。_
|
_¹双击黑边可以去除黑边_
|
||||||
_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_
|
_²点击鼠标右键将在屏幕熄灭时点亮屏幕,其余情况则视为按下返回键 。_
|
||||||
_³鼠标的第4键和第5键。_
|
_³需要安卓版本 Android >= 7。_
|
||||||
_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_
|
|
||||||
_⁵需要安卓版本 Android >= 7。_
|
|
||||||
|
|
||||||
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
|
|
||||||
|
|
||||||
1. 按下 <kbd>MOD</kbd> 不放。
|
|
||||||
2. 双击 <kbd>n</kbd>。
|
|
||||||
3. 松开 <kbd>MOD</kbd>。
|
|
||||||
|
|
||||||
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
|
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
|
||||||
|
|
||||||
@@ -931,20 +670,18 @@ _⁵需要安卓版本 Android >= 7。_
|
|||||||
|
|
||||||
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`:
|
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`:
|
||||||
|
|
||||||
```bash
|
ADB=/path/to/adb scrcpy
|
||||||
ADB=/path/to/adb scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。
|
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。
|
||||||
|
|
||||||
要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|
||||||
|
|
||||||
## 为什么叫 _scrcpy_ ?
|
## 为什么叫 _scrcpy_ ?
|
||||||
|
|
||||||
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
|
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
|
||||||
|
|
||||||
[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。
|
[`strcpy`] 复制一个 **str**ing; `scrcpy` 复制一个 **scr**een。
|
||||||
|
|
||||||
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||||
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
@@ -952,12 +689,14 @@ ADB=/path/to/adb scrcpy
|
|||||||
|
|
||||||
## 如何构建?
|
## 如何构建?
|
||||||
|
|
||||||
请查看 [BUILD]。
|
请查看[BUILD]。
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
|
||||||
|
|
||||||
## 常见问题
|
## 常见问题
|
||||||
|
|
||||||
请查看 [FAQ](FAQ.md)。
|
请查看[FAQ](FAQ.md)。
|
||||||
|
|
||||||
|
|
||||||
## 开发者
|
## 开发者
|
||||||
@@ -970,7 +709,7 @@ ADB=/path/to/adb scrcpy
|
|||||||
## 许可协议
|
## 许可协议
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -679,7 +679,7 @@ _³只支援 Android 7+。_
|
|||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2022 Romain Vimont
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,126 +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
|
|
||||||
;;
|
|
||||||
-s|--serial)
|
|
||||||
# Use 'adb devices' to list serial numbers
|
|
||||||
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
-b|--bitrate \
|
|
||||||
|--codec-options \
|
|
||||||
|--crop \
|
|
||||||
|--display \
|
|
||||||
|--display-buffer \
|
|
||||||
|--encoder \
|
|
||||||
|--max-fps \
|
|
||||||
|-m|--max-size \
|
|
||||||
|-p|--port \
|
|
||||||
|--push-target \
|
|
||||||
|--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 |
Binary file not shown.
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,16 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
|
|
||||||
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
|
|
||||||
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
|
|
||||||
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
|
|
||||||
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
|
|
||||||
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
|
|
||||||
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
|
|
||||||
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
|
|
||||||
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
|
|
||||||
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
|
|
||||||
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
|
|
||||||
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
|
|
||||||
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
|
|
||||||
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
|
|
||||||
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1 +0,0 @@
|
|||||||
@cmd
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=scrcpy (console)
|
|
||||||
GenericName=Android Remote Control
|
|
||||||
Comment=Display and control your Android device
|
|
||||||
# For some users, the PATH or ADB environment variables are set from the shell
|
|
||||||
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
|
||||||
# environment correctly initialized.
|
|
||||||
Exec=/bin/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."'
|
|
||||||
Icon=scrcpy
|
|
||||||
Terminal=true
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility;RemoteAccess;
|
|
||||||
StartupNotify=false
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=scrcpy
|
|
||||||
GenericName=Android Remote Control
|
|
||||||
Comment=Display and control your Android device
|
|
||||||
# For some users, the PATH or ADB environment variables are set from the shell
|
|
||||||
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
|
||||||
# environment correctly initialized.
|
|
||||||
Exec=/bin/sh -c '"$SHELL" -i -c scrcpy'
|
|
||||||
Icon=scrcpy
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility;RemoteAccess;
|
|
||||||
StartupNotify=false
|
|
||||||
@@ -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\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
|
||||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
|
||||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
|
||||||
{-t,--show-touches}'[Show physical touches]'
|
|
||||||
'--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
|
|
||||||
180
app/meson.build
180
app/meson.build
@@ -1,120 +1,85 @@
|
|||||||
src = [
|
src = [
|
||||||
'src/main.c',
|
'src/main.c',
|
||||||
'src/adb/adb.c',
|
'src/adb.c',
|
||||||
'src/adb/adb_device.c',
|
|
||||||
'src/adb/adb_parser.c',
|
|
||||||
'src/adb/adb_tunnel.c',
|
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/clock.c',
|
'src/clock.c',
|
||||||
'src/compat.c',
|
'src/compat.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
'src/demuxer.c',
|
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/icon.c',
|
'src/file_handler.c',
|
||||||
'src/file_pusher.c',
|
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
'src/frame_buffer.c',
|
'src/frame_buffer.c',
|
||||||
'src/input_manager.c',
|
'src/input_manager.c',
|
||||||
'src/keyboard_inject.c',
|
'src/keyboard_inject.c',
|
||||||
'src/mouse_inject.c',
|
'src/mouse_inject.c',
|
||||||
'src/opengl.c',
|
'src/opengl.c',
|
||||||
'src/options.c',
|
|
||||||
'src/receiver.c',
|
'src/receiver.c',
|
||||||
'src/recorder.c',
|
'src/recorder.c',
|
||||||
'src/rtp.c',
|
|
||||||
'src/scrcpy.c',
|
'src/scrcpy.c',
|
||||||
'src/screen.c',
|
'src/screen.c',
|
||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/version.c',
|
'src/stream.c',
|
||||||
|
'src/tiny_xpm.c',
|
||||||
'src/video_buffer.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/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/net_intr.c',
|
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/process_intr.c',
|
'src/util/str_util.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/term.c',
|
|
||||||
'src/util/thread.c',
|
'src/util/thread.c',
|
||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
conf = configuration_data()
|
|
||||||
|
|
||||||
conf.set('_POSIX_C_SOURCE', '200809L')
|
|
||||||
conf.set('_XOPEN_SOURCE', '700')
|
|
||||||
conf.set('_GNU_SOURCE', true)
|
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
windows = import('windows')
|
src += [ 'src/sys/win/process.c' ]
|
||||||
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
|
else
|
||||||
src += [
|
src += [ 'src/sys/unix/process.c' ]
|
||||||
'src/sys/unix/file.c',
|
|
||||||
'src/sys/unix/process.c',
|
|
||||||
]
|
|
||||||
if host_machine.system() == 'darwin'
|
|
||||||
conf.set('_DARWIN_C_SOURCE', true)
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux'
|
v4l2_support = host_machine.system() == 'linux'
|
||||||
if v4l2_support
|
if v4l2_support
|
||||||
src += [ 'src/v4l2_sink.c' ]
|
src += [ 'src/v4l2_sink.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
usb_support = get_option('usb')
|
aoa_hid_support = host_machine.system() == 'linux'
|
||||||
if usb_support
|
if aoa_hid_support
|
||||||
src += [
|
src += [
|
||||||
'src/usb/aoa_hid.c',
|
'src/aoa_hid.c',
|
||||||
'src/usb/hid_keyboard.c',
|
'src/hid_keyboard.c',
|
||||||
'src/usb/hid_mouse.c',
|
|
||||||
'src/usb/scrcpy_otg.c',
|
|
||||||
'src/usb/screen_otg.c',
|
|
||||||
'src/usb/usb.c',
|
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
check_functions = [
|
||||||
|
'strdup'
|
||||||
|
]
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
|
if not get_option('crossbuild_windows')
|
||||||
|
|
||||||
if not crossbuild_windows
|
|
||||||
|
|
||||||
# native build
|
# native build
|
||||||
dependencies = [
|
dependencies = [
|
||||||
dependency('libavformat', version: '>= 57.33'),
|
dependency('libavformat'),
|
||||||
dependency('libavcodec', version: '>= 57.37'),
|
dependency('libavcodec'),
|
||||||
dependency('libavutil'),
|
dependency('libavutil'),
|
||||||
dependency('sdl2', version: '>= 2.0.5'),
|
dependency('sdl2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if v4l2_support
|
if v4l2_support
|
||||||
dependencies += dependency('libavdevice')
|
dependencies += dependency('libavdevice')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if usb_support
|
if aoa_hid_support
|
||||||
dependencies += dependency('libusb-1.0')
|
dependencies += dependency('libusb-1.0')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
else
|
else
|
||||||
# cross-compile mingw32 build (from Linux to Windows)
|
# cross-compile mingw32 build (from Linux to Windows)
|
||||||
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
||||||
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
|
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
|
||||||
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
|
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
|
||||||
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
|
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
|
||||||
|
|
||||||
sdl2 = declare_dependency(
|
sdl2 = declare_dependency(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -124,40 +89,22 @@ else
|
|||||||
include_directories: include_directories(sdl2_include_dir)
|
include_directories: include_directories(sdl2_include_dir)
|
||||||
)
|
)
|
||||||
|
|
||||||
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
|
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
|
||||||
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
|
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
|
||||||
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
|
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
|
||||||
|
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
|
||||||
# ffmpeg versions are different for win32 and win64 builds
|
|
||||||
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
|
|
||||||
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
|
|
||||||
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
|
|
||||||
|
|
||||||
ffmpeg = declare_dependency(
|
ffmpeg = declare_dependency(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
|
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
|
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
|
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
|
||||||
],
|
],
|
||||||
include_directories: include_directories(ffmpeg_include_dir)
|
include_directories: include_directories(ffmpeg_include_dir)
|
||||||
)
|
)
|
||||||
|
|
||||||
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
|
|
||||||
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
|
|
||||||
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb
|
|
||||||
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
|
|
||||||
|
|
||||||
libusb = declare_dependency(
|
|
||||||
dependencies: [
|
|
||||||
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
|
|
||||||
],
|
|
||||||
include_directories: include_directories(libusb_include_dir)
|
|
||||||
)
|
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
ffmpeg,
|
ffmpeg,
|
||||||
sdl2,
|
sdl2,
|
||||||
libusb,
|
|
||||||
cc.find_library('mingw32')
|
cc.find_library('mingw32')
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -167,11 +114,7 @@ if host_machine.system() == 'windows'
|
|||||||
dependencies += cc.find_library('ws2_32')
|
dependencies += cc.find_library('ws2_32')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
check_functions = [
|
conf = configuration_data()
|
||||||
'strdup',
|
|
||||||
'asprintf',
|
|
||||||
'vasprintf',
|
|
||||||
]
|
|
||||||
|
|
||||||
foreach f : check_functions
|
foreach f : check_functions
|
||||||
if cc.has_function(f)
|
if cc.has_function(f)
|
||||||
@@ -180,9 +123,6 @@ foreach f : check_functions
|
|||||||
endif
|
endif
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
conf.set('HAVE_SOCK_CLOEXEC', host_machine.system() != 'windows' and
|
|
||||||
cc.has_header_symbol('sys/socket.h', 'SOCK_CLOEXEC'))
|
|
||||||
|
|
||||||
# the version, updated on release
|
# the version, updated on release
|
||||||
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||||
|
|
||||||
@@ -212,7 +152,7 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
|
|||||||
conf.set('HAVE_V4L2', v4l2_support)
|
conf.set('HAVE_V4L2', v4l2_support)
|
||||||
|
|
||||||
# enable HID over AOA support (linux only)
|
# enable HID over AOA support (linux only)
|
||||||
conf.set('HAVE_USB', usb_support)
|
conf.set('HAVE_AOA_HID', aoa_hid_support)
|
||||||
|
|
||||||
configure_file(configuration: conf, output: 'config.h')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
@@ -224,26 +164,7 @@ executable('scrcpy', src,
|
|||||||
install: true,
|
install: true,
|
||||||
c_args: [])
|
c_args: [])
|
||||||
|
|
||||||
# <https://mesonbuild.com/Builtin-options.html#directories>
|
|
||||||
datadir = get_option('datadir') # by default 'share'
|
|
||||||
|
|
||||||
install_man('scrcpy.1')
|
install_man('scrcpy.1')
|
||||||
install_data('data/icon.png',
|
|
||||||
rename: 'scrcpy.png',
|
|
||||||
install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps'))
|
|
||||||
install_data('data/zsh-completion/_scrcpy',
|
|
||||||
install_dir: join_paths(datadir, 'zsh/site-functions'))
|
|
||||||
install_data('data/bash-completion/scrcpy',
|
|
||||||
install_dir: join_paths(datadir, 'bash-completion/completions'))
|
|
||||||
|
|
||||||
# Desktop entry file for application launchers
|
|
||||||
if host_machine.system() == 'linux'
|
|
||||||
# Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop)
|
|
||||||
install_data('data/scrcpy.desktop',
|
|
||||||
install_dir: join_paths(datadir, 'applications'))
|
|
||||||
install_data('data/scrcpy-console.desktop',
|
|
||||||
install_dir: join_paths(datadir, 'applications'))
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
@@ -251,15 +172,8 @@ endif
|
|||||||
# do not build tests in release (assertions would not be executed at all)
|
# do not build tests in release (assertions would not be executed at all)
|
||||||
if get_option('buildtype') == 'debug'
|
if get_option('buildtype') == 'debug'
|
||||||
tests = [
|
tests = [
|
||||||
['test_adb_parser', [
|
['test_buffer_util', [
|
||||||
'tests/test_adb_parser.c',
|
'tests/test_buffer_util.c'
|
||||||
'src/adb/adb_device.c',
|
|
||||||
'src/adb/adb_parser.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
|
||||||
['test_binary', [
|
|
||||||
'tests/test_binary.c',
|
|
||||||
]],
|
]],
|
||||||
['test_cbuf', [
|
['test_cbuf', [
|
||||||
'tests/test_cbuf.c',
|
'tests/test_cbuf.c',
|
||||||
@@ -267,12 +181,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_cli', [
|
['test_cli', [
|
||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
'src/util/str_util.c',
|
||||||
'src/util/log.c',
|
|
||||||
'src/util/net.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/term.c',
|
|
||||||
]],
|
]],
|
||||||
['test_clock', [
|
['test_clock', [
|
||||||
'tests/test_clock.c',
|
'tests/test_clock.c',
|
||||||
@@ -281,8 +190,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_control_msg_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/util/str.c',
|
'src/util/str_util.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
]],
|
||||||
['test_device_msg_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
'tests/test_device_msg_deserialize.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
@@ -291,17 +199,9 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_queue', [
|
['test_queue', [
|
||||||
'tests/test_queue.c',
|
'tests/test_queue.c',
|
||||||
]],
|
]],
|
||||||
['test_strbuf', [
|
['test_strutil', [
|
||||||
'tests/test_strbuf.c',
|
'tests/test_strutil.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/str_util.c',
|
||||||
]],
|
|
||||||
['test_str', [
|
|
||||||
'tests/test_str.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
|
||||||
['test_vector', [
|
|
||||||
'tests/test_vector.c',
|
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
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
|
|
||||||
130
app/scrcpy.1
130
app/scrcpy.1
@@ -43,12 +43,6 @@ The values are expressed in the device natural orientation (typically, portrait
|
|||||||
.B \-\-max\-size
|
.B \-\-max\-size
|
||||||
value is computed on the cropped size.
|
value is computed on the cropped size.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-d, \-\-select\-usb
|
|
||||||
Use USB device (if there is exactly one, like adb -d).
|
|
||||||
|
|
||||||
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-disable-screensaver"
|
.BI "\-\-disable-screensaver"
|
||||||
Disable screensaver while scrcpy is running.
|
Disable screensaver while scrcpy is running.
|
||||||
@@ -68,12 +62,6 @@ Add a buffering delay (in milliseconds) before displaying. This increases latenc
|
|||||||
|
|
||||||
Default is 0 (no buffering).
|
Default is 0 (no buffering).
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-e, \-\-select\-tcpip
|
|
||||||
Use TCP/IP device (if there is exactly one, like adb -e).
|
|
||||||
|
|
||||||
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-encoder " name
|
.BI "\-\-encoder " name
|
||||||
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
||||||
@@ -95,20 +83,22 @@ Start in fullscreen.
|
|||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-K, \-\-hid\-keyboard
|
.B \-K, \-\-keyboard\-hid
|
||||||
Simulate a physical keyboard by using HID over AOAv2.
|
Simulate a physical keyboard by using HID over AOAv2.
|
||||||
|
|
||||||
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
|
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
|
||||||
|
|
||||||
It may only work over USB.
|
It may only work over USB, and is currently only supported on Linux.
|
||||||
|
|
||||||
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
.TP
|
||||||
|
.B \-i, \-\-input\-mode mode
|
||||||
|
Select input mode for keyboard events.
|
||||||
|
|
||||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
Possible values are "hid" and "inject".
|
||||||
|
|
||||||
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
"hid" uses Android's USB HID over AOAv2 feature to simulate physical keyboard's events, which provides better experience for IME users if supported by you device.
|
||||||
|
|
||||||
Also see \fB\-\-hid\-mouse\fR.
|
"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices and is the default.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-legacy\-paste
|
.B \-\-legacy\-paste
|
||||||
@@ -134,36 +124,6 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
|
|||||||
|
|
||||||
Default is 0 (unlimited).
|
Default is 0 (unlimited).
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-M, \-\-hid\-mouse
|
|
||||||
Simulate a physical mouse by using HID over AOAv2.
|
|
||||||
|
|
||||||
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
|
|
||||||
|
|
||||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
|
||||||
|
|
||||||
It may only work over USB.
|
|
||||||
|
|
||||||
Also see \fB\-\-hid\-keyboard\fR.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-cleanup
|
|
||||||
By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit.
|
|
||||||
|
|
||||||
This option disables this cleanup.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-clipboard\-autosync
|
|
||||||
By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes.
|
|
||||||
|
|
||||||
This option disables this automatic synchronization.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-downsize\-on\-error
|
|
||||||
By default, on MediaCodec error, scrcpy automatically tries again with a lower definition.
|
|
||||||
|
|
||||||
This option disables this behavior.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-n, \-\-no\-control
|
.B \-n, \-\-no\-control
|
||||||
Disable device control (mirror the device in read\-only).
|
Disable device control (mirror the device in read\-only).
|
||||||
@@ -180,34 +140,12 @@ Do not forward repeated key events when a key is held down.
|
|||||||
.B \-\-no\-mipmaps
|
.B \-\-no\-mipmaps
|
||||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-power\-on
|
|
||||||
Do not power on the device on start.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-otg
|
|
||||||
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
|
|
||||||
|
|
||||||
In this mode, adb (USB debugging) is not necessary, and mirroring is disabled.
|
|
||||||
|
|
||||||
LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mouse back to the computer.
|
|
||||||
|
|
||||||
If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both.
|
|
||||||
|
|
||||||
It may only work over USB.
|
|
||||||
|
|
||||||
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port[:port]
|
.BI "\-p, \-\-port " port[:port]
|
||||||
Set the TCP port (range) used by the client to listen.
|
Set the TCP port (range) used by the client to listen.
|
||||||
|
|
||||||
Default is 27183:27199.
|
Default is 27183:27199.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-power\-off\-on\-close
|
|
||||||
Turn the device screen off when closing scrcpy.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-prefer\-text
|
.B \-\-prefer\-text
|
||||||
Inject alpha characters and space as text events instead of key events.
|
Inject alpha characters and space as text events instead of key events.
|
||||||
@@ -215,20 +153,12 @@ Inject alpha characters and space as text events instead of key events.
|
|||||||
This avoids issues when combining multiple keys to enter special characters,
|
This avoids issues when combining multiple keys to enter special characters,
|
||||||
but breaks the expected behavior of alpha keys in games (typically WASD).
|
but breaks the expected behavior of alpha keys in games (typically WASD).
|
||||||
|
|
||||||
.TP
|
|
||||||
.B "\-\-print\-fps
|
|
||||||
Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-push\-target " path
|
.BI "\-\-push\-target " path
|
||||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||||
|
|
||||||
Default is "/sdcard/Download/".
|
Default is "/sdcard/Download/".
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-raw\-key\-events
|
|
||||||
Inject key events for all input keys, and ignore text events.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-r, \-\-record " file
|
.BI "\-r, \-\-record " file
|
||||||
Record screen to
|
Record screen to
|
||||||
@@ -269,14 +199,6 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
|
|||||||
|
|
||||||
Default is "lalt,lsuper" (left-Alt or left-Super).
|
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-tcpip[=ip[:port]]
|
|
||||||
Configure and reconnect the device over TCP/IP.
|
|
||||||
|
|
||||||
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
|
||||||
|
|
||||||
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-S, \-\-turn\-screen\-off
|
.B \-S, \-\-turn\-screen\-off
|
||||||
Turn the device screen off immediately.
|
Turn the device screen off immediately.
|
||||||
@@ -287,18 +209,6 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
|
|
||||||
It only shows physical touches (not clicks from scrcpy).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-tunnel\-host " ip
|
|
||||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
|
||||||
|
|
||||||
Default is localhost.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-tunnel\-port " port
|
|
||||||
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
|
||||||
|
|
||||||
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-v4l2-sink " /dev/videoN
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
Output to v4l2loopback device.
|
Output to v4l2loopback device.
|
||||||
@@ -359,12 +269,6 @@ Set the initial window height.
|
|||||||
|
|
||||||
Default is 0 (automatic).
|
Default is 0 (automatic).
|
||||||
|
|
||||||
.SH EXIT STATUS
|
|
||||||
.B scrcpy
|
|
||||||
will exit with code 0 on normal program termination. If an initial
|
|
||||||
connection cannot be established, the exit code 1 will be returned. If the
|
|
||||||
device disconnects while a session is active, exit code 2 will be returned.
|
|
||||||
|
|
||||||
.SH SHORTCUTS
|
.SH SHORTCUTS
|
||||||
|
|
||||||
In the following list, MOD is the shortcut modifier. By default, it's (left)
|
In the following list, MOD is the shortcut modifier. By default, it's (left)
|
||||||
@@ -470,28 +374,16 @@ Pinch-to-zoom from the center of the screen
|
|||||||
.B Drag & drop APK file
|
.B Drag & drop APK file
|
||||||
Install APK from computer
|
Install APK from computer
|
||||||
|
|
||||||
.TP
|
|
||||||
.B Drag & drop non-APK file
|
|
||||||
Push file to device (see \fB\-\-push\-target\fR)
|
|
||||||
|
|
||||||
|
|
||||||
.SH Environment variables
|
.SH Environment variables
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B ADB
|
.B ADB
|
||||||
Path to adb.
|
Specify the path to adb.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B ANDROID_SERIAL
|
|
||||||
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B SCRCPY_ICON_PATH
|
|
||||||
Path to the program icon.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B SCRCPY_SERVER_PATH
|
.B SCRCPY_SERVER_PATH
|
||||||
Path to the server binary.
|
Specify the path to server binary.
|
||||||
|
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
@@ -516,7 +408,7 @@ Copyright \(co 2018 Genymobile
|
|||||||
Genymobile
|
Genymobile
|
||||||
.UE
|
.UE
|
||||||
|
|
||||||
Copyright \(co 2018\-2022
|
Copyright \(co 2018\-2020
|
||||||
.MT rom@rom1v.com
|
.MT rom@rom1v.com
|
||||||
Romain Vimont
|
Romain Vimont
|
||||||
.ME
|
.ME
|
||||||
|
|||||||
279
app/src/adb.c
Normal file
279
app/src/adb.c
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
#include "adb.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.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 (search_executable(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 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 PROCESS_ERROR_GENERIC:
|
||||||
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
|
LOGE("Failed to execute: %s", buf);
|
||||||
|
break;
|
||||||
|
case PROCESS_ERROR_MISSING_BINARY:
|
||||||
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
|
LOGE("Command not found: %s", buf);
|
||||||
|
LOGE("(make 'adb' accessible from your PATH or define its full"
|
||||||
|
"path in the ADB environment variable)");
|
||||||
|
show_adb_installation_msg();
|
||||||
|
break;
|
||||||
|
case PROCESS_SUCCESS:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
||||||
|
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
||||||
|
pipe_t *pipe_stderr) {
|
||||||
|
int i;
|
||||||
|
process_t process;
|
||||||
|
|
||||||
|
const char **argv = malloc((len + 4) * sizeof(*argv));
|
||||||
|
if (!argv) {
|
||||||
|
return 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 process_result r =
|
||||||
|
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
|
||||||
|
pipe_stderr);
|
||||||
|
if (r != PROCESS_SUCCESS) {
|
||||||
|
show_adb_err_msg(r, argv);
|
||||||
|
process = PROCESS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(argv);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
|
||||||
|
return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_forward(const char *serial, uint16_t local_port,
|
||||||
|
const char *device_socket_name) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_push(const char *serial, const char *local, const char *remote) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
// Windows will parse the string, so the paths must be quoted
|
||||||
|
// (see sys/win/command.c)
|
||||||
|
local = strquote(local);
|
||||||
|
if (!local) {
|
||||||
|
return PROCESS_NONE;
|
||||||
|
}
|
||||||
|
remote = strquote(remote);
|
||||||
|
if (!remote) {
|
||||||
|
free((void *) local);
|
||||||
|
return PROCESS_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"push", local, remote};
|
||||||
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
free((void *) remote);
|
||||||
|
free((void *) local);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_install(const char *serial, const char *local) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
// Windows will parse the string, so the local name must be quoted
|
||||||
|
// (see sys/win/command.c)
|
||||||
|
local = strquote(local);
|
||||||
|
if (!local) {
|
||||||
|
return PROCESS_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"install", "-r", local};
|
||||||
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
free((void *) local);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
adb_execute_for_output(const char *serial, const char *const adb_cmd[],
|
||||||
|
size_t adb_cmd_len, char *buf, size_t buf_len,
|
||||||
|
const char *name) {
|
||||||
|
pipe_t pipe_stdout;
|
||||||
|
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
|
||||||
|
&pipe_stdout, NULL);
|
||||||
|
|
||||||
|
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
|
||||||
|
close_pipe(pipe_stdout);
|
||||||
|
|
||||||
|
if (!process_check_success(proc, name, true)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
truncate_first_line(char *data, size_t len) {
|
||||||
|
data[len - 1] = '\0';
|
||||||
|
char *eol = strpbrk(data, "\r\n");
|
||||||
|
if (eol) {
|
||||||
|
*eol = '\0';
|
||||||
|
len = eol - data;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
adb_get_serialno(void) {
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"get-serialno"};
|
||||||
|
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
|
||||||
|
buf, sizeof(buf), "get-serialno");
|
||||||
|
if (r <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_first_line(buf, r);
|
||||||
|
return strdup(buf);
|
||||||
|
}
|
||||||
43
app/src/adb.h
Normal file
43
app/src/adb.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef SC_ADB_H
|
||||||
|
#define SC_ADB_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "util/process.h"
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
||||||
|
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
||||||
|
pipe_t *pipe_stderr);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_forward(const char *serial, uint16_t local_port,
|
||||||
|
const char *device_socket_name);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_forward_remove(const char *serial, uint16_t local_port);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_reverse(const char *serial, const char *device_socket_name,
|
||||||
|
uint16_t local_port);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_install(const char *serial, const char *local);
|
||||||
|
|
||||||
|
// Return the result of "adb get-serialno".
|
||||||
|
char *
|
||||||
|
adb_get_serialno(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,715 +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) {
|
|
||||||
LOG_OOM();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,117 +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)
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
#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,227 +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;
|
|
||||||
}
|
|
||||||
@@ -1,30 +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);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#include "adb_tunnel.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "adb.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/net_intr.h"
|
|
||||||
#include "util/process_intr.h"
|
|
||||||
|
|
||||||
#define SC_SOCKET_NAME "scrcpy"
|
|
||||||
|
|
||||||
static bool
|
|
||||||
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
|
||||||
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
|
|
||||||
struct sc_intr *intr, const char *serial,
|
|
||||||
struct sc_port_range port_range) {
|
|
||||||
uint16_t port = port_range.first;
|
|
||||||
for (;;) {
|
|
||||||
if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
|
|
||||||
SC_ADB_NO_STDOUT)) {
|
|
||||||
// the command itself failed, it will fail on any port
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At the application level, the device part is "the server" because it
|
|
||||||
// serves video stream and control. However, at the network level, the
|
|
||||||
// client listens and the server connects to the client. That way, the
|
|
||||||
// client can listen before starting the server app, so there is no
|
|
||||||
// need to try to connect until the server socket is listening on the
|
|
||||||
// device.
|
|
||||||
sc_socket server_socket = net_socket();
|
|
||||||
if (server_socket != SC_SOCKET_NONE) {
|
|
||||||
bool ok = listen_on_port(intr, server_socket, port);
|
|
||||||
if (ok) {
|
|
||||||
// success
|
|
||||||
tunnel->server_socket = server_socket;
|
|
||||||
tunnel->local_port = port;
|
|
||||||
tunnel->enabled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
net_close(server_socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc_intr_is_interrupted(intr)) {
|
|
||||||
// Stop immediately
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
|
||||||
if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
|
||||||
SC_ADB_NO_STDOUT)) {
|
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check before incrementing to avoid overflow on port 65535
|
|
||||||
if (port < port_range.last) {
|
|
||||||
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
|
|
||||||
port, (uint16_t) (port + 1));
|
|
||||||
port++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port_range.first == port_range.last) {
|
|
||||||
LOGE("Could not listen on port %" PRIu16, port_range.first);
|
|
||||||
} else {
|
|
||||||
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
|
|
||||||
port_range.first, port_range.last);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
|
|
||||||
struct sc_intr *intr, const char *serial,
|
|
||||||
struct sc_port_range port_range) {
|
|
||||||
tunnel->forward = true;
|
|
||||||
|
|
||||||
uint16_t port = port_range.first;
|
|
||||||
for (;;) {
|
|
||||||
if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
|
|
||||||
SC_ADB_NO_STDOUT)) {
|
|
||||||
// success
|
|
||||||
tunnel->local_port = port;
|
|
||||||
tunnel->enabled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc_intr_is_interrupted(intr)) {
|
|
||||||
// Stop immediately
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port < port_range.last) {
|
|
||||||
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
|
|
||||||
port, (uint16_t) (port + 1));
|
|
||||||
port++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port_range.first == port_range.last) {
|
|
||||||
LOGE("Could not forward port %" PRIu16, port_range.first);
|
|
||||||
} else {
|
|
||||||
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
|
|
||||||
port_range.first, port_range.last);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
|
|
||||||
tunnel->enabled = false;
|
|
||||||
tunnel->forward = false;
|
|
||||||
tunnel->server_socket = SC_SOCKET_NONE;
|
|
||||||
tunnel->local_port = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|
||||||
const char *serial, struct sc_port_range port_range,
|
|
||||||
bool force_adb_forward) {
|
|
||||||
assert(!tunnel->enabled);
|
|
||||||
|
|
||||||
if (!force_adb_forward) {
|
|
||||||
// Attempt to use "adb reverse"
|
|
||||||
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if "adb reverse" does not work (e.g. over "adb connect"), it
|
|
||||||
// fallbacks to "adb forward", so the app socket is the client
|
|
||||||
|
|
||||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
|
||||||
}
|
|
||||||
|
|
||||||
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|
||||||
const char *serial) {
|
|
||||||
assert(tunnel->enabled);
|
|
||||||
|
|
||||||
bool ret;
|
|
||||||
if (tunnel->forward) {
|
|
||||||
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
|
|
||||||
SC_ADB_NO_STDOUT);
|
|
||||||
} else {
|
|
||||||
ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
|
|
||||||
SC_ADB_NO_STDOUT);
|
|
||||||
|
|
||||||
assert(tunnel->server_socket != SC_SOCKET_NONE);
|
|
||||||
if (!net_close(tunnel->server_socket)) {
|
|
||||||
LOGW("Could not close server socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
// server_socket is never used anymore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consider tunnel disabled even if the command failed
|
|
||||||
tunnel->enabled = false;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#ifndef SC_ADB_TUNNEL_H
|
|
||||||
#define SC_ADB_TUNNEL_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "options.h"
|
|
||||||
#include "util/intr.h"
|
|
||||||
#include "util/net.h"
|
|
||||||
|
|
||||||
struct sc_adb_tunnel {
|
|
||||||
bool enabled;
|
|
||||||
bool forward; // use "adb forward" instead of "adb reverse"
|
|
||||||
sc_socket server_socket; // only used if !forward
|
|
||||||
uint16_t local_port;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the adb tunnel struct to default values
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a tunnel
|
|
||||||
*
|
|
||||||
* Blocking calls may be interrupted asynchronously via `intr`.
|
|
||||||
*
|
|
||||||
* If `force_adb_forward` is not set, then attempts to set up an "adb reverse"
|
|
||||||
* tunnel first. Only if it fails (typical on old Android version connected via
|
|
||||||
* TCP/IP), use "adb forward".
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|
||||||
const char *serial, struct sc_port_range port_range,
|
|
||||||
bool force_adb_forward);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the tunnel
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
|
|
||||||
const char *serial);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
363
app/src/aoa_hid.c
Normal file
363
app/src/aoa_hid.c
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "aoa_hid.h"
|
||||||
|
|
||||||
|
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
||||||
|
#define ACCESSORY_REGISTER_HID 54
|
||||||
|
#define ACCESSORY_SET_HID_REPORT_DESC 56
|
||||||
|
#define ACCESSORY_SEND_HID_EVENT 57
|
||||||
|
#define ACCESSORY_UNREGISTER_HID 55
|
||||||
|
|
||||||
|
#define DEFAULT_TIMEOUT 1000
|
||||||
|
|
||||||
|
static void
|
||||||
|
hid_event_log(const struct hid_event *event) {
|
||||||
|
// HID Event: [00] FF FF FF FF...
|
||||||
|
assert(event->size);
|
||||||
|
unsigned buffer_size = event->size * 3 + 1;
|
||||||
|
char *buffer = malloc(buffer_size);
|
||||||
|
if (!buffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i < event->size; ++i) {
|
||||||
|
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
|
||||||
|
}
|
||||||
|
LOGV("HID Event: [%d]%s", event->from_accessory_id, buffer);
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hid_event_destroy(struct hid_event *event) {
|
||||||
|
free(event->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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 *
|
||||||
|
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 (ssize_t i = 0; i < 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
|
||||||
|
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
|
||||||
|
aoa_init(struct aoa *aoa, const char *serial) {
|
||||||
|
cbuf_init(&aoa->queue);
|
||||||
|
|
||||||
|
if (!sc_mutex_init(&aoa->mutex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_cond_init(&aoa->event_cond)) {
|
||||||
|
sc_mutex_destroy(&aoa->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
|
||||||
|
sc_cond_destroy(&aoa->event_cond);
|
||||||
|
sc_mutex_destroy(&aoa->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aoa->usb_device = 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 (aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
||||||
|
LOGW("Open USB handle failed");
|
||||||
|
libusb_unref_device(aoa->usb_device);
|
||||||
|
libusb_exit(aoa->usb_context);
|
||||||
|
sc_cond_destroy(&aoa->event_cond);
|
||||||
|
sc_mutex_destroy(&aoa->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aoa->stopped = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_destroy(struct aoa *aoa) {
|
||||||
|
// Destroy remaining events
|
||||||
|
struct hid_event event;
|
||||||
|
while (cbuf_take(&aoa->queue, &event)) {
|
||||||
|
hid_event_destroy(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_close(aoa->usb_handle);
|
||||||
|
libusb_unref_device(aoa->usb_device);
|
||||||
|
libusb_exit(aoa->usb_context);
|
||||||
|
sc_cond_destroy(&aoa->event_cond);
|
||||||
|
sc_mutex_destroy(&aoa->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
aoa_register_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
|
uint16_t report_desc_size) {
|
||||||
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
|
uint8_t request = ACCESSORY_REGISTER_HID;
|
||||||
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
|
// index (arg1): total length of the HID report descriptor
|
||||||
|
uint16_t value = accessory_id;
|
||||||
|
uint16_t index = report_desc_size;
|
||||||
|
unsigned char *buffer = NULL;
|
||||||
|
uint16_t length = 0;
|
||||||
|
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||||
|
value, index, buffer, length,
|
||||||
|
DEFAULT_TIMEOUT);
|
||||||
|
if (result < 0) {
|
||||||
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
aoa_set_hid_report_desc(struct aoa *aoa, uint16_t accessory_id,
|
||||||
|
const unsigned char *report_desc,
|
||||||
|
uint16_t report_desc_size) {
|
||||||
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
|
uint8_t request = ACCESSORY_SET_HID_REPORT_DESC;
|
||||||
|
/**
|
||||||
|
* If the HID descriptor is longer than the endpoint zero max packet size,
|
||||||
|
* the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
|
||||||
|
* commands. The data for the descriptor must be sent sequentially
|
||||||
|
* if multiple packets are needed.
|
||||||
|
* <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
|
*
|
||||||
|
* libusb handles packet abstraction internally, so we don't need to care
|
||||||
|
* about bMaxPacketSize0 here.
|
||||||
|
*
|
||||||
|
* See <https://libusb.sourceforge.io/api-1.0/libusb_packetoverflow.html>
|
||||||
|
*/
|
||||||
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
|
// index (arg1): offset of data (buffer) in descriptor
|
||||||
|
uint16_t value = accessory_id;
|
||||||
|
uint16_t index = 0;
|
||||||
|
// libusb_control_transfer expects a pointer to non-const
|
||||||
|
unsigned char *buffer = (unsigned char *) report_desc;
|
||||||
|
uint16_t length = report_desc_size;
|
||||||
|
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||||
|
value, index, buffer, length,
|
||||||
|
DEFAULT_TIMEOUT);
|
||||||
|
if (result < 0) {
|
||||||
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
|
const unsigned char *report_desc, uint16_t report_desc_size) {
|
||||||
|
bool ok = aoa_register_hid(aoa, accessory_id, report_desc_size);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
||||||
|
report_desc_size);
|
||||||
|
if (!ok) {
|
||||||
|
if (!aoa_unregister_hid(aoa, accessory_id)) {
|
||||||
|
LOGW("Could not unregister HID");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
aoa_send_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||||
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
|
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
||||||
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
|
// index (arg1): 0 (unused)
|
||||||
|
uint16_t value = event->from_accessory_id;
|
||||||
|
uint16_t index = 0;
|
||||||
|
unsigned char *buffer = event->buffer;
|
||||||
|
uint16_t length = event->size;
|
||||||
|
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||||
|
value, index, buffer, length,
|
||||||
|
DEFAULT_TIMEOUT);
|
||||||
|
if (result < 0) {
|
||||||
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_unregister_hid(struct aoa *aoa, const uint16_t accessory_id) {
|
||||||
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
|
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
||||||
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
|
// index (arg1): 0
|
||||||
|
uint16_t value = accessory_id;
|
||||||
|
uint16_t index = 0;
|
||||||
|
unsigned char *buffer = NULL;
|
||||||
|
uint16_t length = 0;
|
||||||
|
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
|
||||||
|
value, index, buffer, length,
|
||||||
|
DEFAULT_TIMEOUT);
|
||||||
|
if (result < 0) {
|
||||||
|
log_libusb_error((enum libusb_error) result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||||
|
hid_event_log(event);
|
||||||
|
sc_mutex_lock(&aoa->mutex);
|
||||||
|
bool was_empty = cbuf_is_empty(&aoa->queue);
|
||||||
|
bool res = cbuf_push(&aoa->queue, *event);
|
||||||
|
if (was_empty) {
|
||||||
|
sc_cond_signal(&aoa->event_cond);
|
||||||
|
}
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
process_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||||
|
return aoa_send_hid_event(aoa, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_aoa_thread(void *data) {
|
||||||
|
struct aoa *aoa = data;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sc_mutex_lock(&aoa->mutex);
|
||||||
|
while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) {
|
||||||
|
sc_cond_wait(&aoa->event_cond, &aoa->mutex);
|
||||||
|
}
|
||||||
|
if (aoa->stopped) {
|
||||||
|
// Stop immediately, do not process further events
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
struct hid_event event;
|
||||||
|
bool non_empty = cbuf_take(&aoa->queue, &event);
|
||||||
|
assert(non_empty);
|
||||||
|
(void) non_empty;
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
|
LOGD("======= aoa_thread process event");
|
||||||
|
bool ok = process_hid_event(aoa, &event);
|
||||||
|
hid_event_destroy(&event);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not send HID event to USB device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_start(struct aoa *aoa) {
|
||||||
|
LOGD("Starting AOA thread");
|
||||||
|
|
||||||
|
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not start AOA thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_stop(struct aoa *aoa) {
|
||||||
|
sc_mutex_lock(&aoa->mutex);
|
||||||
|
aoa->stopped = true;
|
||||||
|
sc_cond_signal(&aoa->event_cond);
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_join(struct aoa *aoa) {
|
||||||
|
sc_thread_join(&aoa->thread, NULL);
|
||||||
|
}
|
||||||
57
app/src/aoa_hid.h
Normal file
57
app/src/aoa_hid.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef AOA_HID_H
|
||||||
|
#define AOA_HID_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <libusb-1.0/libusb.h>
|
||||||
|
|
||||||
|
#include "scrcpy.h"
|
||||||
|
#include "util/cbuf.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
|
struct hid_event {
|
||||||
|
uint16_t from_accessory_id;
|
||||||
|
unsigned char *buffer;
|
||||||
|
uint16_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hid_event_queue CBUF(struct hid_event, 64);
|
||||||
|
|
||||||
|
struct aoa {
|
||||||
|
libusb_context *usb_context;
|
||||||
|
libusb_device *usb_device;
|
||||||
|
libusb_device_handle *usb_handle;
|
||||||
|
sc_thread thread;
|
||||||
|
sc_mutex mutex;
|
||||||
|
sc_cond event_cond;
|
||||||
|
bool stopped;
|
||||||
|
struct hid_event_queue queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_init(struct aoa *aoa, const char *serial);
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_destroy(struct aoa *aoa);
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_start(struct aoa *aoa);
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_stop(struct aoa *aoa);
|
||||||
|
|
||||||
|
void
|
||||||
|
aoa_join(struct aoa *aoa);
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
|
const unsigned char *report_desc, uint16_t report_desc_size);
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_unregister_hid(struct aoa *aoa, uint16_t accessory_id);
|
||||||
|
|
||||||
|
bool
|
||||||
|
aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event);
|
||||||
|
|
||||||
|
#endif
|
||||||
1646
app/src/cli.c
1646
app/src/cli.c
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
struct scrcpy_cli_args {
|
struct scrcpy_cli_args {
|
||||||
struct scrcpy_options opts;
|
struct scrcpy_options opts;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
|||||||
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
||||||
|
|
||||||
#ifndef SC_CLOCK_NDEBUG
|
#ifndef SC_CLOCK_NDEBUG
|
||||||
LOGD("Clock estimation: %f * pts + %" PRItick,
|
LOGD("Clock estimation: %g * pts + %" PRItick,
|
||||||
clock->slope, clock->offset);
|
clock->slope, clock->offset);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct sc_clock_point {
|
|||||||
* array.
|
* array.
|
||||||
*
|
*
|
||||||
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
||||||
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
|
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
|
||||||
* point"). The slope of the estimated affine function is that of the line
|
* point"). The slope of the estimated affine function is that of the line
|
||||||
* passing through these two points.
|
* passing through these two points.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_COMMON_H
|
#ifndef COMMON_H
|
||||||
#define SC_COMMON_H
|
#define COMMON_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||||
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
|
|
||||||
|
|
||||||
#define container_of(ptr, type, member) \
|
#define container_of(ptr, type, member) \
|
||||||
((type *) (((char *) (ptr)) - offsetof(type, member)))
|
((type *) (((char *) (ptr)) - offsetof(type, member)))
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef HAVE_STRDUP
|
#ifndef HAVE_STRDUP
|
||||||
char *strdup(const char *s) {
|
char *strdup(const char *s) {
|
||||||
size_t size = strlen(s) + 1;
|
size_t size = strlen(s) + 1;
|
||||||
@@ -18,36 +12,3 @@ char *strdup(const char *s) {
|
|||||||
return dup;
|
return dup;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_ASPRINTF
|
|
||||||
int asprintf(char **strp, const char *fmt, ...) {
|
|
||||||
va_list va;
|
|
||||||
va_start(va, fmt);
|
|
||||||
int ret = vasprintf(strp, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_VASPRINTF
|
|
||||||
int vasprintf(char **strp, const char *fmt, va_list ap) {
|
|
||||||
va_list va;
|
|
||||||
va_copy(va, ap);
|
|
||||||
int len = vsnprintf(NULL, 0, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
|
|
||||||
char *str = malloc(len + 1);
|
|
||||||
if (!str) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
va_copy(va, ap);
|
|
||||||
int len2 = vsnprintf(str, len + 1, fmt, va);
|
|
||||||
(void) len2;
|
|
||||||
assert(len == len2);
|
|
||||||
va_end(va);
|
|
||||||
|
|
||||||
*strp = str;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
#ifndef SC_COMPAT_H
|
#ifndef COMPAT_H
|
||||||
#define SC_COMPAT_H
|
#define COMPAT_H
|
||||||
|
|
||||||
#include "config.h"
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#define _XOPEN_SOURCE 700
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# define _DARWIN_C_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <libavformat/version.h>
|
#include <libavformat/version.h>
|
||||||
#include <SDL2/SDL_version.h>
|
#include <SDL2/SDL_version.h>
|
||||||
|
|
||||||
#ifndef __WIN32
|
|
||||||
# define PRIu64_ PRIu64
|
|
||||||
# define SC_PRIsizet "zu"
|
|
||||||
#else
|
|
||||||
# define PRIu64_ "I64u" // Windows...
|
|
||||||
# define SC_PRIsizet "Iu"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
// In ffmpeg/doc/APIchanges:
|
||||||
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
||||||
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
||||||
@@ -37,9 +34,13 @@
|
|||||||
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
|
||||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
# 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
|
#endif
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
||||||
@@ -51,12 +52,4 @@
|
|||||||
char *strdup(const char *s);
|
char *strdup(const char *s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_ASPRINTF
|
|
||||||
int asprintf(char **strp, const char *fmt, ...);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_VASPRINTF
|
|
||||||
int vasprintf(char **strp, const char *fmt, va_list ap);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/binary.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map an enum value to a string based on an array, without crashing on an
|
* Map an enum value to a string based on an array, without crashing on an
|
||||||
@@ -37,11 +37,11 @@ static const char *const android_motionevent_action_labels[] = {
|
|||||||
"move",
|
"move",
|
||||||
"cancel",
|
"cancel",
|
||||||
"outside",
|
"outside",
|
||||||
"pointer-down",
|
"ponter-down",
|
||||||
"pointer-up",
|
"pointer-up",
|
||||||
"hover-move",
|
"hover-move",
|
||||||
"scroll",
|
"scroll",
|
||||||
"hover-enter",
|
"hover-enter"
|
||||||
"hover-exit",
|
"hover-exit",
|
||||||
"btn-press",
|
"btn-press",
|
||||||
"btn-release",
|
"btn-release",
|
||||||
@@ -55,84 +55,83 @@ static const char *const screen_power_mode_labels[] = {
|
|||||||
"suspend",
|
"suspend",
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *const copy_key_labels[] = {
|
|
||||||
"none",
|
|
||||||
"copy",
|
|
||||||
"cut",
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
write_position(uint8_t *buf, const struct position *position) {
|
||||||
sc_write32be(&buf[0], position->point.x);
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
sc_write32be(&buf[4], position->point.y);
|
buffer_write32be(&buf[4], position->point.y);
|
||||||
sc_write16be(&buf[8], position->screen_size.width);
|
buffer_write16be(&buf[8], position->screen_size.width);
|
||||||
sc_write16be(&buf[10], position->screen_size.height);
|
buffer_write16be(&buf[10], position->screen_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write length (4 bytes) + string (non null-terminated)
|
// write length (2 bytes) + string (non nul-terminated)
|
||||||
static size_t
|
static size_t
|
||||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
size_t len = utf8_truncation_index(utf8, max_len);
|
||||||
sc_write32be(buf, len);
|
buffer_write32be(buf, len);
|
||||||
memcpy(&buf[4], utf8, len);
|
memcpy(&buf[4], utf8, len);
|
||||||
return 4 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t
|
||||||
|
to_fixed_point_16(float f) {
|
||||||
|
assert(f >= 0.0f && f <= 1.0f);
|
||||||
|
uint32_t u = f * 0x1p16f; // 2^16
|
||||||
|
if (u >= 0xffff) {
|
||||||
|
u = 0xffff;
|
||||||
|
}
|
||||||
|
return (uint16_t) u;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
sc_write32be(&buf[2], msg->inject_keycode.keycode);
|
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||||
sc_write32be(&buf[6], msg->inject_keycode.repeat);
|
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||||
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||||
return 14;
|
return 14;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
size_t len =
|
size_t len =
|
||||||
write_string(msg->inject_text.text,
|
write_string(msg->inject_text.text,
|
||||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||||
buf[1] = msg->inject_touch_event.action;
|
buf[1] = msg->inject_touch_event.action;
|
||||||
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
||||||
write_position(&buf[10], &msg->inject_touch_event.position);
|
write_position(&buf[10], &msg->inject_touch_event.position);
|
||||||
uint16_t pressure =
|
uint16_t pressure =
|
||||||
sc_float_to_u16fp(msg->inject_touch_event.pressure);
|
to_fixed_point_16(msg->inject_touch_event.pressure);
|
||||||
sc_write16be(&buf[22], pressure);
|
buffer_write16be(&buf[22], pressure);
|
||||||
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
|
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
|
||||||
return 28;
|
return 28;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||||
int16_t hscroll =
|
buffer_write32be(&buf[13],
|
||||||
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
|
(uint32_t) msg->inject_scroll_event.hscroll);
|
||||||
int16_t vscroll =
|
buffer_write32be(&buf[17],
|
||||||
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
sc_write16be(&buf[13], (uint16_t) hscroll);
|
|
||||||
sc_write16be(&buf[15], (uint16_t) vscroll);
|
|
||||||
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
|
|
||||||
return 21;
|
return 21;
|
||||||
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buf[1] = msg->get_clipboard.copy_key;
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
return 2;
|
|
||||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
|
||||||
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
|
||||||
buf[9] = !!msg->set_clipboard.paste;
|
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
&buf[10]);
|
&buf[2]);
|
||||||
return 10 + len;
|
return 2 + len;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
}
|
||||||
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
// no additional data
|
// no additional data
|
||||||
return 1;
|
return 1;
|
||||||
default:
|
default:
|
||||||
@@ -142,27 +141,27 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_log(const struct sc_control_msg *msg) {
|
control_msg_log(const struct control_msg *msg) {
|
||||||
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
|
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
|
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
|
||||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
|
||||||
(int) msg->inject_keycode.keycode,
|
(int) msg->inject_keycode.keycode,
|
||||||
msg->inject_keycode.repeat,
|
msg->inject_keycode.repeat,
|
||||||
(long) msg->inject_keycode.metastate);
|
(long) msg->inject_keycode.metastate);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
LOG_CMSG("text \"%s\"", msg->inject_text.text);
|
LOG_CMSG("text \"%s\"", msg->inject_text.text);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
|
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
|
||||||
int action = msg->inject_touch_event.action
|
int action = msg->inject_touch_event.action
|
||||||
& AMOTION_EVENT_ACTION_MASK;
|
& AMOTION_EVENT_ACTION_MASK;
|
||||||
uint64_t id = msg->inject_touch_event.pointer_id;
|
uint64_t id = msg->inject_touch_event.pointer_id;
|
||||||
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
|
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
|
||||||
// string pointer id
|
// string pointer id
|
||||||
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
||||||
" pressure=%f buttons=%06lx",
|
" pressure=%g buttons=%06lx",
|
||||||
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
|
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
msg->inject_touch_event.position.point.x,
|
msg->inject_touch_event.position.point.x,
|
||||||
@@ -171,8 +170,13 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
(long) msg->inject_touch_event.buttons);
|
(long) msg->inject_touch_event.buttons);
|
||||||
} else {
|
} else {
|
||||||
// numeric pointer id
|
// numeric pointer id
|
||||||
|
#ifndef __WIN32
|
||||||
|
# define PRIu64_ PRIu64
|
||||||
|
#else
|
||||||
|
# define PRIu64_ "I64u" // Windows...
|
||||||
|
#endif
|
||||||
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
||||||
PRIi32 " pressure=%f buttons=%06lx",
|
PRIi32 " pressure=%g buttons=%06lx",
|
||||||
id,
|
id,
|
||||||
MOTIONEVENT_ACTION_LABEL(action),
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
msg->inject_touch_event.position.point.x,
|
msg->inject_touch_event.position.point.x,
|
||||||
@@ -182,43 +186,40 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
|
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
||||||
" vscroll=%f buttons=%06lx",
|
" vscroll=%" PRIi32,
|
||||||
msg->inject_scroll_event.position.point.x,
|
msg->inject_scroll_event.position.point.x,
|
||||||
msg->inject_scroll_event.position.point.y,
|
msg->inject_scroll_event.position.point.y,
|
||||||
msg->inject_scroll_event.hscroll,
|
msg->inject_scroll_event.hscroll,
|
||||||
msg->inject_scroll_event.vscroll,
|
msg->inject_scroll_event.vscroll);
|
||||||
(long) msg->inject_scroll_event.buttons);
|
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
LOG_CMSG("back-or-screen-on %s",
|
LOG_CMSG("back-or-screen-on %s",
|
||||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
LOG_CMSG("get clipboard copy_key=%s",
|
LOG_CMSG("clipboard %s \"%s\"",
|
||||||
copy_key_labels[msg->get_clipboard.copy_key]);
|
msg->set_clipboard.paste ? "paste" : "copy",
|
||||||
break;
|
|
||||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
|
||||||
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
|
||||||
msg->set_clipboard.sequence,
|
|
||||||
msg->set_clipboard.paste ? "paste" : "nopaste",
|
|
||||||
msg->set_clipboard.text);
|
msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
LOG_CMSG("power mode %s",
|
LOG_CMSG("power mode %s",
|
||||||
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
LOG_CMSG("expand notification panel");
|
LOG_CMSG("expand notification panel");
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
LOG_CMSG("expand settings panel");
|
LOG_CMSG("expand settings panel");
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
LOG_CMSG("collapse panels");
|
LOG_CMSG("collapse panels");
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
LOG_CMSG("get clipboard");
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
LOG_CMSG("rotate device");
|
LOG_CMSG("rotate device");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -228,12 +229,12 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
control_msg_destroy(struct control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
free(msg->inject_text.text);
|
free(msg->inject_text.text);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
free(msg->set_clipboard.text);
|
free(msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_CONTROLMSG_H
|
#ifndef CONTROLMSG_H
|
||||||
#define SC_CONTROLMSG_H
|
#define CONTROLMSG_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -11,44 +11,38 @@
|
|||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
|
|
||||||
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
|
|
||||||
#define SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||||
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
||||||
|
|
||||||
enum sc_control_msg_type {
|
enum control_msg_type {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_screen_power_mode {
|
enum screen_power_mode {
|
||||||
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||||
SC_SCREEN_POWER_MODE_OFF = 0,
|
SCREEN_POWER_MODE_OFF = 0,
|
||||||
SC_SCREEN_POWER_MODE_NORMAL = 2,
|
SCREEN_POWER_MODE_NORMAL = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_copy_key {
|
struct control_msg {
|
||||||
SC_COPY_KEY_NONE,
|
enum control_msg_type type;
|
||||||
SC_COPY_KEY_COPY,
|
|
||||||
SC_COPY_KEY_CUT,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_control_msg {
|
|
||||||
enum sc_control_msg_type type;
|
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
enum android_keyevent_action action;
|
enum android_keyevent_action action;
|
||||||
@@ -63,29 +57,24 @@ struct sc_control_msg {
|
|||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
uint64_t pointer_id;
|
uint64_t pointer_id;
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
float pressure;
|
float pressure;
|
||||||
} inject_touch_event;
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
float hscroll;
|
int32_t hscroll;
|
||||||
float vscroll;
|
int32_t vscroll;
|
||||||
enum android_motionevent_buttons buttons;
|
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
enum android_keyevent_action action; // action for the BACK key
|
enum android_keyevent_action action; // action for the BACK key
|
||||||
// screen may only be turned on on ACTION_DOWN
|
// screen may only be turned on on ACTION_DOWN
|
||||||
} back_or_screen_on;
|
} back_or_screen_on;
|
||||||
struct {
|
struct {
|
||||||
enum sc_copy_key copy_key;
|
|
||||||
} get_clipboard;
|
|
||||||
struct {
|
|
||||||
uint64_t sequence;
|
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
bool paste;
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
enum sc_screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -93,12 +82,12 @@ struct sc_control_msg {
|
|||||||
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
||||||
// return the number of bytes written
|
// return the number of bytes written
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
control_msg_log(const struct control_msg *msg);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
control_msg_destroy(struct control_msg *msg);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
controller_init(struct controller *controller, socket_t control_socket) {
|
||||||
struct sc_acksync *acksync) {
|
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
|
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -34,23 +33,23 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_destroy(struct sc_controller *controller) {
|
controller_destroy(struct controller *controller) {
|
||||||
sc_cond_destroy(&controller->msg_cond);
|
sc_cond_destroy(&controller->msg_cond);
|
||||||
sc_mutex_destroy(&controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct control_msg msg;
|
||||||
while (cbuf_take(&controller->queue, &msg)) {
|
while (cbuf_take(&controller->queue, &msg)) {
|
||||||
sc_control_msg_destroy(&msg);
|
control_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_push_msg(struct sc_controller *controller,
|
controller_push_msg(struct controller *controller,
|
||||||
const struct sc_control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
sc_control_msg_log(msg);
|
control_msg_log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
@@ -64,21 +63,20 @@ sc_controller_push_msg(struct sc_controller *controller,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_msg(struct sc_controller *controller,
|
process_msg(struct controller *controller,
|
||||||
const struct sc_control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
size_t length = control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ssize_t w =
|
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
|
||||||
net_send_all(controller->control_socket, serialized_msg, length);
|
|
||||||
return (size_t) w == length;
|
return (size_t) w == length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_controller(void *data) {
|
run_controller(void *data) {
|
||||||
struct sc_controller *controller = data;
|
struct controller *controller = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
@@ -90,14 +88,14 @@ run_controller(void *data) {
|
|||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct sc_control_msg msg;
|
struct control_msg msg;
|
||||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
bool ok = process_msg(controller, &msg);
|
bool ok = process_msg(controller, &msg);
|
||||||
sc_control_msg_destroy(&msg);
|
control_msg_destroy(&msg);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGD("Could not write msg to socket");
|
LOGD("Could not write msg to socket");
|
||||||
break;
|
break;
|
||||||
@@ -107,18 +105,18 @@ run_controller(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_start(struct sc_controller *controller) {
|
controller_start(struct controller *controller) {
|
||||||
LOGD("Starting controller thread");
|
LOGD("Starting controller thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&controller->thread, run_controller,
|
bool ok = sc_thread_create(&controller->thread, run_controller,
|
||||||
"scrcpy-ctl", controller);
|
"controller", controller);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start controller thread");
|
LOGC("Could not start controller thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiver_start(&controller->receiver)) {
|
if (!receiver_start(&controller->receiver)) {
|
||||||
sc_controller_stop(controller);
|
controller_stop(controller);
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -127,7 +125,7 @@ sc_controller_start(struct sc_controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_stop(struct sc_controller *controller) {
|
controller_stop(struct controller *controller) {
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
controller->stopped = true;
|
controller->stopped = true;
|
||||||
sc_cond_signal(&controller->msg_cond);
|
sc_cond_signal(&controller->msg_cond);
|
||||||
@@ -135,7 +133,7 @@ sc_controller_stop(struct sc_controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_join(struct sc_controller *controller) {
|
controller_join(struct controller *controller) {
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
receiver_join(&controller->receiver);
|
receiver_join(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_CONTROLLER_H
|
#ifndef CONTROLLER_H
|
||||||
#define SC_CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -7,41 +7,39 @@
|
|||||||
|
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "receiver.h"
|
#include "receiver.h"
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
|
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||||
|
|
||||||
struct sc_controller {
|
struct controller {
|
||||||
sc_socket control_socket;
|
socket_t control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond msg_cond;
|
sc_cond msg_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_control_msg_queue queue;
|
struct control_msg_queue queue;
|
||||||
struct receiver receiver;
|
struct receiver receiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
controller_init(struct controller *controller, socket_t control_socket);
|
||||||
struct sc_acksync *acksync);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_destroy(struct sc_controller *controller);
|
controller_destroy(struct controller *controller);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_start(struct sc_controller *controller);
|
controller_start(struct controller *controller);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_stop(struct sc_controller *controller);
|
controller_stop(struct controller *controller);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_controller_join(struct sc_controller *controller);
|
controller_join(struct controller *controller);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_controller_push_msg(struct sc_controller *controller,
|
controller_push_msg(struct controller *controller,
|
||||||
const struct sc_control_msg *msg);
|
const struct control_msg *msg);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct sc_size {
|
struct size {
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_point {
|
struct point {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_position {
|
struct position {
|
||||||
// The video screen size may be different from the real device screen size,
|
// The video screen size may be different from the real device screen size,
|
||||||
// so store to which size the absolute position apply, to scale it
|
// so store to which size the absolute position apply, to scale it
|
||||||
// accordingly.
|
// accordingly.
|
||||||
struct sc_size screen_size;
|
struct size screen_size;
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to decoder */
|
/** Downcast packet_sink to decoder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
|
||||||
while (count) {
|
while (count) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[--count];
|
struct sc_frame_sink *sink = decoder->sinks[--count];
|
||||||
sink->ops->close(sink);
|
sink->ops->close(sink);
|
||||||
@@ -20,17 +20,17 @@ sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
decoder_close_sinks(struct decoder *decoder) {
|
||||||
sc_decoder_close_first_sinks(decoder, decoder->sink_count);
|
decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
decoder_open_sinks(struct decoder *decoder) {
|
||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->open(sink)) {
|
if (!sink->ops->open(sink)) {
|
||||||
LOGE("Could not open frame sink %d", i);
|
LOGE("Could not open frame sink %d", i);
|
||||||
sc_decoder_close_first_sinks(decoder, i);
|
decoder_close_first_sinks(decoder, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,15 +39,13 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
||||||
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!decoder->codec_ctx) {
|
if (!decoder->codec_ctx) {
|
||||||
LOG_OOM();
|
LOGC("Could not allocate decoder context");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder->codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
|
||||||
|
|
||||||
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
|
||||||
LOGE("Could not open codec");
|
LOGE("Could not open codec");
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
@@ -56,13 +54,13 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
|
|
||||||
decoder->frame = av_frame_alloc();
|
decoder->frame = av_frame_alloc();
|
||||||
if (!decoder->frame) {
|
if (!decoder->frame) {
|
||||||
LOG_OOM();
|
LOGE("Could not create decoder frame");
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_decoder_open_sinks(decoder)) {
|
if (!decoder_open_sinks(decoder)) {
|
||||||
LOGE("Could not open decoder sinks");
|
LOGE("Could not open decoder sinks");
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
@@ -74,15 +72,15 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_close(struct sc_decoder *decoder) {
|
decoder_close(struct decoder *decoder) {
|
||||||
sc_decoder_close_sinks(decoder);
|
decoder_close_sinks(decoder);
|
||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
|
||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->push(sink, frame)) {
|
if (!sink->ops->push(sink, frame)) {
|
||||||
@@ -95,7 +93,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
if (is_config) {
|
if (is_config) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
@@ -124,40 +122,39 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
return sc_decoder_open(decoder, codec);
|
return decoder_open(decoder, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
sc_decoder_close(decoder);
|
decoder_close(decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
const AVPacket *packet) {
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
return decoder_push(decoder, packet);
|
||||||
return sc_decoder_push(decoder, packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder) {
|
decoder_init(struct decoder *decoder) {
|
||||||
decoder->sink_count = 0;
|
decoder->sink_count = 0;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
.open = sc_decoder_packet_sink_open,
|
.open = decoder_packet_sink_open,
|
||||||
.close = sc_decoder_packet_sink_close,
|
.close = decoder_packet_sink_close,
|
||||||
.push = sc_decoder_packet_sink_push,
|
.push = decoder_packet_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
decoder->packet_sink.ops = &ops;
|
decoder->packet_sink.ops = &ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) {
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
|
||||||
assert(decoder->sink_count < SC_DECODER_MAX_SINKS);
|
assert(decoder->sink_count < DECODER_MAX_SINKS);
|
||||||
assert(sink);
|
assert(sink);
|
||||||
assert(sink->ops);
|
assert(sink->ops);
|
||||||
decoder->sinks[decoder->sink_count++] = sink;
|
decoder->sinks[decoder->sink_count++] = sink;
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
#ifndef SC_DECODER_H
|
#ifndef DECODER_H
|
||||||
#define SC_DECODER_H
|
#define DECODER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include "trait/packet_sink.h"
|
#include "trait/packet_sink.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#define SC_DECODER_MAX_SINKS 2
|
#define DECODER_MAX_SINKS 2
|
||||||
|
|
||||||
struct sc_decoder {
|
struct decoder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
|
||||||
unsigned sink_count;
|
unsigned sink_count;
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
AVCodecContext *codec_ctx;
|
||||||
@@ -22,9 +21,9 @@ struct sc_decoder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_init(struct sc_decoder *decoder);
|
decoder_init(struct decoder *decoder);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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/binary.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
|
|
||||||
// | `- key frame
|
|
||||||
// `-- config packet
|
|
||||||
|
|
||||||
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 3
|
|
||||||
|
|
||||||
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,10 +1,9 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/binary.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
@@ -18,13 +17,13 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
msg->type = buf[0];
|
msg->type = buf[0];
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
size_t clipboard_len = sc_read32be(&buf[1]);
|
size_t clipboard_len = buffer_read32be(&buf[1]);
|
||||||
if (clipboard_len > len - 5) {
|
if (clipboard_len > len - 5) {
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
char *text = malloc(clipboard_len + 1);
|
char *text = malloc(clipboard_len + 1);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOG_OOM();
|
LOGW("Could not allocate text for clipboard");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (clipboard_len) {
|
if (clipboard_len) {
|
||||||
@@ -35,11 +34,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
msg->clipboard.text = text;
|
msg->clipboard.text = text;
|
||||||
return 5 + clipboard_len;
|
return 5 + clipboard_len;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
|
|
||||||
uint64_t sequence = sc_read64be(&buf[1]);
|
|
||||||
msg->ack_clipboard.sequence = sequence;
|
|
||||||
return 9;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
return -1; // error, we cannot recover
|
return -1; // error, we cannot recover
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_DEVICEMSG_H
|
#ifndef DEVICEMSG_H
|
||||||
#define SC_DEVICEMSG_H
|
#define DEVICEMSG_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
enum device_msg_type {
|
enum device_msg_type {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct device_msg {
|
struct device_msg {
|
||||||
@@ -22,9 +21,6 @@ struct device_msg {
|
|||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
} clipboard;
|
} clipboard;
|
||||||
struct {
|
|
||||||
uint64_t sequence;
|
|
||||||
} ack_clipboard;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,2 @@
|
|||||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
|
||||||
#define EVENT_SERVER_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 = 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 process_t
|
||||||
|
install_apk(const char *serial, const char *file) {
|
||||||
|
return adb_install(serial, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static process_t
|
||||||
|
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 = 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;
|
||||||
|
|
||||||
|
process_t process;
|
||||||
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
|
LOGI("Installing %s...", req.file);
|
||||||
|
process = install_apk(file_handler->serial, req.file);
|
||||||
|
} else {
|
||||||
|
LOGI("Pushing %s...", req.file);
|
||||||
|
process = push_file(file_handler->serial, req.file,
|
||||||
|
file_handler->push_target);
|
||||||
|
}
|
||||||
|
file_handler->current_process = process;
|
||||||
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
|
if (process_check_success(process, "adb install", false)) {
|
||||||
|
LOGI("%s successfully installed", req.file);
|
||||||
|
} else {
|
||||||
|
LOGE("Failed to install %s", req.file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (process_check_success(process, "adb push", false)) {
|
||||||
|
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 necessary already terminated)
|
||||||
|
// Execute this call with mutex locked to avoid race conditions with
|
||||||
|
// file_handler_stop()
|
||||||
|
process_close(file_handler->current_process);
|
||||||
|
file_handler->current_process = PROCESS_NONE;
|
||||||
|
sc_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 != PROCESS_NONE) {
|
||||||
|
if (!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;
|
||||||
|
process_t 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"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
|
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_init(struct sc_fps_counter *counter) {
|
fps_counter_init(struct fps_counter *counter) {
|
||||||
bool ok = sc_mutex_init(&counter->mutex);
|
bool ok = sc_mutex_init(&counter->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
@@ -27,26 +27,26 @@ sc_fps_counter_init(struct sc_fps_counter *counter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_destroy(struct sc_fps_counter *counter) {
|
fps_counter_destroy(struct fps_counter *counter) {
|
||||||
sc_cond_destroy(&counter->state_cond);
|
sc_cond_destroy(&counter->state_cond);
|
||||||
sc_mutex_destroy(&counter->mutex);
|
sc_mutex_destroy(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
is_started(struct sc_fps_counter *counter) {
|
is_started(struct fps_counter *counter) {
|
||||||
return atomic_load_explicit(&counter->started, memory_order_acquire);
|
return atomic_load_explicit(&counter->started, memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_started(struct sc_fps_counter *counter, bool started) {
|
set_started(struct fps_counter *counter, bool started) {
|
||||||
atomic_store_explicit(&counter->started, started, memory_order_release);
|
atomic_store_explicit(&counter->started, started, memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be called with mutex locked
|
// must be called with mutex locked
|
||||||
static void
|
static void
|
||||||
display_fps(struct sc_fps_counter *counter) {
|
display_fps(struct fps_counter *counter) {
|
||||||
unsigned rendered_per_second =
|
unsigned rendered_per_second =
|
||||||
counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL;
|
counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL;
|
||||||
if (counter->nr_skipped) {
|
if (counter->nr_skipped) {
|
||||||
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
|
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
|
||||||
counter->nr_skipped);
|
counter->nr_skipped);
|
||||||
@@ -57,7 +57,7 @@ display_fps(struct sc_fps_counter *counter) {
|
|||||||
|
|
||||||
// must be called with mutex locked
|
// must be called with mutex locked
|
||||||
static void
|
static void
|
||||||
check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
|
check_interval_expired(struct fps_counter *counter, uint32_t now) {
|
||||||
if (now < counter->next_timestamp) {
|
if (now < counter->next_timestamp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,13 +67,13 @@ check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
|
|||||||
counter->nr_skipped = 0;
|
counter->nr_skipped = 0;
|
||||||
// add a multiple of the interval
|
// add a multiple of the interval
|
||||||
uint32_t elapsed_slices =
|
uint32_t elapsed_slices =
|
||||||
(now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1;
|
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
|
||||||
counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices;
|
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_fps_counter(void *data) {
|
run_fps_counter(void *data) {
|
||||||
struct sc_fps_counter *counter = data;
|
struct fps_counter *counter = data;
|
||||||
|
|
||||||
sc_mutex_lock(&counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
while (!counter->interrupted) {
|
while (!counter->interrupted) {
|
||||||
@@ -94,9 +94,9 @@ run_fps_counter(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_start(struct sc_fps_counter *counter) {
|
fps_counter_start(struct fps_counter *counter) {
|
||||||
sc_mutex_lock(&counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL;
|
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL;
|
||||||
counter->nr_rendered = 0;
|
counter->nr_rendered = 0;
|
||||||
counter->nr_skipped = 0;
|
counter->nr_skipped = 0;
|
||||||
sc_mutex_unlock(&counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
@@ -108,7 +108,7 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
|
|||||||
// same thread, no need to lock
|
// same thread, no need to lock
|
||||||
if (!counter->thread_started) {
|
if (!counter->thread_started) {
|
||||||
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
|
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
|
||||||
"scrcpy-fps", counter);
|
"fps counter", counter);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start FPS counter thread");
|
LOGE("Could not start FPS counter thread");
|
||||||
return false;
|
return false;
|
||||||
@@ -117,24 +117,22 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
|
|||||||
counter->thread_started = true;
|
counter->thread_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("FPS counter started");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_stop(struct sc_fps_counter *counter) {
|
fps_counter_stop(struct fps_counter *counter) {
|
||||||
set_started(counter, false);
|
set_started(counter, false);
|
||||||
sc_cond_signal(&counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
LOGI("FPS counter stopped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_is_started(struct sc_fps_counter *counter) {
|
fps_counter_is_started(struct fps_counter *counter) {
|
||||||
return is_started(counter);
|
return is_started(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
|
fps_counter_interrupt(struct fps_counter *counter) {
|
||||||
if (!counter->thread_started) {
|
if (!counter->thread_started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,7 +145,7 @@ sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_join(struct sc_fps_counter *counter) {
|
fps_counter_join(struct fps_counter *counter) {
|
||||||
if (counter->thread_started) {
|
if (counter->thread_started) {
|
||||||
// interrupted must be set by the thread calling join(), so no need to
|
// interrupted must be set by the thread calling join(), so no need to
|
||||||
// lock for the assertion
|
// lock for the assertion
|
||||||
@@ -158,7 +156,7 @@ sc_fps_counter_join(struct sc_fps_counter *counter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
|
fps_counter_add_rendered_frame(struct fps_counter *counter) {
|
||||||
if (!is_started(counter)) {
|
if (!is_started(counter)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -171,7 +169,7 @@ sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) {
|
fps_counter_add_skipped_frame(struct fps_counter *counter) {
|
||||||
if (!is_started(counter)) {
|
if (!is_started(counter)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_FPSCOUNTER_H
|
#ifndef FPSCOUNTER_H
|
||||||
#define SC_FPSCOUNTER_H
|
#define FPSCOUNTER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct sc_fps_counter {
|
struct fps_counter {
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond state_cond;
|
sc_cond state_cond;
|
||||||
@@ -28,32 +28,32 @@ struct sc_fps_counter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_init(struct sc_fps_counter *counter);
|
fps_counter_init(struct fps_counter *counter);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_destroy(struct sc_fps_counter *counter);
|
fps_counter_destroy(struct fps_counter *counter);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_start(struct sc_fps_counter *counter);
|
fps_counter_start(struct fps_counter *counter);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_stop(struct sc_fps_counter *counter);
|
fps_counter_stop(struct fps_counter *counter);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_fps_counter_is_started(struct sc_fps_counter *counter);
|
fps_counter_is_started(struct fps_counter *counter);
|
||||||
|
|
||||||
// request to stop the thread (on quit)
|
// request to stop the thread (on quit)
|
||||||
// must be called before sc_fps_counter_join()
|
// must be called before fps_counter_join()
|
||||||
void
|
void
|
||||||
sc_fps_counter_interrupt(struct sc_fps_counter *counter);
|
fps_counter_interrupt(struct fps_counter *counter);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_join(struct sc_fps_counter *counter);
|
fps_counter_join(struct fps_counter *counter);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter);
|
fps_counter_add_rendered_frame(struct fps_counter *counter);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter);
|
fps_counter_add_skipped_frame(struct fps_counter *counter);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -10,13 +10,11 @@ bool
|
|||||||
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
|
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
|
||||||
fb->pending_frame = av_frame_alloc();
|
fb->pending_frame = av_frame_alloc();
|
||||||
if (!fb->pending_frame) {
|
if (!fb->pending_frame) {
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fb->tmp_frame = av_frame_alloc();
|
fb->tmp_frame = av_frame_alloc();
|
||||||
if (!fb->tmp_frame) {
|
if (!fb->tmp_frame) {
|
||||||
LOG_OOM();
|
|
||||||
av_frame_free(&fb->pending_frame);
|
av_frame_free(&fb->pending_frame);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -50,7 +48,9 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
||||||
bool *previous_frame_skipped) {
|
bool *previous_frame_skipped) {
|
||||||
|
sc_mutex_lock(&fb->mutex);
|
||||||
|
|
||||||
// Use a temporary frame to preserve pending_frame in case of error.
|
// Use a temporary frame to preserve pending_frame in case of error.
|
||||||
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
||||||
int r = av_frame_ref(fb->tmp_frame, frame);
|
int r = av_frame_ref(fb->tmp_frame, frame);
|
||||||
@@ -59,8 +59,6 @@ sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_mutex_lock(&fb->mutex);
|
|
||||||
|
|
||||||
// Now that av_frame_ref() succeeded, we can replace the previous
|
// Now that av_frame_ref() succeeded, we can replace the previous
|
||||||
// pending_frame
|
// pending_frame
|
||||||
swap_frames(&fb->pending_frame, &fb->tmp_frame);
|
swap_frames(&fb->pending_frame, &fb->tmp_frame);
|
||||||
|
|||||||
341
app/src/hid_keyboard.c
Normal file
341
app/src/hid_keyboard.c
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
#include "hid_keyboard.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast key processor to hid_keyboard */
|
||||||
|
#define DOWNCAST(KP) \
|
||||||
|
container_of(KP, struct hid_keyboard, key_processor)
|
||||||
|
|
||||||
|
#define HID_KEYBOARD_ACCESSORY_ID 1
|
||||||
|
|
||||||
|
#define HID_KEYBOARD_MODIFIER_NONE 0x00
|
||||||
|
#define HID_KEYBOARD_MODIFIER_LEFT_CONTROL (1 << 0)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_LEFT_SHIFT (1 << 1)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_LEFT_ALT (1 << 2)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_LEFT_GUI (1 << 3)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_RIGHT_CONTROL (1 << 4)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_RIGHT_SHIFT (1 << 5)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_RIGHT_ALT (1 << 6)
|
||||||
|
#define HID_KEYBOARD_MODIFIER_RIGHT_GUI (1 << 7)
|
||||||
|
|
||||||
|
#define HID_KEYBOARD_INDEX_MODIFIER 0
|
||||||
|
#define HID_KEYBOARD_INDEX_KEYS 2
|
||||||
|
|
||||||
|
// USB HID protocol says 6 keys in an event is the requirement for BIOS
|
||||||
|
// keyboard support, though OS could support more keys via modifying the report
|
||||||
|
// desc. 6 should be enough for scrcpy.
|
||||||
|
#define HID_KEYBOARD_MAX_KEYS 6
|
||||||
|
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
|
||||||
|
|
||||||
|
#define HID_KEYBOARD_RESERVED 0x00
|
||||||
|
#define HID_KEYBOARD_ERROR_ROLL_OVER 0x01
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For HID over AOAv2, only report descriptor is needed.
|
||||||
|
*
|
||||||
|
* The specification is available here:
|
||||||
|
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||||
|
*
|
||||||
|
* In particular, read:
|
||||||
|
* - 6.2.2 Report Descriptor
|
||||||
|
* - Appendix B.1 Protocol 1 (Keyboard)
|
||||||
|
* - Appendix C: Keyboard Implementation
|
||||||
|
*
|
||||||
|
* Normally a basic HID keyboard uses 8 bytes:
|
||||||
|
* Modifier Reserved Key Key Key Key Key Key
|
||||||
|
*
|
||||||
|
* You can dump your device's report descriptor with:
|
||||||
|
*
|
||||||
|
* sudo usbhid-dump -m vid:pid -e descriptor
|
||||||
|
*
|
||||||
|
* (change vid:pid' to your device's vendor ID and product ID).
|
||||||
|
*/
|
||||||
|
static const unsigned char keyboard_report_desc[] = {
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (Keyboard)
|
||||||
|
0x09, 0x06,
|
||||||
|
|
||||||
|
// Collection (Application)
|
||||||
|
0xA1, 0x01,
|
||||||
|
|
||||||
|
// Usage Page (Key Codes)
|
||||||
|
0x05, 0x07,
|
||||||
|
// Usage Minimum (224)
|
||||||
|
0x19, 0xE0,
|
||||||
|
// Usage Maximum (231)
|
||||||
|
0x29, 0xE7,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (1)
|
||||||
|
0x25, 0x01,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Report Count (8)
|
||||||
|
0x95, 0x08,
|
||||||
|
// Input (Data, Variable, Absolute): Modifier byte
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Report Size (8)
|
||||||
|
0x75, 0x08,
|
||||||
|
// Report Count (1)
|
||||||
|
0x95, 0x01,
|
||||||
|
// Input (Constant): Reserved byte
|
||||||
|
0x81, 0x01,
|
||||||
|
|
||||||
|
// Usage Page (LEDs)
|
||||||
|
0x05, 0x08,
|
||||||
|
// Usage Minimum (1)
|
||||||
|
0x19, 0x01,
|
||||||
|
// Usage Maximum (5)
|
||||||
|
0x29, 0x05,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Report Count (5)
|
||||||
|
0x95, 0x05,
|
||||||
|
// Output (Data, Variable, Absolute): LED report
|
||||||
|
0x91, 0x02,
|
||||||
|
|
||||||
|
// Report Size (3)
|
||||||
|
0x75, 0x03,
|
||||||
|
// Report Count (1)
|
||||||
|
0x95, 0x01,
|
||||||
|
// Output (Constant): LED report padding
|
||||||
|
0x91, 0x01,
|
||||||
|
|
||||||
|
// Usage Page (Key Codes)
|
||||||
|
0x05, 0x07,
|
||||||
|
// Usage Minimum (0)
|
||||||
|
0x19, 0x00,
|
||||||
|
// Usage Maximum (101)
|
||||||
|
0x29, HID_KEYBOARD_KEYS - 1,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum(101)
|
||||||
|
0x25, HID_KEYBOARD_KEYS - 1,
|
||||||
|
// Report Size (8)
|
||||||
|
0x75, 0x08,
|
||||||
|
// Report Count (6)
|
||||||
|
0x95, HID_KEYBOARD_MAX_KEYS,
|
||||||
|
// Input (Data, Array): Keys
|
||||||
|
0x81, 0x00,
|
||||||
|
|
||||||
|
// End Collection
|
||||||
|
0xC0
|
||||||
|
};
|
||||||
|
|
||||||
|
static unsigned char *
|
||||||
|
create_hid_keyboard_event(void) {
|
||||||
|
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
||||||
|
if (!buffer) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_KEYBOARD_MODIFIER_NONE;
|
||||||
|
buffer[1] = HID_KEYBOARD_RESERVED;
|
||||||
|
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char
|
||||||
|
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
|
||||||
|
unsigned char modifiers = HID_KEYBOARD_MODIFIER_NONE;
|
||||||
|
if (mod & KMOD_LCTRL) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_CONTROL;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_LSHIFT) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_SHIFT;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_LALT) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_ALT;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_LGUI) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_GUI;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_RCTRL) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_CONTROL;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_RSHIFT) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_SHIFT;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_RALT) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_ALT;
|
||||||
|
}
|
||||||
|
if (mod & KMOD_RGUI) {
|
||||||
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_GUI;
|
||||||
|
}
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
gen(struct hid_keyboard *kb, struct hid_event *hid_event) {
|
||||||
|
hid_event->from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
|
||||||
|
hid_event->buffer = create_hid_keyboard_event();
|
||||||
|
if (!hid_event->buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hid_event->size = HID_KEYBOARD_EVENT_SIZE;
|
||||||
|
hid_event->buffer[HID_KEYBOARD_INDEX_KEYS] = 57; //CAPSLOCK
|
||||||
|
for (int i = 0; i < HID_KEYBOARD_EVENT_SIZE; ++i)
|
||||||
|
printf("%02x ", hid_event->buffer[i]);
|
||||||
|
printf("\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_hid_keyboard_event(struct hid_keyboard *kb, struct hid_event *hid_event,
|
||||||
|
const SDL_KeyboardEvent *event) {
|
||||||
|
hid_event->buffer = create_hid_keyboard_event();
|
||||||
|
if (!hid_event->buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hid_event->size = HID_KEYBOARD_EVENT_SIZE;
|
||||||
|
|
||||||
|
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
|
||||||
|
|
||||||
|
SDL_Scancode scancode = event->keysym.scancode;
|
||||||
|
assert(scancode >= 0);
|
||||||
|
if (scancode < HID_KEYBOARD_KEYS) {
|
||||||
|
// Pressed is true and released is false
|
||||||
|
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
|
||||||
|
LOGV("keys[%02x] = %s", scancode,
|
||||||
|
kb->keys[scancode] ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
|
||||||
|
|
||||||
|
// Re-calculate pressed keys every time
|
||||||
|
int keys_pressed_count = 0;
|
||||||
|
for (int i = 0; i < HID_KEYBOARD_KEYS; ++i) {
|
||||||
|
if (kb->keys[i]) {
|
||||||
|
// USB HID protocol says that if keys exceeds report count, a
|
||||||
|
// phantom state should be reported
|
||||||
|
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
|
||||||
|
// Pantom state:
|
||||||
|
// - Modifiers
|
||||||
|
// - Reserved
|
||||||
|
// - ErrorRollOver * HID_KEYBOARD_MAX_KEYS
|
||||||
|
memset(&hid_event->buffer[HID_KEYBOARD_INDEX_KEYS],
|
||||||
|
HID_KEYBOARD_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_event->buffer[HID_KEYBOARD_INDEX_KEYS + keys_pressed_count] = i;
|
||||||
|
++keys_pressed_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
scancode_is_modifier(SDL_Scancode scancode) {
|
||||||
|
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
hid_keyboard_convert_event(struct hid_keyboard *kb, struct hid_event *hid_event,
|
||||||
|
const SDL_KeyboardEvent *event) {
|
||||||
|
LOGV(
|
||||||
|
"Type: %s, Repeat: %s, Modifiers: %02x, Key: %02x",
|
||||||
|
event->type == SDL_KEYDOWN ? "down" : "up",
|
||||||
|
event->repeat != 0 ? "true" : "false",
|
||||||
|
sdl_keymod_to_hid_modifiers(event->keysym.mod),
|
||||||
|
event->keysym.scancode
|
||||||
|
);
|
||||||
|
|
||||||
|
hid_event->from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
|
||||||
|
|
||||||
|
SDL_Scancode scancode = event->keysym.scancode;
|
||||||
|
assert(scancode >= 0);
|
||||||
|
// SDL also generates events when only modifiers are pressed, we cannot
|
||||||
|
// ignore them totally, for example press 'a' first then press 'Control',
|
||||||
|
// if we ignore 'Control' event, only 'a' is sent.
|
||||||
|
if (scancode < HID_KEYBOARD_KEYS || scancode_is_modifier(scancode)) {
|
||||||
|
return convert_hid_keyboard_event(kb, hid_event, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
|
const SDL_KeyboardEvent *event) {
|
||||||
|
if (event->repeat) {
|
||||||
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
|
// just ignore key repeat here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hid_keyboard *kh = DOWNCAST(kp);
|
||||||
|
|
||||||
|
struct hid_event hid_event;
|
||||||
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
|
if (hid_keyboard_convert_event(kh, &hid_event, event)) {
|
||||||
|
if (!aoa_push_hid_event(kh->aoa, &hid_event)) {
|
||||||
|
LOGW("Could request HID event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||||
|
const SDL_TextInputEvent *event) {
|
||||||
|
(void) kp;
|
||||||
|
(void) event;
|
||||||
|
|
||||||
|
// Never forward text input via HID (all the keys are injected separately)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod) {
|
||||||
|
kb->aoa = aoa;
|
||||||
|
|
||||||
|
// FIXME problem: aoa_setup_hid is executed from this thread, but events
|
||||||
|
// are sent from the aoa thread. Is this thread-safe?
|
||||||
|
// In practice, sending CAPS_LOCK immediately after fails with a Pipe error
|
||||||
|
// but we must know immediately if this fails or not
|
||||||
|
// TODO test with a simple mutex to confirm
|
||||||
|
bool ok = aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||||
|
keyboard_report_desc,
|
||||||
|
ARRAY_LEN(keyboard_report_desc));
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Register HID for keyboard failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all states
|
||||||
|
memset(kb->keys, false, HID_KEYBOARD_KEYS);
|
||||||
|
|
||||||
|
static const struct sc_key_processor_ops ops = {
|
||||||
|
.process_key = sc_key_processor_process_key,
|
||||||
|
.process_text = sc_key_processor_process_text,
|
||||||
|
};
|
||||||
|
|
||||||
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
|
if (lock_mod & SC_MOD_CAPS_LOCK) {
|
||||||
|
struct hid_event event;
|
||||||
|
gen(kb, &event); // FIXME error handling
|
||||||
|
if (!aoa_push_hid_event(kb->aoa, &event)) {
|
||||||
|
LOGW("Could request HID event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lock_mod & SC_MOD_NUM_LOCK) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hid_keyboard_destroy(struct hid_keyboard *kb) {
|
||||||
|
// Unregister HID keyboard so the soft keyboard shows again on Android
|
||||||
|
bool ok = aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not unregister HID");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
#ifndef SC_HID_KEYBOARD_H
|
#ifndef HID_KEYBOARD_H
|
||||||
#define SC_HID_KEYBOARD_H
|
#define HID_KEYBOARD_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "aoa_hid.h"
|
#include "aoa_hid.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
@@ -12,7 +14,10 @@
|
|||||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||||
// HID protocol.
|
// HID protocol.
|
||||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
#define HID_KEYBOARD_KEYS 0x66
|
||||||
|
|
||||||
|
#define SC_MOD_CAPS_LOCK 0x1
|
||||||
|
#define SC_MOD_NUM_LOCK 0x2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||||
@@ -26,19 +31,17 @@
|
|||||||
* phantom state. Don't forget that modifiers should be updated too, even for
|
* phantom state. Don't forget that modifiers should be updated too, even for
|
||||||
* phantom state.
|
* phantom state.
|
||||||
*/
|
*/
|
||||||
struct sc_hid_keyboard {
|
struct hid_keyboard {
|
||||||
struct sc_key_processor key_processor; // key processor trait
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
|
||||||
struct sc_aoa *aoa;
|
struct aoa *aoa;
|
||||||
bool keys[SC_HID_KEYBOARD_KEYS];
|
bool keys[HID_KEYBOARD_KEYS];
|
||||||
|
|
||||||
bool mod_lock_synchronized;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
|
hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
|
hid_keyboard_destroy(struct hid_keyboard *kb);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
291
app/src/icon.c
291
app/src/icon.c
@@ -1,291 +0,0 @@
|
|||||||
#include "icon.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
#include <libavutil/pixdesc.h>
|
|
||||||
#include <libavutil/pixfmt.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
|
||||||
#define SCRCPY_DEFAULT_ICON_PATH \
|
|
||||||
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
|
|
||||||
|
|
||||||
static char *
|
|
||||||
get_icon_path(void) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
|
|
||||||
#else
|
|
||||||
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
|
|
||||||
#endif
|
|
||||||
if (icon_path_env) {
|
|
||||||
// if the envvar is set, use it
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
char *icon_path = sc_str_from_wchars(icon_path_env);
|
|
||||||
#else
|
|
||||||
char *icon_path = strdup(icon_path_env);
|
|
||||||
#endif
|
|
||||||
if (!icon_path) {
|
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
|
||||||
return icon_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef PORTABLE
|
|
||||||
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
|
||||||
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
|
||||||
if (!icon_path) {
|
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
|
||||||
if (!icon_path) {
|
|
||||||
LOGE("Could not get icon path");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
LOGD("Using icon (portable): %s", icon_path);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return icon_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AVFrame *
|
|
||||||
decode_image(const char *path) {
|
|
||||||
AVFrame *result = NULL;
|
|
||||||
|
|
||||||
AVFormatContext *ctx = avformat_alloc_context();
|
|
||||||
if (!ctx) {
|
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
|
|
||||||
LOGE("Could not open image codec: %s", path);
|
|
||||||
goto free_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avformat_find_stream_info(ctx, NULL) < 0) {
|
|
||||||
LOGE("Could not find image stream info");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
|
||||||
if (stream < 0 ) {
|
|
||||||
LOGE("Could not find best image stream");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
|
||||||
if (!codec) {
|
|
||||||
LOGE("Could not find image decoder");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
|
||||||
if (!codec_ctx) {
|
|
||||||
LOG_OOM();
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
|
|
||||||
LOGE("Could not fill codec context");
|
|
||||||
goto free_codec_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
|
||||||
LOGE("Could not open image codec");
|
|
||||||
goto free_codec_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVFrame *frame = av_frame_alloc();
|
|
||||||
if (!frame) {
|
|
||||||
LOG_OOM();
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
|
||||||
if (!packet) {
|
|
||||||
LOG_OOM();
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (av_read_frame(ctx, packet) < 0) {
|
|
||||||
LOGE("Could not read frame");
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
|
|
||||||
LOGE("Could not send icon packet: %d", ret);
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
|
||||||
LOGE("Could not receive icon frame: %d", ret);
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
av_packet_free(&packet);
|
|
||||||
|
|
||||||
result = frame;
|
|
||||||
|
|
||||||
close_codec:
|
|
||||||
avcodec_close(codec_ctx);
|
|
||||||
free_codec_ctx:
|
|
||||||
avcodec_free_context(&codec_ctx);
|
|
||||||
close_input:
|
|
||||||
avformat_close_input(&ctx);
|
|
||||||
free_ctx:
|
|
||||||
avformat_free_context(ctx);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
|
||||||
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
|
|
||||||
// versions.
|
|
||||||
typedef int SDL_PixelFormatEnum;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static SDL_PixelFormatEnum
|
|
||||||
to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
|
||||||
switch (fmt) {
|
|
||||||
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
|
|
||||||
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
|
|
||||||
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
|
|
||||||
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
|
|
||||||
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
|
|
||||||
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
|
|
||||||
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
|
|
||||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
|
|
||||||
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
|
|
||||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
|
|
||||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 12)
|
|
||||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
|
|
||||||
#endif
|
|
||||||
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
|
|
||||||
default: return SDL_PIXELFORMAT_UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SDL_Surface *
|
|
||||||
load_from_path(const char *path) {
|
|
||||||
AVFrame *frame = decode_image(path);
|
|
||||||
if (!frame) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
|
|
||||||
if (!desc) {
|
|
||||||
LOGE("Could not get icon format descriptor");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
|
|
||||||
if (!is_packed) {
|
|
||||||
LOGE("Could not load non-packed icon");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
|
|
||||||
if (format == SDL_PIXELFORMAT_UNKNOWN) {
|
|
||||||
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
|
|
||||||
frame->format);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bits_per_pixel = av_get_bits_per_pixel(desc);
|
|
||||||
SDL_Surface *surface =
|
|
||||||
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
|
|
||||||
frame->width, frame->height,
|
|
||||||
bits_per_pixel,
|
|
||||||
frame->linesize[0],
|
|
||||||
format);
|
|
||||||
|
|
||||||
if (!surface) {
|
|
||||||
LOGE("Could not create icon surface");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame->format == AV_PIX_FMT_PAL8) {
|
|
||||||
// Initialize the SDL palette
|
|
||||||
uint8_t *data = frame->data[1];
|
|
||||||
SDL_Color colors[256];
|
|
||||||
for (int i = 0; i < 256; ++i) {
|
|
||||||
SDL_Color *color = &colors[i];
|
|
||||||
|
|
||||||
// The palette is transported in AVFrame.data[1], is 1024 bytes
|
|
||||||
// long (256 4-byte entries) and is formatted the same as in
|
|
||||||
// AV_PIX_FMT_RGB32 described above (i.e., it is also
|
|
||||||
// endian-specific).
|
|
||||||
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
|
|
||||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
||||||
color->a = data[i * 4];
|
|
||||||
color->r = data[i * 4 + 1];
|
|
||||||
color->g = data[i * 4 + 2];
|
|
||||||
color->b = data[i * 4 + 3];
|
|
||||||
#else
|
|
||||||
color->a = data[i * 4 + 3];
|
|
||||||
color->r = data[i * 4 + 2];
|
|
||||||
color->g = data[i * 4 + 1];
|
|
||||||
color->b = data[i * 4];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Palette *palette = surface->format->palette;
|
|
||||||
assert(palette);
|
|
||||||
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
|
|
||||||
if (ret) {
|
|
||||||
LOGE("Could not set palette colors");
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
surface->userdata = frame; // frame owns the data
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
|
|
||||||
error:
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface *
|
|
||||||
scrcpy_icon_load() {
|
|
||||||
char *icon_path = get_icon_path();
|
|
||||||
if (!icon_path) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface *icon = load_from_path(icon_path);
|
|
||||||
free(icon_path);
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
scrcpy_icon_destroy(SDL_Surface *icon) {
|
|
||||||
AVFrame *frame = icon->userdata;
|
|
||||||
assert(frame);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
SDL_FreeSurface(icon);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#ifndef SC_ICON_H
|
|
||||||
#define SC_ICON_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
SDL_Surface *
|
|
||||||
scrcpy_icon_load(void);
|
|
||||||
|
|
||||||
void
|
|
||||||
scrcpy_icon_destroy(SDL_Surface *icon);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
53
app/src/icon.xpm
Normal file
53
app/src/icon.xpm
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/* XPM */
|
||||||
|
static char * icon_xpm[] = {
|
||||||
|
"48 48 2 1",
|
||||||
|
" c None",
|
||||||
|
". c #96C13E",
|
||||||
|
" .. .. ",
|
||||||
|
" ... ... ",
|
||||||
|
" ... ...... ... ",
|
||||||
|
" ................ ",
|
||||||
|
" .............. ",
|
||||||
|
" ................ ",
|
||||||
|
" .................. ",
|
||||||
|
" .................... ",
|
||||||
|
" ..... ........ ..... ",
|
||||||
|
" ..... ........ ..... ",
|
||||||
|
" ...................... ",
|
||||||
|
" ........................ ",
|
||||||
|
" ........................ ",
|
||||||
|
" ........................ ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" .... ........................ .... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" .... ........................ .... ",
|
||||||
|
" ........................ ",
|
||||||
|
" ...................... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" .... .... "};
|
||||||
@@ -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;
|
|
||||||
float hscroll;
|
|
||||||
float 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
|
#ifndef INPUTMANAGER_H
|
||||||
#define SC_INPUTMANAGER_H
|
#define INPUTMANAGER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -8,23 +8,22 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "file_pusher.h"
|
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
#include "screen.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
struct sc_input_manager {
|
struct input_manager {
|
||||||
struct sc_controller *controller;
|
struct controller *controller;
|
||||||
struct sc_file_pusher *fp;
|
struct screen *screen;
|
||||||
struct sc_screen *screen;
|
|
||||||
|
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
|
||||||
|
bool control;
|
||||||
bool forward_all_clicks;
|
bool forward_all_clicks;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool clipboard_autosync;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
@@ -39,28 +38,15 @@ struct sc_input_manager {
|
|||||||
unsigned key_repeat;
|
unsigned key_repeat;
|
||||||
SDL_Keycode last_keycode;
|
SDL_Keycode last_keycode;
|
||||||
uint16_t last_mod;
|
uint16_t last_mod;
|
||||||
|
|
||||||
uint64_t next_sequence; // used for request acknowledgements
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_input_manager_params {
|
|
||||||
struct sc_controller *controller;
|
|
||||||
struct sc_file_pusher *fp;
|
|
||||||
struct sc_screen *screen;
|
|
||||||
struct sc_key_processor *kp;
|
|
||||||
struct sc_mouse_processor *mp;
|
|
||||||
|
|
||||||
bool forward_all_clicks;
|
|
||||||
bool legacy_paste;
|
|
||||||
bool clipboard_autosync;
|
|
||||||
const struct sc_shortcut_mods *shortcut_mods;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_input_manager_init(struct sc_input_manager *im,
|
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||||
const struct sc_input_manager_params *params);
|
struct screen *screen, struct sc_key_processor *kp,
|
||||||
|
struct sc_mouse_processor *mp,
|
||||||
|
const struct scrcpy_options *options);
|
||||||
|
|
||||||
void
|
bool
|
||||||
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
|
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,191 +1,108 @@
|
|||||||
#include "keyboard_inject.h"
|
#include "keyboard_inject.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
#include "android/input.h"
|
#include "android/input.h"
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "input_events.h"
|
|
||||||
#include "util/intmap.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast key processor to sc_keyboard_inject */
|
/** Downcast key processor to sc_keyboard_inject */
|
||||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
|
#define DOWNCAST(KP) \
|
||||||
|
container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||||
|
|
||||||
static enum android_keyevent_action
|
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||||
convert_keycode_action(enum sc_action action) {
|
#define FAIL default: return false
|
||||||
if (action == SC_ACTION_DOWN) {
|
static bool
|
||||||
return AKEY_EVENT_ACTION_DOWN;
|
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
||||||
|
switch (from) {
|
||||||
|
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
||||||
|
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
|
||||||
|
FAIL;
|
||||||
}
|
}
|
||||||
assert(action == SC_ACTION_UP);
|
|
||||||
return AKEY_EVENT_ACTION_UP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
enum sc_key_inject_mode key_inject_mode) {
|
bool prefer_text) {
|
||||||
// Navigation keys and ENTER.
|
switch (from) {
|
||||||
// Used in all modes.
|
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||||
static const struct sc_intmap_entry special_keys[] = {
|
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||||
{SC_KEYCODE_RETURN, AKEYCODE_ENTER},
|
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
|
||||||
{SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
|
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
|
||||||
{SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE},
|
MAP(SDLK_TAB, AKEYCODE_TAB);
|
||||||
{SC_KEYCODE_BACKSPACE, AKEYCODE_DEL},
|
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
|
||||||
{SC_KEYCODE_TAB, AKEYCODE_TAB},
|
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
|
||||||
{SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP},
|
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
|
||||||
{SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL},
|
MAP(SDLK_END, AKEYCODE_MOVE_END);
|
||||||
{SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME},
|
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
|
||||||
{SC_KEYCODE_END, AKEYCODE_MOVE_END},
|
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
|
||||||
{SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN},
|
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||||
{SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT},
|
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||||
{SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT},
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
{SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN},
|
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||||
{SC_KEYCODE_UP, AKEYCODE_DPAD_UP},
|
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
|
||||||
{SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT},
|
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||||
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
|
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||||
{SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
|
|
||||||
{SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Numpad navigation keys.
|
|
||||||
// Used in all modes, when NumLock and Shift are disabled.
|
|
||||||
static const struct sc_intmap_entry kp_nav_keys[] = {
|
|
||||||
{SC_KEYCODE_KP_0, AKEYCODE_INSERT},
|
|
||||||
{SC_KEYCODE_KP_1, AKEYCODE_MOVE_END},
|
|
||||||
{SC_KEYCODE_KP_2, AKEYCODE_DPAD_DOWN},
|
|
||||||
{SC_KEYCODE_KP_3, AKEYCODE_PAGE_DOWN},
|
|
||||||
{SC_KEYCODE_KP_4, AKEYCODE_DPAD_LEFT},
|
|
||||||
{SC_KEYCODE_KP_6, AKEYCODE_DPAD_RIGHT},
|
|
||||||
{SC_KEYCODE_KP_7, AKEYCODE_MOVE_HOME},
|
|
||||||
{SC_KEYCODE_KP_8, AKEYCODE_DPAD_UP},
|
|
||||||
{SC_KEYCODE_KP_9, AKEYCODE_PAGE_UP},
|
|
||||||
{SC_KEYCODE_KP_PERIOD, AKEYCODE_FORWARD_DEL},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Letters and space.
|
|
||||||
// Used in non-text mode.
|
|
||||||
static const struct sc_intmap_entry alphaspace_keys[] = {
|
|
||||||
{SC_KEYCODE_a, AKEYCODE_A},
|
|
||||||
{SC_KEYCODE_b, AKEYCODE_B},
|
|
||||||
{SC_KEYCODE_c, AKEYCODE_C},
|
|
||||||
{SC_KEYCODE_d, AKEYCODE_D},
|
|
||||||
{SC_KEYCODE_e, AKEYCODE_E},
|
|
||||||
{SC_KEYCODE_f, AKEYCODE_F},
|
|
||||||
{SC_KEYCODE_g, AKEYCODE_G},
|
|
||||||
{SC_KEYCODE_h, AKEYCODE_H},
|
|
||||||
{SC_KEYCODE_i, AKEYCODE_I},
|
|
||||||
{SC_KEYCODE_j, AKEYCODE_J},
|
|
||||||
{SC_KEYCODE_k, AKEYCODE_K},
|
|
||||||
{SC_KEYCODE_l, AKEYCODE_L},
|
|
||||||
{SC_KEYCODE_m, AKEYCODE_M},
|
|
||||||
{SC_KEYCODE_n, AKEYCODE_N},
|
|
||||||
{SC_KEYCODE_o, AKEYCODE_O},
|
|
||||||
{SC_KEYCODE_p, AKEYCODE_P},
|
|
||||||
{SC_KEYCODE_q, AKEYCODE_Q},
|
|
||||||
{SC_KEYCODE_r, AKEYCODE_R},
|
|
||||||
{SC_KEYCODE_s, AKEYCODE_S},
|
|
||||||
{SC_KEYCODE_t, AKEYCODE_T},
|
|
||||||
{SC_KEYCODE_u, AKEYCODE_U},
|
|
||||||
{SC_KEYCODE_v, AKEYCODE_V},
|
|
||||||
{SC_KEYCODE_w, AKEYCODE_W},
|
|
||||||
{SC_KEYCODE_x, AKEYCODE_X},
|
|
||||||
{SC_KEYCODE_y, AKEYCODE_Y},
|
|
||||||
{SC_KEYCODE_z, AKEYCODE_Z},
|
|
||||||
{SC_KEYCODE_SPACE, AKEYCODE_SPACE},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Numbers and punctuation keys.
|
|
||||||
// Used in raw mode only.
|
|
||||||
static const struct sc_intmap_entry numbers_punct_keys[] = {
|
|
||||||
{SC_KEYCODE_HASH, AKEYCODE_POUND},
|
|
||||||
{SC_KEYCODE_PERCENT, AKEYCODE_PERIOD},
|
|
||||||
{SC_KEYCODE_QUOTE, AKEYCODE_APOSTROPHE},
|
|
||||||
{SC_KEYCODE_ASTERISK, AKEYCODE_STAR},
|
|
||||||
{SC_KEYCODE_PLUS, AKEYCODE_PLUS},
|
|
||||||
{SC_KEYCODE_COMMA, AKEYCODE_COMMA},
|
|
||||||
{SC_KEYCODE_MINUS, AKEYCODE_MINUS},
|
|
||||||
{SC_KEYCODE_PERIOD, AKEYCODE_PERIOD},
|
|
||||||
{SC_KEYCODE_SLASH, AKEYCODE_SLASH},
|
|
||||||
{SC_KEYCODE_0, AKEYCODE_0},
|
|
||||||
{SC_KEYCODE_1, AKEYCODE_1},
|
|
||||||
{SC_KEYCODE_2, AKEYCODE_2},
|
|
||||||
{SC_KEYCODE_3, AKEYCODE_3},
|
|
||||||
{SC_KEYCODE_4, AKEYCODE_4},
|
|
||||||
{SC_KEYCODE_5, AKEYCODE_5},
|
|
||||||
{SC_KEYCODE_6, AKEYCODE_6},
|
|
||||||
{SC_KEYCODE_7, AKEYCODE_7},
|
|
||||||
{SC_KEYCODE_8, AKEYCODE_8},
|
|
||||||
{SC_KEYCODE_9, AKEYCODE_9},
|
|
||||||
{SC_KEYCODE_SEMICOLON, AKEYCODE_SEMICOLON},
|
|
||||||
{SC_KEYCODE_EQUALS, AKEYCODE_EQUALS},
|
|
||||||
{SC_KEYCODE_AT, AKEYCODE_AT},
|
|
||||||
{SC_KEYCODE_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
|
|
||||||
{SC_KEYCODE_BACKSLASH, AKEYCODE_BACKSLASH},
|
|
||||||
{SC_KEYCODE_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
|
|
||||||
{SC_KEYCODE_BACKQUOTE, AKEYCODE_GRAVE},
|
|
||||||
{SC_KEYCODE_KP_1, AKEYCODE_NUMPAD_1},
|
|
||||||
{SC_KEYCODE_KP_2, AKEYCODE_NUMPAD_2},
|
|
||||||
{SC_KEYCODE_KP_3, AKEYCODE_NUMPAD_3},
|
|
||||||
{SC_KEYCODE_KP_4, AKEYCODE_NUMPAD_4},
|
|
||||||
{SC_KEYCODE_KP_5, AKEYCODE_NUMPAD_5},
|
|
||||||
{SC_KEYCODE_KP_6, AKEYCODE_NUMPAD_6},
|
|
||||||
{SC_KEYCODE_KP_7, AKEYCODE_NUMPAD_7},
|
|
||||||
{SC_KEYCODE_KP_8, AKEYCODE_NUMPAD_8},
|
|
||||||
{SC_KEYCODE_KP_9, AKEYCODE_NUMPAD_9},
|
|
||||||
{SC_KEYCODE_KP_0, AKEYCODE_NUMPAD_0},
|
|
||||||
{SC_KEYCODE_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
|
|
||||||
{SC_KEYCODE_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
|
|
||||||
{SC_KEYCODE_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
|
|
||||||
{SC_KEYCODE_KP_PLUS, AKEYCODE_NUMPAD_ADD},
|
|
||||||
{SC_KEYCODE_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
|
|
||||||
{SC_KEYCODE_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
|
|
||||||
{SC_KEYCODE_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
|
|
||||||
{SC_KEYCODE_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
|
|
||||||
};
|
|
||||||
|
|
||||||
const struct sc_intmap_entry *entry =
|
|
||||||
SC_INTMAP_FIND_ENTRY(special_keys, from);
|
|
||||||
if (entry) {
|
|
||||||
*to = entry->value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) {
|
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||||
// Handle Numpad events when Num Lock is disabled
|
// Handle Numpad events when Num Lock is disabled
|
||||||
// If SHIFT is pressed, a text event will be sent instead
|
// If SHIFT is pressed, a text event will be sent instead
|
||||||
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
|
switch(from) {
|
||||||
if (entry) {
|
MAP(SDLK_KP_0, AKEYCODE_INSERT);
|
||||||
*to = entry->value;
|
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
|
||||||
return true;
|
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
|
||||||
|
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
|
||||||
|
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
|
||||||
|
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
|
||||||
|
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
|
||||||
|
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
|
||||||
|
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
|
||||||
|
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT &&
|
if (prefer_text && !(mod & KMOD_CTRL)) {
|
||||||
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
|
|
||||||
// do not forward alpha and space key events (unless Ctrl is pressed)
|
// do not forward alpha and space key events (unless Ctrl is pressed)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) {
|
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ALT and META are not pressed, also handle letters and space
|
// if ALT and META are not pressed, also handle letters and space
|
||||||
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
|
switch (from) {
|
||||||
if (entry) {
|
MAP(SDLK_a, AKEYCODE_A);
|
||||||
*to = entry->value;
|
MAP(SDLK_b, AKEYCODE_B);
|
||||||
return true;
|
MAP(SDLK_c, AKEYCODE_C);
|
||||||
|
MAP(SDLK_d, AKEYCODE_D);
|
||||||
|
MAP(SDLK_e, AKEYCODE_E);
|
||||||
|
MAP(SDLK_f, AKEYCODE_F);
|
||||||
|
MAP(SDLK_g, AKEYCODE_G);
|
||||||
|
MAP(SDLK_h, AKEYCODE_H);
|
||||||
|
MAP(SDLK_i, AKEYCODE_I);
|
||||||
|
MAP(SDLK_j, AKEYCODE_J);
|
||||||
|
MAP(SDLK_k, AKEYCODE_K);
|
||||||
|
MAP(SDLK_l, AKEYCODE_L);
|
||||||
|
MAP(SDLK_m, AKEYCODE_M);
|
||||||
|
MAP(SDLK_n, AKEYCODE_N);
|
||||||
|
MAP(SDLK_o, AKEYCODE_O);
|
||||||
|
MAP(SDLK_p, AKEYCODE_P);
|
||||||
|
MAP(SDLK_q, AKEYCODE_Q);
|
||||||
|
MAP(SDLK_r, AKEYCODE_R);
|
||||||
|
MAP(SDLK_s, AKEYCODE_S);
|
||||||
|
MAP(SDLK_t, AKEYCODE_T);
|
||||||
|
MAP(SDLK_u, AKEYCODE_U);
|
||||||
|
MAP(SDLK_v, AKEYCODE_V);
|
||||||
|
MAP(SDLK_w, AKEYCODE_W);
|
||||||
|
MAP(SDLK_x, AKEYCODE_X);
|
||||||
|
MAP(SDLK_y, AKEYCODE_Y);
|
||||||
|
MAP(SDLK_z, AKEYCODE_Z);
|
||||||
|
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
||||||
|
FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
|
||||||
entry = SC_INTMAP_FIND_ENTRY(numbers_punct_keys, from);
|
|
||||||
if (entry) {
|
|
||||||
*to = entry->value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum android_metastate
|
static enum android_metastate
|
||||||
@@ -208,69 +125,70 @@ autocomplete_metastate(enum android_metastate metastate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static enum android_metastate
|
static enum android_metastate
|
||||||
convert_meta_state(uint16_t mod) {
|
convert_meta_state(SDL_Keymod mod) {
|
||||||
enum android_metastate metastate = 0;
|
enum android_metastate metastate = 0;
|
||||||
if (mod & SC_MOD_LSHIFT) {
|
if (mod & KMOD_LSHIFT) {
|
||||||
metastate |= AMETA_SHIFT_LEFT_ON;
|
metastate |= AMETA_SHIFT_LEFT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_RSHIFT) {
|
if (mod & KMOD_RSHIFT) {
|
||||||
metastate |= AMETA_SHIFT_RIGHT_ON;
|
metastate |= AMETA_SHIFT_RIGHT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_LCTRL) {
|
if (mod & KMOD_LCTRL) {
|
||||||
metastate |= AMETA_CTRL_LEFT_ON;
|
metastate |= AMETA_CTRL_LEFT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_RCTRL) {
|
if (mod & KMOD_RCTRL) {
|
||||||
metastate |= AMETA_CTRL_RIGHT_ON;
|
metastate |= AMETA_CTRL_RIGHT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_LALT) {
|
if (mod & KMOD_LALT) {
|
||||||
metastate |= AMETA_ALT_LEFT_ON;
|
metastate |= AMETA_ALT_LEFT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_RALT) {
|
if (mod & KMOD_RALT) {
|
||||||
metastate |= AMETA_ALT_RIGHT_ON;
|
metastate |= AMETA_ALT_RIGHT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_LGUI) { // Windows key
|
if (mod & KMOD_LGUI) { // Windows key
|
||||||
metastate |= AMETA_META_LEFT_ON;
|
metastate |= AMETA_META_LEFT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_RGUI) { // Windows key
|
if (mod & KMOD_RGUI) { // Windows key
|
||||||
metastate |= AMETA_META_RIGHT_ON;
|
metastate |= AMETA_META_RIGHT_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_NUM) {
|
if (mod & KMOD_NUM) {
|
||||||
metastate |= AMETA_NUM_LOCK_ON;
|
metastate |= AMETA_NUM_LOCK_ON;
|
||||||
}
|
}
|
||||||
if (mod & SC_MOD_CAPS) {
|
if (mod & KMOD_CAPS) {
|
||||||
metastate |= AMETA_CAPS_LOCK_ON;
|
metastate |= AMETA_CAPS_LOCK_ON;
|
||||||
}
|
}
|
||||||
|
if (mod & KMOD_MODE) { // Alt Gr
|
||||||
|
// no mapping?
|
||||||
|
}
|
||||||
|
|
||||||
// fill the dependent fields
|
// fill the dependent fields
|
||||||
return autocomplete_metastate(metastate);
|
return autocomplete_metastate(metastate);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
|
bool prefer_text, uint32_t repeat) {
|
||||||
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||||
|
|
||||||
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
|
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||||
event->mods_state, key_inject_mode)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg->inject_keycode.action = convert_keycode_action(event->action);
|
uint16_t mod = from->keysym.mod;
|
||||||
msg->inject_keycode.repeat = repeat;
|
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||||
msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
|
prefer_text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to->inject_keycode.repeat = repeat;
|
||||||
|
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
const struct sc_key_event *event,
|
const SDL_KeyboardEvent *event) {
|
||||||
uint64_t ack_to_wait) {
|
|
||||||
// The device clipboard synchronization and the key event messages are
|
|
||||||
// serialized, there is nothing special to do to ensure that the clipboard
|
|
||||||
// is set before injecting Ctrl+v.
|
|
||||||
(void) ack_to_wait;
|
|
||||||
|
|
||||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||||
|
|
||||||
if (event->repeat) {
|
if (event->repeat) {
|
||||||
@@ -282,9 +200,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
ki->repeat = 0;
|
ki->repeat = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
|
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
|
||||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
if (!controller_push_msg(ki->controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,31 +210,26 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_key_processor_process_text(struct sc_key_processor *kp,
|
sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||||
const struct sc_text_event *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||||
|
|
||||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
if (!ki->prefer_text) {
|
||||||
// Never inject text events
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
|
|
||||||
char c = event->text[0];
|
char c = event->text[0];
|
||||||
if (isalpha(c) || c == ' ') {
|
if (isalpha(c) || c == ' ') {
|
||||||
assert(event->text[1] == '\0');
|
assert(event->text[1] == '\0');
|
||||||
// Letters and space are handled as raw key events
|
// letters and space are handled as raw key event
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = strdup(event->text);
|
msg.inject_text.text = strdup(event->text);
|
||||||
if (!msg.inject_text.text) {
|
if (!msg.inject_text.text) {
|
||||||
LOGW("Could not strdup input text");
|
LOGW("Could not strdup input text");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
if (!controller_push_msg(ki->controller, &msg)) {
|
||||||
free(msg.inject_text.text);
|
free(msg.inject_text.text);
|
||||||
LOGW("Could not request 'inject text'");
|
LOGW("Could not request 'inject text'");
|
||||||
}
|
}
|
||||||
@@ -324,12 +237,11 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||||
struct sc_controller *controller,
|
struct controller *controller,
|
||||||
enum sc_key_inject_mode key_inject_mode,
|
const struct scrcpy_options *options) {
|
||||||
bool forward_key_repeat) {
|
|
||||||
ki->controller = controller;
|
ki->controller = controller;
|
||||||
ki->key_inject_mode = key_inject_mode;
|
ki->prefer_text = options->prefer_text;
|
||||||
ki->forward_key_repeat = forward_key_repeat;
|
ki->forward_key_repeat = options->forward_key_repeat;
|
||||||
|
|
||||||
ki->repeat = 0;
|
ki->repeat = 0;
|
||||||
|
|
||||||
@@ -338,7 +250,5 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
|||||||
.process_text = sc_key_processor_process_text,
|
.process_text = sc_key_processor_process_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Key injection and clipboard synchronization are serialized
|
|
||||||
ki->key_processor.async_paste = false;
|
|
||||||
ki->key_processor.ops = &ops;
|
ki->key_processor.ops = &ops;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,25 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
struct sc_keyboard_inject {
|
struct sc_keyboard_inject {
|
||||||
struct sc_key_processor key_processor; // key processor trait
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
|
||||||
struct sc_controller *controller;
|
struct controller *controller;
|
||||||
|
|
||||||
// SDL reports repeated events as a boolean, but Android expects the actual
|
// SDL reports repeated events as a boolean, but Android expects the actual
|
||||||
// number of repetitions. This variable keeps track of the count.
|
// number of repetitions. This variable keeps track of the count.
|
||||||
unsigned repeat;
|
unsigned repeat;
|
||||||
|
|
||||||
enum sc_key_inject_mode key_inject_mode;
|
bool prefer_text;
|
||||||
bool forward_key_repeat;
|
bool forward_key_repeat;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||||
struct sc_controller *controller,
|
struct controller *controller,
|
||||||
enum sc_key_inject_mode key_inject_mode,
|
const struct scrcpy_options *options);
|
||||||
bool forward_key_repeat);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
112
app/src/main.c
112
app/src/main.c
@@ -1,13 +1,11 @@
|
|||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include "util/str.h"
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
# include <libavdevice/avdevice.h>
|
# include <libavdevice/avdevice.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -15,26 +13,42 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "options.h"
|
|
||||||
#include "scrcpy.h"
|
|
||||||
#include "usb/scrcpy_otg.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "version.h"
|
|
||||||
|
static void
|
||||||
|
print_version(void) {
|
||||||
|
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
|
||||||
|
|
||||||
|
fprintf(stderr, "dependencies:\n");
|
||||||
|
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
|
||||||
|
SDL_PATCHLEVEL);
|
||||||
|
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
|
||||||
|
LIBAVCODEC_VERSION_MINOR,
|
||||||
|
LIBAVCODEC_VERSION_MICRO);
|
||||||
|
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
|
||||||
|
LIBAVFORMAT_VERSION_MINOR,
|
||||||
|
LIBAVFORMAT_VERSION_MICRO);
|
||||||
|
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
||||||
|
LIBAVUTIL_VERSION_MINOR,
|
||||||
|
LIBAVUTIL_VERSION_MICRO);
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
|
||||||
|
LIBAVDEVICE_VERSION_MINOR,
|
||||||
|
LIBAVDEVICE_VERSION_MICRO);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main_scrcpy(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
#ifdef _WIN32
|
#ifdef __WINDOWS__
|
||||||
// disable buffering, we want logs immediately
|
// disable buffering, we want logs immediately
|
||||||
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
|
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
|
||||||
setbuf(stdout, NULL);
|
setbuf(stdout, NULL);
|
||||||
setbuf(stderr, NULL);
|
setbuf(stderr, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
printf("scrcpy " SCRCPY_VERSION
|
|
||||||
" <https://github.com/Genymobile/scrcpy>\n");
|
|
||||||
|
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
@@ -44,21 +58,23 @@ main_scrcpy(int argc, char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!scrcpy_parse_args(&args, argc, argv)) {
|
if (!scrcpy_parse_args(&args, argc, argv)) {
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_set_log_level(args.opts.log_level);
|
sc_set_log_level(args.opts.log_level);
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
scrcpy_print_usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.version) {
|
if (args.version) {
|
||||||
scrcpy_print_version();
|
print_version();
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||||
av_register_all();
|
av_register_all();
|
||||||
#endif
|
#endif
|
||||||
@@ -70,66 +86,12 @@ main_scrcpy(int argc, char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (avformat_network_init()) {
|
if (avformat_network_init()) {
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_USB
|
int res = scrcpy(&args.opts) ? 0 : 1;
|
||||||
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
|
|
||||||
: scrcpy(&args.opts);
|
|
||||||
#else
|
|
||||||
enum scrcpy_exit_code ret = scrcpy(&args.opts);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
avformat_network_deinit(); // ignore failure
|
avformat_network_deinit(); // ignore failure
|
||||||
|
|
||||||
return ret;
|
return res;
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char *argv[]) {
|
|
||||||
#ifndef _WIN32
|
|
||||||
return main_scrcpy(argc, argv);
|
|
||||||
#else
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
int wargc;
|
|
||||||
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
|
||||||
if (!wargv) {
|
|
||||||
LOG_OOM();
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
|
|
||||||
if (!argv_utf8) {
|
|
||||||
LOG_OOM();
|
|
||||||
LocalFree(wargv);
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
argv_utf8[wargc] = NULL;
|
|
||||||
|
|
||||||
for (int i = 0; i < wargc; ++i) {
|
|
||||||
argv_utf8[i] = sc_str_from_wchars(wargv[i]);
|
|
||||||
if (!argv_utf8[i]) {
|
|
||||||
LOG_OOM();
|
|
||||||
for (int j = 0; j < i; ++j) {
|
|
||||||
free(argv_utf8[j]);
|
|
||||||
}
|
|
||||||
LocalFree(wargv);
|
|
||||||
free(argv_utf8);
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalFree(wargv);
|
|
||||||
|
|
||||||
int ret = main_scrcpy(wargc, argv_utf8);
|
|
||||||
|
|
||||||
for (int i = 0; i < wargc; ++i) {
|
|
||||||
free(argv_utf8[i]);
|
|
||||||
}
|
|
||||||
free(argv_utf8);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#include "mouse_inject.h"
|
#include "mouse_inject.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
#include "android/input.h"
|
#include "android/input.h"
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "input_events.h"
|
|
||||||
#include "util/intmap.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast mouse processor to sc_mouse_inject */
|
/** Downcast mouse processor to sc_mouse_inject */
|
||||||
@@ -15,147 +14,198 @@
|
|||||||
static enum android_motionevent_buttons
|
static enum android_motionevent_buttons
|
||||||
convert_mouse_buttons(uint32_t state) {
|
convert_mouse_buttons(uint32_t state) {
|
||||||
enum android_motionevent_buttons buttons = 0;
|
enum android_motionevent_buttons buttons = 0;
|
||||||
if (state & SC_MOUSE_BUTTON_LEFT) {
|
if (state & SDL_BUTTON_LMASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
|
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
|
||||||
}
|
}
|
||||||
if (state & SC_MOUSE_BUTTON_RIGHT) {
|
if (state & SDL_BUTTON_RMASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
|
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
|
||||||
}
|
}
|
||||||
if (state & SC_MOUSE_BUTTON_MIDDLE) {
|
if (state & SDL_BUTTON_MMASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
||||||
}
|
}
|
||||||
if (state & SC_MOUSE_BUTTON_X1) {
|
if (state & SDL_BUTTON_X1MASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
||||||
}
|
}
|
||||||
if (state & SC_MOUSE_BUTTON_X2) {
|
if (state & SDL_BUTTON_X2MASK) {
|
||||||
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
||||||
}
|
}
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum android_motionevent_action
|
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||||
convert_mouse_action(enum sc_action action) {
|
#define FAIL default: return false
|
||||||
if (action == SC_ACTION_DOWN) {
|
static bool
|
||||||
return AMOTION_EVENT_ACTION_DOWN;
|
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||||
|
switch (from) {
|
||||||
|
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||||
|
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
|
||||||
|
FAIL;
|
||||||
}
|
}
|
||||||
assert(action == SC_ACTION_UP);
|
|
||||||
return AMOTION_EVENT_ACTION_UP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum android_motionevent_action
|
static bool
|
||||||
convert_touch_action(enum sc_touch_action action) {
|
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||||
switch (action) {
|
switch (from) {
|
||||||
case SC_TOUCH_ACTION_MOVE:
|
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
|
||||||
return AMOTION_EVENT_ACTION_MOVE;
|
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||||
case SC_TOUCH_ACTION_DOWN:
|
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
|
||||||
return AMOTION_EVENT_ACTION_DOWN;
|
FAIL;
|
||||||
default:
|
|
||||||
assert(action == SC_TOUCH_ACTION_UP);
|
|
||||||
return AMOTION_EVENT_ACTION_UP;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
|
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||||
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
to->inject_touch_event.position.point =
|
||||||
|
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||||
|
to->inject_touch_event.pressure = 1.f;
|
||||||
|
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
|
|
||||||
|
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to->inject_touch_event.pointer_id = from->fingerId;
|
||||||
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
|
||||||
|
int dw;
|
||||||
|
int dh;
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
|
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||||
|
int32_t x = from->x * dw;
|
||||||
|
int32_t y = from->y * dh;
|
||||||
|
to->inject_touch_event.position.point =
|
||||||
|
screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||||
|
|
||||||
|
to->inject_touch_event.pressure = from->pressure;
|
||||||
|
to->inject_touch_event.buttons = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
|
||||||
|
|
||||||
|
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
to->inject_touch_event.position.point =
|
||||||
|
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||||
|
to->inject_touch_event.pressure =
|
||||||
|
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
|
||||||
|
to->inject_touch_event.buttons =
|
||||||
|
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||||
|
struct control_msg *to) {
|
||||||
|
|
||||||
|
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||||
|
int mouse_x;
|
||||||
|
int mouse_y;
|
||||||
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
|
struct position position = {
|
||||||
|
.screen_size = screen->frame_size,
|
||||||
|
.point = screen_convert_window_to_frame_coords(screen,
|
||||||
|
mouse_x, mouse_y),
|
||||||
|
};
|
||||||
|
|
||||||
|
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
|
||||||
|
to->inject_scroll_event.position = position;
|
||||||
|
to->inject_scroll_event.hscroll = from->x;
|
||||||
|
to->inject_scroll_event.vscroll = from->y;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
const struct sc_mouse_motion_event *event) {
|
const SDL_MouseMotionEvent *event) {
|
||||||
if (!event->buttons_state) {
|
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||||
// Do not send motion events when no click is pressed
|
|
||||||
|
struct control_msg msg;
|
||||||
|
if (!convert_mouse_motion(event, mi->screen, &msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
if (!controller_push_msg(mi->controller, &msg)) {
|
||||||
|
|
||||||
struct sc_control_msg msg = {
|
|
||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
|
||||||
.inject_touch_event = {
|
|
||||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
|
||||||
.pointer_id = POINTER_ID_MOUSE,
|
|
||||||
.position = event->position,
|
|
||||||
.pressure = 1.f,
|
|
||||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
|
||||||
LOGW("Could not request 'inject mouse motion event'");
|
LOGW("Could not request 'inject mouse motion event'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|
||||||
const struct sc_mouse_click_event *event) {
|
|
||||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_control_msg msg = {
|
|
||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
|
||||||
.inject_touch_event = {
|
|
||||||
.action = convert_mouse_action(event->action),
|
|
||||||
.pointer_id = POINTER_ID_MOUSE,
|
|
||||||
.position = event->position,
|
|
||||||
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
|
|
||||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
|
||||||
LOGW("Could not request 'inject mouse click event'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|
||||||
const struct sc_mouse_scroll_event *event) {
|
|
||||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_control_msg msg = {
|
|
||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
|
||||||
.inject_scroll_event = {
|
|
||||||
.position = event->position,
|
|
||||||
.hscroll = event->hscroll,
|
|
||||||
.vscroll = event->vscroll,
|
|
||||||
.buttons = convert_mouse_buttons(event->buttons_state),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
|
||||||
LOGW("Could not request 'inject mouse scroll event'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||||
const struct sc_touch_event *event) {
|
const SDL_TouchFingerEvent *event) {
|
||||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_control_msg msg = {
|
struct control_msg msg;
|
||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
if (convert_touch(event, mi->screen, &msg)) {
|
||||||
.inject_touch_event = {
|
if (!controller_push_msg(mi->controller, &msg)) {
|
||||||
.action = convert_touch_action(event->action),
|
LOGW("Could not request 'inject touch event'");
|
||||||
.pointer_id = event->pointer_id,
|
}
|
||||||
.position = event->position,
|
}
|
||||||
.pressure = event->pressure,
|
}
|
||||||
.buttons = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
static void
|
||||||
LOGW("Could not request 'inject touch event'");
|
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
|
||||||
|
const SDL_MouseButtonEvent *event) {
|
||||||
|
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct control_msg msg;
|
||||||
|
if (convert_mouse_button(event, mi->screen, &msg)) {
|
||||||
|
if (!controller_push_msg(mi->controller, &msg)) {
|
||||||
|
LOGW("Could not request 'inject mouse button event'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
|
||||||
|
const SDL_MouseWheelEvent *event) {
|
||||||
|
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct control_msg msg;
|
||||||
|
if (convert_mouse_wheel(event, mi->screen, &msg)) {
|
||||||
|
if (!controller_push_msg(mi->controller, &msg)) {
|
||||||
|
LOGW("Could not request 'inject mouse wheel event'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
|
||||||
struct sc_controller *controller) {
|
struct screen *screen) {
|
||||||
mi->controller = controller;
|
mi->controller = controller;
|
||||||
|
mi->screen = screen;
|
||||||
|
|
||||||
static const struct sc_mouse_processor_ops ops = {
|
static const struct sc_mouse_processor_ops ops = {
|
||||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||||
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
|
||||||
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
|
||||||
.process_touch = sc_mouse_processor_process_touch,
|
.process_touch = sc_mouse_processor_process_touch,
|
||||||
|
.process_mouse_button = sc_mouse_processor_process_mouse_button,
|
||||||
|
.process_mouse_wheel = sc_mouse_processor_process_mouse_wheel,
|
||||||
};
|
};
|
||||||
|
|
||||||
mi->mouse_processor.ops = &ops;
|
mi->mouse_processor.ops = &ops;
|
||||||
|
|
||||||
mi->mouse_processor.relative_mode = false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,19 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
struct sc_mouse_inject {
|
struct sc_mouse_inject {
|
||||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||||
|
|
||||||
struct sc_controller *controller;
|
struct controller *controller;
|
||||||
|
struct screen *screen;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
|
||||||
struct sc_controller *controller);
|
struct screen *screen);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ sc_opengl_init(struct sc_opengl *gl) {
|
|||||||
sizeof(OPENGL_ES_PREFIX) - 1);
|
sizeof(OPENGL_ES_PREFIX) - 1);
|
||||||
if (gl->is_opengles) {
|
if (gl->is_opengles) {
|
||||||
/* skip the prefix */
|
/* skip the prefix */
|
||||||
version += sizeof(OPENGL_ES_PREFIX) - 1;
|
version += sizeof(PREFIX) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
|
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
#include "options.h"
|
|
||||||
|
|
||||||
const struct scrcpy_options scrcpy_options_default = {
|
|
||||||
.serial = NULL,
|
|
||||||
.crop = NULL,
|
|
||||||
.record_filename = NULL,
|
|
||||||
.window_title = NULL,
|
|
||||||
.push_target = NULL,
|
|
||||||
.render_driver = NULL,
|
|
||||||
.codec_options = NULL,
|
|
||||||
.encoder_name = NULL,
|
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
.v4l2_device = NULL,
|
|
||||||
#endif
|
|
||||||
.log_level = SC_LOG_LEVEL_INFO,
|
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
|
||||||
.port_range = {
|
|
||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
|
||||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
|
||||||
},
|
|
||||||
.tunnel_host = 0,
|
|
||||||
.tunnel_port = 0,
|
|
||||||
.shortcut_mods = {
|
|
||||||
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
|
|
||||||
.count = 2,
|
|
||||||
},
|
|
||||||
.max_size = 0,
|
|
||||||
.bit_rate = DEFAULT_BIT_RATE,
|
|
||||||
.max_fps = 0,
|
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
|
||||||
.rotation = 0,
|
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED,
|
|
||||||
.window_width = 0,
|
|
||||||
.window_height = 0,
|
|
||||||
.display_id = 0,
|
|
||||||
.display_buffer = 0,
|
|
||||||
.v4l2_buffer = 0,
|
|
||||||
#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,
|
|
||||||
.window_borderless = false,
|
|
||||||
.mipmaps = true,
|
|
||||||
.stay_awake = false,
|
|
||||||
.force_adb_forward = false,
|
|
||||||
.disable_screensaver = false,
|
|
||||||
.forward_key_repeat = true,
|
|
||||||
.forward_all_clicks = false,
|
|
||||||
.legacy_paste = false,
|
|
||||||
.power_off_on_close = false,
|
|
||||||
.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,
|
|
||||||
};
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
#ifndef SCRCPY_OPTIONS_H
|
|
||||||
#define SCRCPY_OPTIONS_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "util/tick.h"
|
|
||||||
|
|
||||||
enum sc_log_level {
|
|
||||||
SC_LOG_LEVEL_VERBOSE,
|
|
||||||
SC_LOG_LEVEL_DEBUG,
|
|
||||||
SC_LOG_LEVEL_INFO,
|
|
||||||
SC_LOG_LEVEL_WARN,
|
|
||||||
SC_LOG_LEVEL_ERROR,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_record_format {
|
|
||||||
SC_RECORD_FORMAT_AUTO,
|
|
||||||
SC_RECORD_FORMAT_MP4,
|
|
||||||
SC_RECORD_FORMAT_MKV,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_lock_video_orientation {
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
|
||||||
// lock the current orientation when scrcpy starts
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_1,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_3,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_keyboard_input_mode {
|
|
||||||
SC_KEYBOARD_INPUT_MODE_INJECT,
|
|
||||||
SC_KEYBOARD_INPUT_MODE_HID,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_shortcut_mods {
|
|
||||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
|
||||||
unsigned count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_port_range {
|
|
||||||
uint16_t first;
|
|
||||||
uint16_t last;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
|
||||||
|
|
||||||
struct scrcpy_options {
|
|
||||||
const char *serial;
|
|
||||||
const char *crop;
|
|
||||||
const char *record_filename;
|
|
||||||
const char *window_title;
|
|
||||||
const char *push_target;
|
|
||||||
const char *render_driver;
|
|
||||||
const char *codec_options;
|
|
||||||
const char *encoder_name;
|
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
const char *v4l2_device;
|
|
||||||
#endif
|
|
||||||
enum sc_log_level log_level;
|
|
||||||
enum sc_record_format record_format;
|
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
|
||||||
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;
|
|
||||||
uint16_t max_fps;
|
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
|
||||||
uint8_t rotation;
|
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
|
||||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
|
||||||
uint16_t window_width;
|
|
||||||
uint16_t window_height;
|
|
||||||
uint32_t display_id;
|
|
||||||
sc_tick display_buffer;
|
|
||||||
sc_tick v4l2_buffer;
|
|
||||||
#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 window_borderless;
|
|
||||||
bool mipmaps;
|
|
||||||
bool stay_awake;
|
|
||||||
bool force_adb_forward;
|
|
||||||
bool disable_screensaver;
|
|
||||||
bool forward_key_repeat;
|
|
||||||
bool forward_all_clicks;
|
|
||||||
bool legacy_paste;
|
|
||||||
bool power_off_on_close;
|
|
||||||
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;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -7,16 +7,12 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
||||||
struct sc_acksync *acksync) {
|
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver->control_socket = control_socket;
|
receiver->control_socket = control_socket;
|
||||||
receiver->acksync = acksync;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +22,7 @@ receiver_destroy(struct receiver *receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
process_msg(struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
char *current = SDL_GetClipboardText();
|
char *current = SDL_GetClipboardText();
|
||||||
@@ -41,17 +37,11 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
|
|||||||
SDL_SetClipboardText(msg->clipboard.text);
|
SDL_SetClipboardText(msg->clipboard.text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
|
||||||
assert(receiver->acksync);
|
|
||||||
LOGD("Ack device clipboard sequence=%" PRIu64_,
|
|
||||||
msg->ack_clipboard.sequence);
|
|
||||||
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
process_msgs(const unsigned char *buf, size_t len) {
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
@@ -63,7 +53,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
|||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_msg(receiver, &msg);
|
process_msg(&msg);
|
||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
@@ -91,7 +81,7 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
ssize_t consumed = process_msgs(buf, head);
|
||||||
if (consumed == -1) {
|
if (consumed == -1) {
|
||||||
// an error occurred
|
// an error occurred
|
||||||
break;
|
break;
|
||||||
@@ -111,10 +101,10 @@ bool
|
|||||||
receiver_start(struct receiver *receiver) {
|
receiver_start(struct receiver *receiver) {
|
||||||
LOGD("Starting receiver thread");
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
|
||||||
"scrcpy-receiver", receiver);
|
receiver);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start receiver thread");
|
LOGC("Could not start receiver thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
#ifndef SC_RECEIVER_H
|
#ifndef RECEIVER_H
|
||||||
#define SC_RECEIVER_H
|
#define RECEIVER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct receiver {
|
struct receiver {
|
||||||
sc_socket control_socket;
|
socket_t control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
|
|
||||||
struct sc_acksync *acksync;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, socket_t control_socket);
|
||||||
struct sc_acksync *acksync);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver);
|
receiver_destroy(struct receiver *receiver);
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to recorder */
|
/** Downcast packet_sink to recorder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
@@ -26,21 +26,19 @@ find_muxer(const char *name) {
|
|||||||
oformat = av_oformat_next(oformat);
|
oformat = av_oformat_next(oformat);
|
||||||
#endif
|
#endif
|
||||||
// until null or containing the requested name
|
// until null or containing the requested name
|
||||||
} while (oformat && !sc_str_list_contains(oformat->name, ',', name));
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_record_packet *
|
static struct record_packet *
|
||||||
sc_record_packet_new(const AVPacket *packet) {
|
record_packet_new(const AVPacket *packet) {
|
||||||
struct sc_record_packet *rec = malloc(sizeof(*rec));
|
struct record_packet *rec = malloc(sizeof(*rec));
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
rec->packet = av_packet_alloc();
|
rec->packet = av_packet_alloc();
|
||||||
if (!rec->packet) {
|
if (!rec->packet) {
|
||||||
LOG_OOM();
|
|
||||||
free(rec);
|
free(rec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -54,22 +52,22 @@ sc_record_packet_new(const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_record_packet_delete(struct sc_record_packet *rec) {
|
record_packet_delete(struct record_packet *rec) {
|
||||||
av_packet_free(&rec->packet);
|
av_packet_free(&rec->packet);
|
||||||
free(rec);
|
free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
|
recorder_queue_clear(struct recorder_queue *queue) {
|
||||||
while (!sc_queue_is_empty(queue)) {
|
while (!sc_queue_is_empty(queue)) {
|
||||||
struct sc_record_packet *rec;
|
struct record_packet *rec;
|
||||||
sc_queue_take(queue, next, &rec);
|
sc_queue_take(queue, next, &rec);
|
||||||
sc_record_packet_delete(rec);
|
record_packet_delete(rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
sc_recorder_get_format_name(enum sc_record_format format) {
|
recorder_get_format_name(enum sc_record_format format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case SC_RECORD_FORMAT_MP4: return "mp4";
|
case SC_RECORD_FORMAT_MP4: return "mp4";
|
||||||
case SC_RECORD_FORMAT_MKV: return "matroska";
|
case SC_RECORD_FORMAT_MKV: return "matroska";
|
||||||
@@ -78,12 +76,12 @@ sc_recorder_get_format_name(enum sc_record_format format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
|
|
||||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
if (!extradata) {
|
if (!extradata) {
|
||||||
LOG_OOM();
|
LOGC("Could not allocate extradata");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,19 +101,19 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) {
|
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
recorder_write(struct recorder *recorder, AVPacket *packet) {
|
||||||
if (!recorder->header_written) {
|
if (!recorder->header_written) {
|
||||||
if (packet->pts != AV_NOPTS_VALUE) {
|
if (packet->pts != AV_NOPTS_VALUE) {
|
||||||
LOGE("The first packet is not a config packet");
|
LOGE("The first packet is not a config packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool ok = sc_recorder_write_header(recorder, packet);
|
bool ok = recorder_write_header(recorder, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -128,13 +126,13 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_recorder_rescale_packet(recorder, packet);
|
recorder_rescale_packet(recorder, packet);
|
||||||
return av_write_frame(recorder->ctx, packet) >= 0;
|
return av_write_frame(recorder->ctx, packet) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_recorder(void *data) {
|
run_recorder(void *data) {
|
||||||
struct sc_recorder *recorder = data;
|
struct recorder *recorder = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
@@ -148,29 +146,29 @@ run_recorder(void *data) {
|
|||||||
|
|
||||||
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
struct sc_record_packet *last = recorder->previous;
|
struct record_packet *last = recorder->previous;
|
||||||
if (last) {
|
if (last) {
|
||||||
// assign an arbitrary duration to the last packet
|
// assign an arbitrary duration to the last packet
|
||||||
last->packet->duration = 100000;
|
last->packet->duration = 100000;
|
||||||
bool ok = sc_recorder_write(recorder, last->packet);
|
bool ok = recorder_write(recorder, last->packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// failing to write the last frame is not very serious, no
|
// failing to write the last frame is not very serious, no
|
||||||
// future frame may depend on it, so the resulting file
|
// future frame may depend on it, so the resulting file
|
||||||
// will still be valid
|
// will still be valid
|
||||||
LOGW("Could not record last packet");
|
LOGW("Could not record last packet");
|
||||||
}
|
}
|
||||||
sc_record_packet_delete(last);
|
record_packet_delete(last);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_record_packet *rec;
|
struct record_packet *rec;
|
||||||
sc_queue_take(&recorder->queue, next, &rec);
|
sc_queue_take(&recorder->queue, next, &rec);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
// recorder->previous is only written from this thread, no need to lock
|
// recorder->previous is only written from this thread, no need to lock
|
||||||
struct sc_record_packet *previous = recorder->previous;
|
struct record_packet *previous = recorder->previous;
|
||||||
recorder->previous = rec;
|
recorder->previous = rec;
|
||||||
|
|
||||||
if (!previous) {
|
if (!previous) {
|
||||||
@@ -186,15 +184,15 @@ run_recorder(void *data) {
|
|||||||
rec->packet->pts - previous->packet->pts;
|
rec->packet->pts - previous->packet->pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_recorder_write(recorder, previous->packet);
|
bool ok = recorder_write(recorder, previous->packet);
|
||||||
sc_record_packet_delete(previous);
|
record_packet_delete(previous);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record packet");
|
LOGE("Could not record packet");
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->failed = true;
|
recorder->failed = true;
|
||||||
// discard pending packets
|
// discard pending packets
|
||||||
sc_recorder_queue_clear(&recorder->queue);
|
recorder_queue_clear(&recorder->queue);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -216,7 +214,7 @@ run_recorder(void *data) {
|
|||||||
if (recorder->failed) {
|
if (recorder->failed) {
|
||||||
LOGE("Recording failed to %s", recorder->filename);
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
} else {
|
} else {
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
LOGI("Recording complete to %s file: %s", format_name,
|
LOGI("Recording complete to %s file: %s", format_name,
|
||||||
recorder->filename);
|
recorder->filename);
|
||||||
}
|
}
|
||||||
@@ -227,14 +225,16 @@ run_recorder(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||||
bool ok = sc_mutex_init(&recorder->mutex);
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
LOGC("Could not create mutex");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->queue_cond);
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
LOGC("Could not create cond");
|
||||||
goto error_mutex_destroy;
|
goto error_mutex_destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
|||||||
recorder->header_written = false;
|
recorder->header_written = false;
|
||||||
recorder->previous = NULL;
|
recorder->previous = NULL;
|
||||||
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
assert(format_name);
|
assert(format_name);
|
||||||
const AVOutputFormat *format = find_muxer(format_name);
|
const AVOutputFormat *format = find_muxer(format_name);
|
||||||
if (!format) {
|
if (!format) {
|
||||||
@@ -254,7 +254,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
|||||||
|
|
||||||
recorder->ctx = avformat_alloc_context();
|
recorder->ctx = avformat_alloc_context();
|
||||||
if (!recorder->ctx) {
|
if (!recorder->ctx) {
|
||||||
LOG_OOM();
|
LOGE("Could not allocate output context");
|
||||||
goto error_cond_destroy;
|
goto error_cond_destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,10 +287,10 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Starting recorder thread");
|
LOGD("Starting recorder thread");
|
||||||
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
|
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
||||||
recorder);
|
recorder);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not start recorder thread");
|
LOGC("Could not start recorder thread");
|
||||||
goto error_avio_close;
|
goto error_avio_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ error_mutex_destroy:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_close(struct sc_recorder *recorder) {
|
recorder_close(struct recorder *recorder) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
@@ -326,7 +326,7 @@ sc_recorder_close(struct sc_recorder *recorder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
assert(!recorder->stopped);
|
assert(!recorder->stopped);
|
||||||
|
|
||||||
@@ -336,9 +336,9 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
struct record_packet *rec = record_packet_new(packet);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOG_OOM();
|
LOGC("Could not allocate record packet");
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -351,33 +351,31 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
|
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
const AVCodec *codec) {
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
return recorder_open(recorder, codec);
|
||||||
return sc_recorder_open(recorder, codec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
sc_recorder_close(recorder);
|
recorder_close(recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
|
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
const AVPacket *packet) {
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
struct sc_recorder *recorder = DOWNCAST(sink);
|
return recorder_push(recorder, packet);
|
||||||
return sc_recorder_push(recorder, packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder,
|
recorder_init(struct recorder *recorder,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format,
|
||||||
struct sc_size declared_frame_size) {
|
struct size declared_frame_size) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOG_OOM();
|
LOGE("Could not strdup filename");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,9 +383,9 @@ sc_recorder_init(struct sc_recorder *recorder,
|
|||||||
recorder->declared_frame_size = declared_frame_size;
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
.open = sc_recorder_packet_sink_open,
|
.open = recorder_packet_sink_open,
|
||||||
.close = sc_recorder_packet_sink_close,
|
.close = recorder_packet_sink_close,
|
||||||
.push = sc_recorder_packet_sink_push,
|
.push = recorder_packet_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
recorder->packet_sink.ops = &ops;
|
recorder->packet_sink.ops = &ops;
|
||||||
@@ -396,6 +394,6 @@ sc_recorder_init(struct sc_recorder *recorder,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
recorder_destroy(struct recorder *recorder) {
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef SC_RECORDER_H
|
#ifndef RECORDER_H
|
||||||
#define SC_RECORDER_H
|
#define RECORDER_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
@@ -7,25 +7,25 @@
|
|||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "trait/packet_sink.h"
|
#include "trait/packet_sink.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct sc_record_packet {
|
struct record_packet {
|
||||||
AVPacket *packet;
|
AVPacket *packet;
|
||||||
struct sc_record_packet *next;
|
struct record_packet *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
struct recorder_queue SC_QUEUE(struct record_packet);
|
||||||
|
|
||||||
struct sc_recorder {
|
struct recorder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
@@ -33,21 +33,20 @@ struct sc_recorder {
|
|||||||
sc_cond queue_cond;
|
sc_cond queue_cond;
|
||||||
bool stopped; // set on recorder_close()
|
bool stopped; // set on recorder_close()
|
||||||
bool failed; // set on packet write failure
|
bool failed; // set on packet write failure
|
||||||
struct sc_recorder_queue queue;
|
struct recorder_queue queue;
|
||||||
|
|
||||||
// we can write a packet only once we received the next one so that we can
|
// we can write a packet only once we received the next one so that we can
|
||||||
// set its duration (next_pts - current_pts)
|
// set its duration (next_pts - current_pts)
|
||||||
// "previous" is only accessed from the recorder thread, so it does not
|
// "previous" is only accessed from the recorder thread, so it does not
|
||||||
// need to be protected by the mutex
|
// need to be protected by the mutex
|
||||||
struct sc_record_packet *previous;
|
struct record_packet *previous;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
recorder_init(struct recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format, struct size declared_frame_size);
|
||||||
struct sc_size declared_frame_size);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
319
app/src/rtp.c
319
app/src/rtp.c
@@ -1,319 +0,0 @@
|
|||||||
#include "rtp.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
#include <libavutil/time.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
/** Downcast packet_sink to rtp */
|
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_rtp, packet_sink)
|
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
|
||||||
|
|
||||||
static struct sc_rtp_packet *
|
|
||||||
sc_rtp_packet_new(const AVPacket *packet) {
|
|
||||||
struct sc_rtp_packet *rtp = malloc(sizeof(*rtp));
|
|
||||||
if (!rtp) {
|
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtp->packet = av_packet_alloc();
|
|
||||||
if (!rtp->packet) {
|
|
||||||
LOG_OOM();
|
|
||||||
free(rtp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (av_packet_ref(rtp->packet, packet)) {
|
|
||||||
av_packet_free(&rtp->packet);
|
|
||||||
free(rtp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_rtp_packet_delete(struct sc_rtp_packet *rtp) {
|
|
||||||
av_packet_free(&rtp->packet);
|
|
||||||
free(rtp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_rtp_queue_clear(struct sc_rtp_queue *queue) {
|
|
||||||
while (!sc_queue_is_empty(queue)) {
|
|
||||||
struct sc_rtp_packet *rtp;
|
|
||||||
sc_queue_take(queue, next, &rtp);
|
|
||||||
sc_rtp_packet_delete(rtp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_write_header(struct sc_rtp *rtp, const AVPacket *packet) {
|
|
||||||
AVStream *ostream = rtp->ctx->streams[0];
|
|
||||||
|
|
||||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
|
||||||
if (!extradata) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the first packet to the extra data
|
|
||||||
memcpy(extradata, packet->data, packet->size);
|
|
||||||
|
|
||||||
ostream->codecpar->extradata = extradata;
|
|
||||||
ostream->codecpar->extradata_size = packet->size;
|
|
||||||
|
|
||||||
int ret = avformat_write_header(rtp->ctx, NULL);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write RTP header");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_rtp_rescale_packet(struct sc_rtp *rtp, AVPacket *packet) {
|
|
||||||
AVStream *ostream = rtp->ctx->streams[0];
|
|
||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_write(struct sc_rtp *rtp, AVPacket *packet) {
|
|
||||||
if (!rtp->header_written) {
|
|
||||||
if (packet->pts != AV_NOPTS_VALUE) {
|
|
||||||
LOGE("The first packet is not a config packet");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool ok = sc_rtp_write_header(rtp, packet);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rtp->header_written = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet->pts == AV_NOPTS_VALUE) {
|
|
||||||
// ignore config packets
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_rtp_rescale_packet(rtp, packet);
|
|
||||||
return av_write_frame(rtp->ctx, packet) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_rtp(void *data) {
|
|
||||||
struct sc_rtp *rtp = data;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
sc_mutex_lock(&rtp->mutex);
|
|
||||||
|
|
||||||
while (!rtp->stopped && sc_queue_is_empty(&rtp->queue)) {
|
|
||||||
sc_cond_wait(&rtp->queue_cond, &rtp->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if stopped is set, continue to process the remaining events (to
|
|
||||||
// finish the streaming) before actually stopping
|
|
||||||
|
|
||||||
if (rtp->stopped && sc_queue_is_empty(&rtp->queue)) {
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_rtp_packet *pkt;
|
|
||||||
sc_queue_take(&rtp->queue, next, &pkt);
|
|
||||||
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
|
|
||||||
bool ok = sc_rtp_write(rtp, pkt->packet);
|
|
||||||
sc_rtp_packet_delete(pkt);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not send packet");
|
|
||||||
|
|
||||||
sc_mutex_lock(&rtp->mutex);
|
|
||||||
rtp->failed = true;
|
|
||||||
// discard pending packets
|
|
||||||
sc_rtp_queue_clear(&rtp->queue);
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rtp->failed) {
|
|
||||||
if (rtp->header_written) {
|
|
||||||
int ret = av_write_trailer(rtp->ctx);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write RTP trailer");
|
|
||||||
rtp->failed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// nothing has been sent
|
|
||||||
rtp->failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rtp->failed) {
|
|
||||||
LOGE("Streaming over RTP failed");
|
|
||||||
} else {
|
|
||||||
LOGI("Streaming over RTP complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("RTP streaming thread ended");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_open(struct sc_rtp *rtp, const AVCodec *input_codec) {
|
|
||||||
bool ok = sc_mutex_init(&rtp->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&rtp->queue_cond);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_mutex_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_queue_init(&rtp->queue);
|
|
||||||
rtp->stopped = false;
|
|
||||||
rtp->failed = false;
|
|
||||||
rtp->header_written = false;
|
|
||||||
|
|
||||||
int ret = avformat_alloc_output_context2(&rtp->ctx, NULL, "rtp",
|
|
||||||
rtp->out_url);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto error_cond_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(rtp->ctx, input_codec);
|
|
||||||
if (!ostream) {
|
|
||||||
goto error_avformat_free_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
ostream->codecpar->codec_id = input_codec->id;
|
|
||||||
ostream->codecpar->width = rtp->declared_frame_size.width;
|
|
||||||
ostream->codecpar->height = rtp->declared_frame_size.height;
|
|
||||||
|
|
||||||
ret = avio_open(&rtp->ctx->pb, rtp->out_url, AVIO_FLAG_WRITE);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to open output: %s", rtp->out_url);
|
|
||||||
// ostream will be cleaned up during context cleaning
|
|
||||||
goto error_avformat_free_context;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Starting RTP thread");
|
|
||||||
ok = sc_thread_create(&rtp->thread, run_rtp, "scrcpy-rtp", rtp);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not start RTP thread");
|
|
||||||
goto error_avio_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("Streaming started to %s", rtp->out_url);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error_avio_close:
|
|
||||||
avio_close(rtp->ctx->pb);
|
|
||||||
error_avformat_free_context:
|
|
||||||
avformat_free_context(rtp->ctx);
|
|
||||||
error_cond_destroy:
|
|
||||||
sc_cond_destroy(&rtp->queue_cond);
|
|
||||||
error_mutex_destroy:
|
|
||||||
sc_mutex_destroy(&rtp->mutex);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_rtp_close(struct sc_rtp *rtp) {
|
|
||||||
sc_mutex_lock(&rtp->mutex);
|
|
||||||
rtp->stopped = true;
|
|
||||||
sc_cond_signal(&rtp->queue_cond);
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
|
|
||||||
sc_thread_join(&rtp->thread, NULL);
|
|
||||||
|
|
||||||
avio_close(rtp->ctx->pb);
|
|
||||||
avformat_free_context(rtp->ctx);
|
|
||||||
sc_cond_destroy(&rtp->queue_cond);
|
|
||||||
sc_mutex_destroy(&rtp->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_push(struct sc_rtp *rtp, const AVPacket *packet) {
|
|
||||||
sc_mutex_lock(&rtp->mutex);
|
|
||||||
assert(!rtp->stopped);
|
|
||||||
|
|
||||||
if (rtp->failed) {
|
|
||||||
// reject any new packet (this will stop the stream)
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_rtp_packet *pkt = sc_rtp_packet_new(packet);
|
|
||||||
if (!pkt) {
|
|
||||||
LOG_OOM();
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_queue_push(&rtp->queue, next, pkt);
|
|
||||||
sc_cond_signal(&rtp->queue_cond);
|
|
||||||
|
|
||||||
sc_mutex_unlock(&rtp->mutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_packet_sink_open(struct sc_packet_sink *sink,
|
|
||||||
const AVCodec *codec) {
|
|
||||||
struct sc_rtp *rtp = DOWNCAST(sink);
|
|
||||||
return sc_rtp_open(rtp, codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_rtp_packet_sink_close(struct sc_packet_sink *sink) {
|
|
||||||
struct sc_rtp *rtp = DOWNCAST(sink);
|
|
||||||
sc_rtp_close(rtp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_rtp_packet_sink_push(struct sc_packet_sink *sink,
|
|
||||||
const AVPacket *packet) {
|
|
||||||
struct sc_rtp *rtp = DOWNCAST(sink);
|
|
||||||
return sc_rtp_push(rtp, packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_rtp_init(struct sc_rtp *rtp, const char *out_url,
|
|
||||||
struct sc_size declared_frame_size) {
|
|
||||||
rtp->out_url = strdup(out_url);
|
|
||||||
if (!rtp->out_url) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtp->declared_frame_size = declared_frame_size;
|
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
|
||||||
.open = sc_rtp_packet_sink_open,
|
|
||||||
.close = sc_rtp_packet_sink_close,
|
|
||||||
.push = sc_rtp_packet_sink_push,
|
|
||||||
};
|
|
||||||
|
|
||||||
rtp->packet_sink.ops = &ops;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_rtp_destroy(struct sc_rtp *rtp) {
|
|
||||||
free(rtp->out_url);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#ifndef SC_RTP_H
|
|
||||||
#define SC_RTP_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
#include "coords.h"
|
|
||||||
#include "trait/packet_sink.h"
|
|
||||||
#include "util/queue.h"
|
|
||||||
#include "util/thread.h"
|
|
||||||
|
|
||||||
struct sc_rtp_packet {
|
|
||||||
AVPacket *packet;
|
|
||||||
struct sc_rtp_packet *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_rtp_queue SC_QUEUE(struct sc_rtp_packet);
|
|
||||||
|
|
||||||
struct sc_rtp {
|
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait;
|
|
||||||
|
|
||||||
char *out_url;
|
|
||||||
AVFormatContext *ctx;
|
|
||||||
struct sc_size declared_frame_size;
|
|
||||||
bool header_written;
|
|
||||||
|
|
||||||
sc_thread thread;
|
|
||||||
sc_mutex mutex;
|
|
||||||
sc_cond queue_cond;
|
|
||||||
bool stopped; // set on rtp_close()
|
|
||||||
bool failed; // set on packet write failure
|
|
||||||
struct sc_rtp_queue queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_rtp_init(struct sc_rtp *rtp, const char *out_url,
|
|
||||||
struct sc_size declared_frame_size);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_rtp_destroy(struct sc_rtp *rtp);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
702
app/src/scrcpy.c
702
app/src/scrcpy.c
@@ -15,22 +15,19 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "demuxer.h"
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "file_pusher.h"
|
#include "file_handler.h"
|
||||||
|
#include "input_manager.h"
|
||||||
|
#ifdef HAVE_AOA_HID
|
||||||
|
# include "hid_keyboard.h"
|
||||||
|
#endif
|
||||||
#include "keyboard_inject.h"
|
#include "keyboard_inject.h"
|
||||||
#include "mouse_inject.h"
|
#include "mouse_inject.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "rtp.h"
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#ifdef HAVE_USB
|
#include "stream.h"
|
||||||
# include "usb/aoa_hid.h"
|
#include "tiny_xpm.h"
|
||||||
# include "usb/hid_keyboard.h"
|
|
||||||
# include "usb/hid_mouse.h"
|
|
||||||
# include "usb/usb.h"
|
|
||||||
#endif
|
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -38,61 +35,64 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct scrcpy {
|
struct scrcpy {
|
||||||
struct sc_server server;
|
struct server server;
|
||||||
struct sc_screen screen;
|
struct screen screen;
|
||||||
struct sc_demuxer demuxer;
|
struct stream stream;
|
||||||
struct sc_decoder decoder;
|
struct decoder decoder;
|
||||||
struct sc_recorder recorder;
|
struct recorder recorder;
|
||||||
struct sc_rtp rtp;
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
struct sc_v4l2_sink v4l2_sink;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
#endif
|
#endif
|
||||||
struct sc_controller controller;
|
struct controller controller;
|
||||||
struct sc_file_pusher file_pusher;
|
struct file_handler file_handler;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_usb usb;
|
struct aoa aoa;
|
||||||
struct sc_aoa aoa;
|
|
||||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
|
||||||
struct sc_acksync acksync;
|
|
||||||
#endif
|
#endif
|
||||||
union {
|
union {
|
||||||
struct sc_keyboard_inject keyboard_inject;
|
struct sc_keyboard_inject keyboard_inject;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_hid_keyboard keyboard_hid;
|
struct hid_keyboard keyboard_hid;
|
||||||
#endif
|
|
||||||
};
|
|
||||||
union {
|
|
||||||
struct sc_mouse_inject mouse_inject;
|
|
||||||
#ifdef HAVE_USB
|
|
||||||
struct sc_hid_mouse mouse_hid;
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
struct sc_mouse_inject mouse_inject;
|
||||||
|
struct input_manager input_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
|
||||||
push_event(uint32_t type, const char *name) {
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = type;
|
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
|
||||||
// What could we do?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||||
if (ctrl_type == CTRL_C_EVENT) {
|
if (ctrl_type == CTRL_C_EVENT) {
|
||||||
PUSH_EVENT(SDL_QUIT);
|
SDL_Event event;
|
||||||
|
event.type = SDL_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
static void
|
// init SDL and set appropriate hints
|
||||||
sdl_set_hints(const char *render_driver) {
|
static bool
|
||||||
|
sdl_init_and_configure(bool display, const char *render_driver,
|
||||||
|
bool disable_screensaver) {
|
||||||
|
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
||||||
|
if (SDL_Init(flags)) {
|
||||||
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Clean up properly on Ctrl+C on Windows
|
||||||
|
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not set Ctrl+C handler");
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
if (!display) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
||||||
LOGW("Could not set render driver");
|
LOGW("Could not set render driver");
|
||||||
@@ -103,18 +103,11 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
LOGW("Could not enable linear filtering");
|
LOGW("Could not enable linear filtering");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
|
||||||
// Handle a click to gain focus as any other click
|
// Handle a click to gain focus as any other click
|
||||||
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
||||||
LOGW("Could not enable mouse focus clickthrough");
|
LOGW("Could not enable mouse focus clickthrough");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
|
||||||
// Disable synthetic mouse events from touch events
|
|
||||||
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
|
|
||||||
// better not to generate them in the first place.
|
|
||||||
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
|
|
||||||
LOGW("Could not disable synthetic mouse events");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
@@ -128,71 +121,89 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||||
LOGW("Could not disable minimize on focus loss");
|
LOGW("Could not disable minimize on focus loss");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sdl_configure(bool display, bool disable_screensaver) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Clean up properly on Ctrl+C on Windows
|
|
||||||
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not set Ctrl+C handler");
|
|
||||||
}
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
if (!display) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disable_screensaver) {
|
if (disable_screensaver) {
|
||||||
|
LOGD("Screensaver disabled");
|
||||||
SDL_DisableScreenSaver();
|
SDL_DisableScreenSaver();
|
||||||
} else {
|
} else {
|
||||||
|
LOGD("Screensaver enabled");
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum scrcpy_exit_code
|
|
||||||
event_loop(struct scrcpy *s) {
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_WaitEvent(&event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case EVENT_STREAM_STOPPED:
|
|
||||||
LOGW("Device disconnected");
|
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
|
||||||
case SDL_QUIT:
|
|
||||||
LOGD("User requested to quit");
|
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
|
||||||
default:
|
|
||||||
sc_screen_handle_event(&s->screen, &event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true on success, false on error
|
|
||||||
static bool
|
static bool
|
||||||
await_for_server(bool *connected) {
|
is_apk(const char *file) {
|
||||||
SDL_Event event;
|
const char *ext = strrchr(file, '.');
|
||||||
while (SDL_WaitEvent(&event)) {
|
return ext && !strcmp(ext, ".apk");
|
||||||
switch (event.type) {
|
}
|
||||||
case SDL_QUIT:
|
|
||||||
LOGD("User requested to quit");
|
enum event_result {
|
||||||
*connected = false;
|
EVENT_RESULT_CONTINUE,
|
||||||
return true;
|
EVENT_RESULT_STOPPED_BY_USER,
|
||||||
case EVENT_SERVER_CONNECTION_FAILED:
|
EVENT_RESULT_STOPPED_BY_EOS,
|
||||||
LOGE("Server connection failed");
|
};
|
||||||
return false;
|
|
||||||
case EVENT_SERVER_CONNECTED:
|
static enum event_result
|
||||||
LOGD("Server connected");
|
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||||
*connected = true;
|
SDL_Event *event) {
|
||||||
return true;
|
switch (event->type) {
|
||||||
default:
|
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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
|
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)) {
|
||||||
|
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 false;
|
||||||
|
case EVENT_RESULT_CONTINUE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +235,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
size_t fmt_len = strlen(fmt);
|
size_t fmt_len = strlen(fmt);
|
||||||
char *local_fmt = malloc(fmt_len + 10);
|
char *local_fmt = malloc(fmt_len + 10);
|
||||||
if (!local_fmt) {
|
if (!local_fmt) {
|
||||||
LOG_OOM();
|
LOGC("Could not allocate string");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
||||||
@@ -234,82 +245,46 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
stream_on_eos(struct stream *stream, void *userdata) {
|
||||||
(void) demuxer;
|
(void) stream;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
SDL_Event stop_event;
|
||||||
|
stop_event.type = EVENT_STREAM_STOPPED;
|
||||||
|
SDL_PushEvent(&stop_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
bool
|
||||||
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
|
||||||
(void) server;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_server_on_connected(struct sc_server *server, void *userdata) {
|
|
||||||
(void) server;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
|
||||||
(void) server;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
LOGD("Server disconnected");
|
|
||||||
// Do nothing, the disconnection will be handled by the "stream stopped"
|
|
||||||
// event
|
|
||||||
}
|
|
||||||
|
|
||||||
enum scrcpy_exit_code
|
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
struct scrcpy *s = &scrcpy;
|
struct scrcpy *s = &scrcpy;
|
||||||
|
|
||||||
// Minimal SDL initialization
|
if (!server_init(&s->server)) {
|
||||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
return false;
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
atexit(SDL_Quit);
|
bool ret = false;
|
||||||
|
|
||||||
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
|
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
bool file_pusher_initialized = false;
|
bool file_handler_initialized = false;
|
||||||
bool recorder_initialized = false;
|
bool recorder_initialized = false;
|
||||||
bool rtp_initialized = false;
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
bool v4l2_sink_initialized = false;
|
bool v4l2_sink_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool demuxer_started = false;
|
bool stream_started = false;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_AOA_HID
|
||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
bool hid_keyboard_initialized = false;
|
|
||||||
bool hid_mouse_initialized = false;
|
|
||||||
#endif
|
#endif
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
bool screen_initialized = false;
|
bool screen_initialized = false;
|
||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
bool record = !!options->record_filename;
|
||||||
|
struct server_params params = {
|
||||||
struct sc_server_params params = {
|
.serial = options->serial,
|
||||||
.req_serial = options->serial,
|
|
||||||
.select_usb = options->select_usb,
|
|
||||||
.select_tcpip = options->select_tcpip,
|
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
.tunnel_host = options->tunnel_host,
|
|
||||||
.tunnel_port = options->tunnel_port,
|
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
.bit_rate = options->bit_rate,
|
.bit_rate = options->bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
@@ -322,86 +297,49 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.encoder_name = options->encoder_name,
|
.encoder_name = options->encoder_name,
|
||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
|
||||||
.downsize_on_error = options->downsize_on_error,
|
|
||||||
.tcpip = options->tcpip,
|
|
||||||
.tcpip_dst = options->tcpip_dst,
|
|
||||||
.cleanup = options->cleanup,
|
|
||||||
.power_on = options->power_on,
|
|
||||||
};
|
};
|
||||||
|
if (!server_start(&s->server, ¶ms)) {
|
||||||
static const struct sc_server_callbacks cbs = {
|
|
||||||
.on_connection_failed = sc_server_on_connection_failed,
|
|
||||||
.on_connected = sc_server_on_connected,
|
|
||||||
.on_disconnected = sc_server_on_disconnected,
|
|
||||||
};
|
|
||||||
if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) {
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_server_start(&s->server)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_started = true;
|
server_started = true;
|
||||||
|
|
||||||
if (options->display) {
|
if (!sdl_init_and_configure(options->display, options->render_driver,
|
||||||
sdl_set_hints(options->render_driver);
|
options->disable_screensaver)) {
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize SDL video in addition if display is enabled
|
|
||||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||||
|
struct size frame_size;
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||||
bool connected;
|
|
||||||
if (!await_for_server(&connected)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connected) {
|
|
||||||
// This is not an error, user requested to quit
|
|
||||||
ret = SCRCPY_EXIT_SUCCESS;
|
|
||||||
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 (options->display && options->control) {
|
||||||
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
if (!file_handler_init(&s->file_handler, s->server.serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
fp = &s->file_pusher;
|
file_handler_initialized = true;
|
||||||
file_pusher_initialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_decoder *dec = NULL;
|
struct decoder *dec = NULL;
|
||||||
bool needs_decoder = options->display;
|
bool needs_decoder = options->display;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
needs_decoder |= !!options->v4l2_device;
|
needs_decoder |= !!options->v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
if (needs_decoder) {
|
if (needs_decoder) {
|
||||||
sc_decoder_init(&s->decoder);
|
decoder_init(&s->decoder);
|
||||||
dec = &s->decoder;
|
dec = &s->decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_recorder *rec = NULL;
|
struct recorder *rec = NULL;
|
||||||
if (options->record_filename) {
|
if (record) {
|
||||||
if (!sc_recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info->frame_size)) {
|
frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
@@ -410,197 +348,48 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
av_log_set_callback(av_log_callback);
|
av_log_set_callback(av_log_callback);
|
||||||
|
|
||||||
static const struct sc_demuxer_callbacks demuxer_cbs = {
|
static const struct stream_callbacks stream_cbs = {
|
||||||
.on_eos = sc_demuxer_on_eos,
|
.on_eos = stream_on_eos,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL);
|
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
||||||
|
|
||||||
if (dec) {
|
if (dec) {
|
||||||
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink);
|
stream_add_sink(&s->stream, &dec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec) {
|
if (rec) {
|
||||||
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink);
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_rtp *rtp = NULL;
|
|
||||||
if (!sc_rtp_init(&s->rtp, "rtp://127.0.0.1:1234", info->frame_size)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
rtp = &s->rtp;
|
|
||||||
rtp_initialized = true;
|
|
||||||
sc_demuxer_add_sink(&s->demuxer, &rtp->packet_sink);
|
|
||||||
|
|
||||||
struct sc_controller *controller = NULL;
|
|
||||||
struct sc_key_processor *kp = NULL;
|
|
||||||
struct sc_mouse_processor *mp = NULL;
|
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
#ifdef HAVE_USB
|
if (!controller_init(&s->controller, s->server.control_socket)) {
|
||||||
bool use_hid_keyboard =
|
|
||||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
|
|
||||||
bool use_hid_mouse =
|
|
||||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
|
|
||||||
if (use_hid_keyboard || use_hid_mouse) {
|
|
||||||
bool ok = sc_acksync_init(&s->acksync);
|
|
||||||
if (!ok) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_usb_init(&s->usb);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Failed to initialize USB");
|
|
||||||
sc_acksync_destroy(&s->acksync);
|
|
||||||
goto aoa_hid_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(serial);
|
|
||||||
struct sc_usb_device usb_device;
|
|
||||||
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
|
|
||||||
if (!ok) {
|
|
||||||
sc_usb_destroy(&s->usb);
|
|
||||||
goto aoa_hid_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
|
|
||||||
usb_device.serial, usb_device.vid, usb_device.pid,
|
|
||||||
usb_device.manufacturer, usb_device.product);
|
|
||||||
|
|
||||||
ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL);
|
|
||||||
sc_usb_device_destroy(&usb_device);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Failed to connect to USB device %s", serial);
|
|
||||||
sc_usb_destroy(&s->usb);
|
|
||||||
sc_acksync_destroy(&s->acksync);
|
|
||||||
goto aoa_hid_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Failed to enable HID over AOA");
|
|
||||||
sc_usb_disconnect(&s->usb);
|
|
||||||
sc_usb_destroy(&s->usb);
|
|
||||||
sc_acksync_destroy(&s->acksync);
|
|
||||||
goto aoa_hid_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_hid_keyboard) {
|
|
||||||
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
|
||||||
hid_keyboard_initialized = true;
|
|
||||||
kp = &s->keyboard_hid.key_processor;
|
|
||||||
} else {
|
|
||||||
LOGE("Could not initialize HID keyboard");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_hid_mouse) {
|
|
||||||
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
|
|
||||||
hid_mouse_initialized = true;
|
|
||||||
mp = &s->mouse_hid.mouse_processor;
|
|
||||||
} else {
|
|
||||||
LOGE("Could not initialized HID mouse");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
|
|
||||||
|
|
||||||
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
|
|
||||||
sc_acksync_destroy(&s->acksync);
|
|
||||||
sc_usb_disconnect(&s->usb);
|
|
||||||
sc_usb_destroy(&s->usb);
|
|
||||||
sc_aoa_destroy(&s->aoa);
|
|
||||||
goto aoa_hid_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
acksync = &s->acksync;
|
|
||||||
|
|
||||||
aoa_hid_initialized = true;
|
|
||||||
|
|
||||||
aoa_hid_end:
|
|
||||||
if (!aoa_hid_initialized) {
|
|
||||||
if (hid_keyboard_initialized) {
|
|
||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
|
||||||
hid_keyboard_initialized = false;
|
|
||||||
}
|
|
||||||
if (hid_mouse_initialized) {
|
|
||||||
sc_hid_mouse_destroy(&s->mouse_hid);
|
|
||||||
hid_mouse_initialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_hid_keyboard && !hid_keyboard_initialized) {
|
|
||||||
LOGE("Fallback to default keyboard injection method "
|
|
||||||
"(-K/--hid-keyboard ignored)");
|
|
||||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_hid_mouse && !hid_mouse_initialized) {
|
|
||||||
LOGE("Fallback to default mouse injection method "
|
|
||||||
"(-M/--hid-mouse ignored)");
|
|
||||||
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
|
|
||||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// keyboard_input_mode may have been reset if HID mode failed
|
|
||||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
|
|
||||||
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
|
|
||||||
options->key_inject_mode,
|
|
||||||
options->forward_key_repeat);
|
|
||||||
kp = &s->keyboard_inject.key_processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse_input_mode may have been reset if HID mode failed
|
|
||||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
|
|
||||||
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
|
|
||||||
mp = &s->mouse_inject.mouse_processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
|
||||||
acksync)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_initialized = true;
|
controller_initialized = true;
|
||||||
|
|
||||||
if (!sc_controller_start(&s->controller)) {
|
if (!controller_start(&s->controller)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_started = true;
|
controller_started = true;
|
||||||
controller = &s->controller;
|
|
||||||
|
|
||||||
if (options->turn_screen_off) {
|
if (options->turn_screen_off) {
|
||||||
struct sc_control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||||
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
if (!controller_push_msg(&s->controller, &msg)) {
|
||||||
LOGW("Could not request 'set screen power mode'");
|
LOGW("Could not request 'set screen power mode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is a controller if and only if control is enabled
|
|
||||||
assert(options->control == !!controller);
|
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : device_name;
|
||||||
|
|
||||||
struct sc_screen_params screen_params = {
|
struct screen_params screen_params = {
|
||||||
.controller = controller,
|
|
||||||
.fp = fp,
|
|
||||||
.kp = kp,
|
|
||||||
.mp = mp,
|
|
||||||
.forward_all_clicks = options->forward_all_clicks,
|
|
||||||
.legacy_paste = options->legacy_paste,
|
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
|
||||||
.shortcut_mods = &options->shortcut_mods,
|
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info->frame_size,
|
.frame_size = frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
@@ -610,82 +399,146 @@ aoa_hid_end:
|
|||||||
.rotation = options->rotation,
|
.rotation = options->rotation,
|
||||||
.mipmaps = options->mipmaps,
|
.mipmaps = options->mipmaps,
|
||||||
.fullscreen = options->fullscreen,
|
.fullscreen = options->fullscreen,
|
||||||
.start_fps_counter = options->start_fps_counter,
|
|
||||||
.buffering_time = options->display_buffer,
|
.buffering_time = options->display_buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
if (!screen_init(&s->screen, &screen_params)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
screen_initialized = true;
|
screen_initialized = true;
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
|
||||||
info->frame_size, options->v4l2_buffer)) {
|
options->v4l2_buffer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
||||||
|
|
||||||
v4l2_sink_initialized = true;
|
v4l2_sink_initialized = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// now we consumed the header values, the socket receives the video stream
|
// now we consumed the header values, the socket receives the video stream
|
||||||
// start the demuxer
|
// start the stream
|
||||||
if (!sc_demuxer_start(&s->demuxer)) {
|
if (!stream_start(&s->stream)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
demuxer_started = true;
|
stream_started = true;
|
||||||
|
|
||||||
ret = event_loop(s);
|
struct sc_key_processor *kp = NULL;
|
||||||
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
|
||||||
|
if (options->control) {
|
||||||
|
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
|
||||||
|
#ifdef HAVE_AOA_HID
|
||||||
|
bool aoa_hid_ok = false;
|
||||||
|
|
||||||
|
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 = aoa_init(&s->aoa, serial);
|
||||||
|
free(serialno);
|
||||||
|
if (!ok) {
|
||||||
|
goto aoa_hid_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aoa_start(&s->aoa)) {
|
||||||
|
aoa_destroy(&s->aoa);
|
||||||
|
goto aoa_hid_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned mod = SC_MOD_CAPS_LOCK;
|
||||||
|
if (!hid_keyboard_init(&s->keyboard_hid, &s->aoa, mod)) {
|
||||||
|
aoa_join(&s->aoa);
|
||||||
|
aoa_stop(&s->aoa);
|
||||||
|
aoa_destroy(&s->aoa);
|
||||||
|
goto aoa_hid_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
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/--keyboard-hid ignored)");
|
||||||
|
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
LOGE("HID over AOA is not supported on this platform, "
|
||||||
|
"fallback to default keyboard injection method "
|
||||||
|
"(-K/--keyboard-hid ignored)");
|
||||||
|
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyboard_input_mode may have been reset if HID mode failed
|
||||||
|
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
|
||||||
|
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
|
||||||
|
options);
|
||||||
|
kp = &s->keyboard_inject.key_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen);
|
||||||
|
mp = &s->mouse_inject.mouse_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
|
||||||
|
options);
|
||||||
|
|
||||||
|
ret = event_loop(s, options);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
// Close the window immediately on closing, because screen_destroy() may
|
// Close the window immediately on closing, because screen_destroy() may
|
||||||
// only be called once the demuxer thread is joined (it may take time)
|
// only be called once the stream thread is joined (it may take time)
|
||||||
sc_screen_hide_window(&s->screen);
|
screen_hide_window(&s->screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
// The demuxer is not stopped explicitly, because it will stop by itself on
|
// The stream is not stopped explicitly, because it will stop by itself on
|
||||||
// end-of-stream
|
// end-of-stream
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_AOA_HID
|
||||||
if (aoa_hid_initialized) {
|
if (aoa_hid_initialized) {
|
||||||
if (hid_keyboard_initialized) {
|
hid_keyboard_destroy(&s->keyboard_hid);
|
||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
aoa_stop(&s->aoa);
|
||||||
}
|
|
||||||
if (hid_mouse_initialized) {
|
|
||||||
sc_hid_mouse_destroy(&s->mouse_hid);
|
|
||||||
}
|
|
||||||
sc_aoa_stop(&s->aoa);
|
|
||||||
sc_usb_stop(&s->usb);
|
|
||||||
}
|
|
||||||
if (acksync) {
|
|
||||||
sc_acksync_destroy(acksync);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
sc_controller_stop(&s->controller);
|
controller_stop(&s->controller);
|
||||||
}
|
}
|
||||||
if (file_pusher_initialized) {
|
if (file_handler_initialized) {
|
||||||
sc_file_pusher_stop(&s->file_pusher);
|
file_handler_stop(&s->file_handler);
|
||||||
}
|
}
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_interrupt(&s->screen);
|
screen_interrupt(&s->screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server_started) {
|
if (server_started) {
|
||||||
// shutdown the sockets and kill the server
|
// shutdown the sockets and kill the server
|
||||||
sc_server_stop(&s->server);
|
server_stop(&s->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now that the sockets are shutdown, the demuxer and controller are
|
// now that the sockets are shutdown, the stream and controller are
|
||||||
// interrupted, we can join them
|
// interrupted, we can join them
|
||||||
if (demuxer_started) {
|
if (stream_started) {
|
||||||
sc_demuxer_join(&s->demuxer);
|
stream_join(&s->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@@ -694,44 +547,37 @@ end:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_AOA_HID
|
||||||
if (aoa_hid_initialized) {
|
if (aoa_hid_initialized) {
|
||||||
sc_aoa_join(&s->aoa);
|
aoa_join(&s->aoa);
|
||||||
sc_aoa_destroy(&s->aoa);
|
aoa_destroy(&s->aoa);
|
||||||
sc_usb_join(&s->usb);
|
|
||||||
sc_usb_disconnect(&s->usb);
|
|
||||||
sc_usb_destroy(&s->usb);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Destroy the screen only after the demuxer is guaranteed to be finished,
|
// Destroy the screen only after the stream is guaranteed to be finished,
|
||||||
// because otherwise the screen could receive new frames after destruction
|
// because otherwise the screen could receive new frames after destruction
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_join(&s->screen);
|
screen_join(&s->screen);
|
||||||
sc_screen_destroy(&s->screen);
|
screen_destroy(&s->screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
sc_controller_join(&s->controller);
|
controller_join(&s->controller);
|
||||||
}
|
}
|
||||||
if (controller_initialized) {
|
if (controller_initialized) {
|
||||||
sc_controller_destroy(&s->controller);
|
controller_destroy(&s->controller);
|
||||||
}
|
|
||||||
|
|
||||||
if (rtp_initialized) {
|
|
||||||
sc_rtp_destroy(&s->rtp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recorder_initialized) {
|
if (recorder_initialized) {
|
||||||
sc_recorder_destroy(&s->recorder);
|
recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_pusher_initialized) {
|
if (file_handler_initialized) {
|
||||||
sc_file_pusher_join(&s->file_pusher);
|
file_handler_join(&s->file_handler);
|
||||||
sc_file_pusher_destroy(&s->file_pusher);
|
file_handler_destroy(&s->file_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_server_destroy(&s->server);
|
server_destroy(&s->server);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
160
app/src/scrcpy.h
160
app/src/scrcpy.h
@@ -4,20 +4,160 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "options.h"
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
enum scrcpy_exit_code {
|
#include "util/tick.h"
|
||||||
// Normal program termination
|
|
||||||
SCRCPY_EXIT_SUCCESS,
|
|
||||||
|
|
||||||
// No connection could be established
|
enum sc_log_level {
|
||||||
SCRCPY_EXIT_FAILURE,
|
SC_LOG_LEVEL_VERBOSE,
|
||||||
|
SC_LOG_LEVEL_DEBUG,
|
||||||
// Device was disconnected while running
|
SC_LOG_LEVEL_INFO,
|
||||||
SCRCPY_EXIT_DISCONNECTED,
|
SC_LOG_LEVEL_WARN,
|
||||||
|
SC_LOG_LEVEL_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum scrcpy_exit_code
|
enum sc_record_format {
|
||||||
|
SC_RECORD_FORMAT_AUTO,
|
||||||
|
SC_RECORD_FORMAT_MP4,
|
||||||
|
SC_RECORD_FORMAT_MKV,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_keyboard_input_mode {
|
||||||
|
SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||||
|
SC_KEYBOARD_INPUT_MODE_HID,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
|
enum sc_shortcut_mod {
|
||||||
|
SC_MOD_LCTRL = 1 << 0,
|
||||||
|
SC_MOD_RCTRL = 1 << 1,
|
||||||
|
SC_MOD_LALT = 1 << 2,
|
||||||
|
SC_MOD_RALT = 1 << 3,
|
||||||
|
SC_MOD_LSUPER = 1 << 4,
|
||||||
|
SC_MOD_RSUPER = 1 << 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_shortcut_mods {
|
||||||
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
|
unsigned count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_port_range {
|
||||||
|
uint16_t first;
|
||||||
|
uint16_t last;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
||||||
|
|
||||||
|
struct scrcpy_options {
|
||||||
|
const char *serial;
|
||||||
|
const char *crop;
|
||||||
|
const char *record_filename;
|
||||||
|
const char *window_title;
|
||||||
|
const char *push_target;
|
||||||
|
const char *render_driver;
|
||||||
|
const char *codec_options;
|
||||||
|
const char *encoder_name;
|
||||||
|
const char *v4l2_device;
|
||||||
|
enum sc_log_level log_level;
|
||||||
|
enum sc_record_format record_format;
|
||||||
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
|
struct sc_port_range port_range;
|
||||||
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
|
uint16_t max_size;
|
||||||
|
uint32_t bit_rate;
|
||||||
|
uint16_t max_fps;
|
||||||
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
|
uint8_t rotation;
|
||||||
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
|
uint16_t window_width;
|
||||||
|
uint16_t window_height;
|
||||||
|
uint32_t display_id;
|
||||||
|
sc_tick display_buffer;
|
||||||
|
sc_tick v4l2_buffer;
|
||||||
|
bool show_touches;
|
||||||
|
bool fullscreen;
|
||||||
|
bool always_on_top;
|
||||||
|
bool control;
|
||||||
|
bool display;
|
||||||
|
bool turn_screen_off;
|
||||||
|
bool prefer_text;
|
||||||
|
bool window_borderless;
|
||||||
|
bool mipmaps;
|
||||||
|
bool stay_awake;
|
||||||
|
bool force_adb_forward;
|
||||||
|
bool disable_screensaver;
|
||||||
|
bool forward_key_repeat;
|
||||||
|
bool forward_all_clicks;
|
||||||
|
bool legacy_paste;
|
||||||
|
bool power_off_on_close;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
|
.serial = NULL, \
|
||||||
|
.crop = NULL, \
|
||||||
|
.record_filename = NULL, \
|
||||||
|
.window_title = NULL, \
|
||||||
|
.push_target = NULL, \
|
||||||
|
.render_driver = NULL, \
|
||||||
|
.codec_options = NULL, \
|
||||||
|
.encoder_name = NULL, \
|
||||||
|
.v4l2_device = NULL, \
|
||||||
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, \
|
||||||
|
.port_range = { \
|
||||||
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
||||||
|
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
|
||||||
|
}, \
|
||||||
|
.shortcut_mods = { \
|
||||||
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
|
.count = 2, \
|
||||||
|
}, \
|
||||||
|
.max_size = 0, \
|
||||||
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
|
.max_fps = 0, \
|
||||||
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
|
||||||
|
.rotation = 0, \
|
||||||
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
.window_width = 0, \
|
||||||
|
.window_height = 0, \
|
||||||
|
.display_id = 0, \
|
||||||
|
.display_buffer = 0, \
|
||||||
|
.v4l2_buffer = 0, \
|
||||||
|
.show_touches = false, \
|
||||||
|
.fullscreen = false, \
|
||||||
|
.always_on_top = false, \
|
||||||
|
.control = true, \
|
||||||
|
.display = true, \
|
||||||
|
.turn_screen_off = false, \
|
||||||
|
.prefer_text = false, \
|
||||||
|
.window_borderless = false, \
|
||||||
|
.mipmaps = true, \
|
||||||
|
.stay_awake = false, \
|
||||||
|
.force_adb_forward = false, \
|
||||||
|
.disable_screensaver = false, \
|
||||||
|
.forward_key_repeat = true, \
|
||||||
|
.forward_all_clicks = false, \
|
||||||
|
.legacy_paste = false, \
|
||||||
|
.power_off_on_close = false, \
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
scrcpy(struct scrcpy_options *options);
|
scrcpy(struct scrcpy_options *options);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
532
app/src/screen.c
532
app/src/screen.c
@@ -5,18 +5,19 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "icon.h"
|
#include "icon.xpm"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DISPLAY_MARGINS 96
|
#define DISPLAY_MARGINS 96
|
||||||
|
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||||
|
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_rotated_size(struct sc_size size, int rotation) {
|
get_rotated_size(struct size size, int rotation) {
|
||||||
struct sc_size rotated_size;
|
struct size rotated_size;
|
||||||
if (rotation & 1) {
|
if (rotation & 1) {
|
||||||
rotated_size.width = size.height;
|
rotated_size.width = size.height;
|
||||||
rotated_size.height = size.width;
|
rotated_size.height = size.width;
|
||||||
@@ -27,26 +28,26 @@ get_rotated_size(struct sc_size size, int rotation) {
|
|||||||
return rotated_size;
|
return rotated_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the window size in a struct sc_size
|
// get the window size in a struct size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_window_size(const struct sc_screen *screen) {
|
get_window_size(const struct screen *screen) {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
SDL_GetWindowSize(screen->window, &width, &height);
|
SDL_GetWindowSize(screen->window, &width, &height);
|
||||||
|
|
||||||
struct sc_size size;
|
struct size size;
|
||||||
size.width = width;
|
size.width = width;
|
||||||
size.height = height;
|
size.height = height;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct point
|
||||||
get_window_position(const struct sc_screen *screen) {
|
get_window_position(const struct screen *screen) {
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||||
|
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
point.x = x;
|
point.x = x;
|
||||||
point.y = y;
|
point.y = y;
|
||||||
return point;
|
return point;
|
||||||
@@ -54,7 +55,7 @@ get_window_position(const struct sc_screen *screen) {
|
|||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
static void
|
static void
|
||||||
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
assert(!screen->fullscreen);
|
assert(!screen->fullscreen);
|
||||||
assert(!screen->maximized);
|
assert(!screen->maximized);
|
||||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||||
@@ -62,9 +63,14 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
|||||||
|
|
||||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||||
static bool
|
static bool
|
||||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
get_preferred_display_bounds(struct size *bounds) {
|
||||||
SDL_Rect rect;
|
SDL_Rect rect;
|
||||||
if (SDL_GetDisplayUsableBounds(0, &rect)) {
|
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
||||||
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
||||||
|
#else
|
||||||
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
|
||||||
|
#endif
|
||||||
|
if (GET_DISPLAY_BOUNDS(0, &rect)) {
|
||||||
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -75,7 +81,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
is_optimal_size(struct size current_size, struct size content_size) {
|
||||||
// The size is optimal if we can recompute one dimension of the current
|
// The size is optimal if we can recompute one dimension of the current
|
||||||
// size from the other
|
// size from the other
|
||||||
return current_size.height == current_size.width * content_size.height
|
return current_size.height == current_size.width * content_size.height
|
||||||
@@ -89,21 +95,20 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
|||||||
// crops the black borders)
|
// crops the black borders)
|
||||||
// - it keeps the aspect ratio
|
// - it keeps the aspect ratio
|
||||||
// - it scales down to make it fit in the display_size
|
// - it scales down to make it fit in the display_size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size,
|
get_optimal_size(struct size current_size, struct size content_size) {
|
||||||
bool within_display_bounds) {
|
|
||||||
if (content_size.width == 0 || content_size.height == 0) {
|
if (content_size.width == 0 || content_size.height == 0) {
|
||||||
// avoid division by 0
|
// avoid division by 0
|
||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
|
|
||||||
struct sc_size display_size;
|
struct size display_size;
|
||||||
if (!within_display_bounds ||
|
if (!get_preferred_display_bounds(&display_size)) {
|
||||||
!get_preferred_display_bounds(&display_size)) {
|
// could not get display bounds, do not constraint the size
|
||||||
// do not constraint the size
|
window_size.width = current_size.width;
|
||||||
window_size = current_size;
|
window_size.height = current_size.height;
|
||||||
} else {
|
} else {
|
||||||
window_size.width = MIN(current_size.width, display_size.width);
|
window_size.width = MIN(current_size.width, display_size.width);
|
||||||
window_size.height = MIN(current_size.height, display_size.height);
|
window_size.height = MIN(current_size.height, display_size.height);
|
||||||
@@ -131,12 +136,12 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size,
|
|||||||
|
|
||||||
// initially, there is no current size, so use the frame size as current size
|
// initially, there is no current size, so use the frame size as current size
|
||||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
||||||
uint16_t req_height) {
|
uint16_t req_height) {
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
if (!req_width && !req_height) {
|
if (!req_width && !req_height) {
|
||||||
window_size = get_optimal_size(content_size, content_size, true);
|
window_size = get_optimal_size(content_size, content_size);
|
||||||
} else {
|
} else {
|
||||||
if (req_width) {
|
if (req_width) {
|
||||||
window_size.width = req_width;
|
window_size.width = req_width;
|
||||||
@@ -156,62 +161,15 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
sc_screen_is_relative_mode(struct sc_screen *screen) {
|
|
||||||
// screen->im.mp may be NULL if --no-control
|
|
||||||
return screen->im.mp && screen->im.mp->relative_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) {
|
screen_update_content_rect(struct screen *screen) {
|
||||||
#ifdef __APPLE__
|
|
||||||
// Workaround for SDL bug on macOS:
|
|
||||||
// <https://github.com/libsdl-org/SDL/issues/5340>
|
|
||||||
if (capture) {
|
|
||||||
int mouse_x, mouse_y;
|
|
||||||
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
|
|
||||||
|
|
||||||
int x, y, w, h;
|
|
||||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
|
||||||
SDL_GetWindowSize(screen->window, &w, &h);
|
|
||||||
|
|
||||||
bool outside_window = mouse_x < x || mouse_x >= x + w
|
|
||||||
|| mouse_y < y || mouse_y >= y + h;
|
|
||||||
if (outside_window) {
|
|
||||||
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void) screen;
|
|
||||||
#endif
|
|
||||||
if (SDL_SetRelativeMouseMode(capture)) {
|
|
||||||
LOGE("Could not set relative mouse mode to %s: %s",
|
|
||||||
capture ? "true" : "false", SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool
|
|
||||||
sc_screen_get_mouse_capture(struct sc_screen *screen) {
|
|
||||||
(void) screen;
|
|
||||||
return SDL_GetRelativeMouseMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
|
|
||||||
(void) screen;
|
|
||||||
bool new_value = !sc_screen_get_mouse_capture(screen);
|
|
||||||
sc_screen_set_mouse_capture(screen, new_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
|
||||||
int dw;
|
int dw;
|
||||||
int dh;
|
int dh;
|
||||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
// The drawable size is the window size * the HiDPI scale
|
// The drawable size is the window size * the HiDPI scale
|
||||||
struct sc_size drawable_size = {dw, dh};
|
struct size drawable_size = {dw, dh};
|
||||||
|
|
||||||
SDL_Rect *rect = &screen->rect;
|
SDL_Rect *rect = &screen->rect;
|
||||||
|
|
||||||
@@ -241,9 +199,9 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline SDL_Texture *
|
static inline SDL_Texture *
|
||||||
create_texture(struct sc_screen *screen) {
|
create_texture(struct screen *screen) {
|
||||||
SDL_Renderer *renderer = screen->renderer;
|
SDL_Renderer *renderer = screen->renderer;
|
||||||
struct sc_size size = screen->frame_size;
|
struct size size = screen->frame_size;
|
||||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
size.width, size.height);
|
size.width, size.height);
|
||||||
@@ -267,45 +225,6 @@ create_texture(struct sc_screen *screen) {
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the texture to the renderer
|
|
||||||
//
|
|
||||||
// Set the update_content_rect flag if the window or content size may have
|
|
||||||
// changed, so that the content rectangle is recomputed
|
|
||||||
static void
|
|
||||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
|
||||||
if (update_content_rect) {
|
|
||||||
sc_screen_update_content_rect(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderClear(screen->renderer);
|
|
||||||
if (screen->rotation == 0) {
|
|
||||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
|
||||||
} else {
|
|
||||||
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
|
||||||
// counterclockwise (to be consistent with --lock-video-orientation)
|
|
||||||
int cw_rotation = (4 - screen->rotation) % 4;
|
|
||||||
double angle = 90 * cw_rotation;
|
|
||||||
|
|
||||||
SDL_Rect *dstrect = NULL;
|
|
||||||
SDL_Rect rect;
|
|
||||||
if (screen->rotation & 1) {
|
|
||||||
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
|
|
||||||
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
|
|
||||||
rect.w = screen->rect.h;
|
|
||||||
rect.h = screen->rect.w;
|
|
||||||
dstrect = ▭
|
|
||||||
} else {
|
|
||||||
assert(screen->rotation == 2);
|
|
||||||
dstrect = &screen->rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
|
||||||
angle, NULL, 0);
|
|
||||||
}
|
|
||||||
SDL_RenderPresent(screen->renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||||
#endif
|
#endif
|
||||||
@@ -318,20 +237,20 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
|||||||
// <https://stackoverflow.com/a/40693139/1987178>
|
// <https://stackoverflow.com/a/40693139/1987178>
|
||||||
static int
|
static int
|
||||||
event_watcher(void *data, SDL_Event *event) {
|
event_watcher(void *data, SDL_Event *event) {
|
||||||
struct sc_screen *screen = data;
|
struct screen *screen = data;
|
||||||
if (event->type == SDL_WINDOWEVENT
|
if (event->type == SDL_WINDOWEVENT
|
||||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
// In practice, it seems to always be called from the same thread in
|
// In practice, it seems to always be called from the same thread in
|
||||||
// that specific case. Anyway, it's just a workaround.
|
// that specific case. Anyway, it's just a workaround.
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct screen *screen = DOWNCAST(sink);
|
||||||
(void) screen;
|
(void) screen;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
screen->open = true;
|
screen->open = true;
|
||||||
@@ -342,8 +261,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct screen *screen = DOWNCAST(sink);
|
||||||
(void) screen;
|
(void) screen;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
screen->open = false;
|
screen->open = false;
|
||||||
@@ -353,8 +272,8 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct screen *screen = DOWNCAST(sink);
|
||||||
return sc_video_buffer_push(&screen->vb, frame);
|
return sc_video_buffer_push(&screen->vb, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,54 +281,28 @@ static void
|
|||||||
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||||
void *userdata) {
|
void *userdata) {
|
||||||
(void) vb;
|
(void) vb;
|
||||||
struct sc_screen *screen = userdata;
|
struct screen *screen = userdata;
|
||||||
|
|
||||||
// event_failed implies previous_skipped (the previous frame may not have
|
|
||||||
// been consumed if the event was not sent)
|
|
||||||
assert(!screen->event_failed || previous_skipped);
|
|
||||||
|
|
||||||
bool need_new_event;
|
|
||||||
if (previous_skipped) {
|
if (previous_skipped) {
|
||||||
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead, unless the previous event failed
|
// this new frame instead
|
||||||
need_new_event = screen->event_failed;
|
|
||||||
} else {
|
} else {
|
||||||
need_new_event = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (need_new_event) {
|
|
||||||
static SDL_Event new_frame_event = {
|
static SDL_Event new_frame_event = {
|
||||||
.type = EVENT_NEW_FRAME,
|
.type = EVENT_NEW_FRAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
int ret = SDL_PushEvent(&new_frame_event);
|
SDL_PushEvent(&new_frame_event);
|
||||||
if (ret < 0) {
|
|
||||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
|
||||||
screen->event_failed = true;
|
|
||||||
} else {
|
|
||||||
screen->event_failed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_screen_init(struct sc_screen *screen,
|
screen_init(struct screen *screen, const struct screen_params *params) {
|
||||||
const struct sc_screen_params *params) {
|
|
||||||
screen->resize_pending = false;
|
screen->resize_pending = false;
|
||||||
screen->has_frame = false;
|
screen->has_frame = false;
|
||||||
screen->fullscreen = false;
|
screen->fullscreen = false;
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
screen->event_failed = false;
|
|
||||||
screen->mouse_capture_key_pressed = 0;
|
|
||||||
|
|
||||||
screen->req.x = params->window_x;
|
|
||||||
screen->req.y = params->window_y;
|
|
||||||
screen->req.width = params->window_width;
|
|
||||||
screen->req.height = params->window_height;
|
|
||||||
screen->req.fullscreen = params->fullscreen;
|
|
||||||
screen->req.start_fps_counter = params->start_fps_counter;
|
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
@@ -418,15 +311,18 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
|
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
|
||||||
screen);
|
screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
LOGE("Could not initialize video buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_video_buffer_start(&screen->vb);
|
ok = sc_video_buffer_start(&screen->vb);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
LOGE("Could not start video_buffer");
|
||||||
goto error_destroy_video_buffer;
|
goto error_destroy_video_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_fps_counter_init(&screen->fps_counter)) {
|
if (!fps_counter_init(&screen->fps_counter)) {
|
||||||
|
LOGE("Could not initialize FPS counter");
|
||||||
goto error_stop_and_join_video_buffer;
|
goto error_stop_and_join_video_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,32 +331,44 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
if (screen->rotation) {
|
if (screen->rotation) {
|
||||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||||
}
|
}
|
||||||
struct sc_size content_size =
|
struct size content_size =
|
||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
|
struct size window_size = get_initial_optimal_size(content_size,
|
||||||
|
params->window_width,
|
||||||
|
params->window_height);
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
| SDL_WINDOW_RESIZABLE
|
| SDL_WINDOW_RESIZABLE
|
||||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
if (params->always_on_top) {
|
if (params->always_on_top) {
|
||||||
|
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||||
|
#else
|
||||||
|
LOGW("The 'always on top' flag is not available "
|
||||||
|
"(compile with SDL >= 2.0.5 to enable it)");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (params->window_borderless) {
|
if (params->window_borderless) {
|
||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The window will be positioned and sized on first video frame
|
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||||
screen->window =
|
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
|
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
||||||
|
window_size.width, window_size.height,
|
||||||
|
window_flags);
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGE("Could not create window: %s", SDL_GetError());
|
LOGC("Could not create window: %s", SDL_GetError());
|
||||||
goto error_destroy_fps_counter;
|
goto error_destroy_fps_counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
||||||
SDL_RENDERER_ACCELERATED);
|
SDL_RENDERER_ACCELERATED);
|
||||||
if (!screen->renderer) {
|
if (!screen->renderer) {
|
||||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
LOGC("Could not create renderer: %s", SDL_GetError());
|
||||||
goto error_destroy_window;
|
goto error_destroy_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,10 +405,10 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface *icon = scrcpy_icon_load();
|
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||||
if (icon) {
|
if (icon) {
|
||||||
SDL_SetWindowIcon(screen->window, icon);
|
SDL_SetWindowIcon(screen->window, icon);
|
||||||
scrcpy_icon_destroy(icon);
|
SDL_FreeSurface(icon);
|
||||||
} else {
|
} else {
|
||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
@@ -509,38 +417,35 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
params->frame_size.height);
|
params->frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
screen->texture = create_texture(screen);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
goto error_destroy_renderer;
|
goto error_destroy_renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->frame = av_frame_alloc();
|
screen->frame = av_frame_alloc();
|
||||||
if (!screen->frame) {
|
if (!screen->frame) {
|
||||||
LOG_OOM();
|
LOGC("Could not create screen frame");
|
||||||
goto error_destroy_texture;
|
goto error_destroy_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_input_manager_params im_params = {
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||||
.controller = params->controller,
|
// HiDPI issues with some SDL renderers when several displays having
|
||||||
.fp = params->fp,
|
// different HiDPI scaling are connected
|
||||||
.screen = screen,
|
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
||||||
.kp = params->kp,
|
|
||||||
.mp = params->mp,
|
|
||||||
.forward_all_clicks = params->forward_all_clicks,
|
|
||||||
.legacy_paste = params->legacy_paste,
|
|
||||||
.clipboard_autosync = params->clipboard_autosync,
|
|
||||||
.shortcut_mods = params->shortcut_mods,
|
|
||||||
};
|
|
||||||
|
|
||||||
sc_input_manager_init(&screen->im, &im_params);
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
|
if (params->fullscreen) {
|
||||||
|
screen_switch_fullscreen(screen);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
SDL_AddEventWatch(event_watcher, screen);
|
SDL_AddEventWatch(event_watcher, screen);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct sc_frame_sink_ops ops = {
|
static const struct sc_frame_sink_ops ops = {
|
||||||
.open = sc_screen_frame_sink_open,
|
.open = screen_frame_sink_open,
|
||||||
.close = sc_screen_frame_sink_close,
|
.close = screen_frame_sink_close,
|
||||||
.push = sc_screen_frame_sink_push,
|
.push = screen_frame_sink_push,
|
||||||
};
|
};
|
||||||
|
|
||||||
screen->frame_sink.ops = &ops;
|
screen->frame_sink.ops = &ops;
|
||||||
@@ -558,7 +463,7 @@ error_destroy_renderer:
|
|||||||
error_destroy_window:
|
error_destroy_window:
|
||||||
SDL_DestroyWindow(screen->window);
|
SDL_DestroyWindow(screen->window);
|
||||||
error_destroy_fps_counter:
|
error_destroy_fps_counter:
|
||||||
sc_fps_counter_destroy(&screen->fps_counter);
|
fps_counter_destroy(&screen->fps_counter);
|
||||||
error_stop_and_join_video_buffer:
|
error_stop_and_join_video_buffer:
|
||||||
sc_video_buffer_stop(&screen->vb);
|
sc_video_buffer_stop(&screen->vb);
|
||||||
sc_video_buffer_join(&screen->vb);
|
sc_video_buffer_join(&screen->vb);
|
||||||
@@ -569,49 +474,29 @@ error_destroy_video_buffer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_screen_show_initial_window(struct sc_screen *screen) {
|
screen_show_window(struct screen *screen) {
|
||||||
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
|
|
||||||
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
|
|
||||||
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
|
|
||||||
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
|
|
||||||
|
|
||||||
struct sc_size window_size =
|
|
||||||
get_initial_optimal_size(screen->content_size, screen->req.width,
|
|
||||||
screen->req.height);
|
|
||||||
|
|
||||||
set_window_size(screen, window_size);
|
|
||||||
SDL_SetWindowPosition(screen->window, x, y);
|
|
||||||
|
|
||||||
if (screen->req.fullscreen) {
|
|
||||||
sc_screen_switch_fullscreen(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screen->req.start_fps_counter) {
|
|
||||||
sc_fps_counter_start(&screen->fps_counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_ShowWindow(screen->window);
|
SDL_ShowWindow(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_hide_window(struct sc_screen *screen) {
|
screen_hide_window(struct screen *screen) {
|
||||||
SDL_HideWindow(screen->window);
|
SDL_HideWindow(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_interrupt(struct sc_screen *screen) {
|
screen_interrupt(struct screen *screen) {
|
||||||
sc_video_buffer_stop(&screen->vb);
|
sc_video_buffer_stop(&screen->vb);
|
||||||
sc_fps_counter_interrupt(&screen->fps_counter);
|
fps_counter_interrupt(&screen->fps_counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_join(struct sc_screen *screen) {
|
screen_join(struct screen *screen) {
|
||||||
sc_video_buffer_join(&screen->vb);
|
sc_video_buffer_join(&screen->vb);
|
||||||
sc_fps_counter_join(&screen->fps_counter);
|
fps_counter_join(&screen->fps_counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_destroy(struct sc_screen *screen) {
|
screen_destroy(struct screen *screen) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(!screen->open);
|
assert(!screen->open);
|
||||||
#endif
|
#endif
|
||||||
@@ -619,26 +504,26 @@ sc_screen_destroy(struct sc_screen *screen) {
|
|||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
SDL_DestroyRenderer(screen->renderer);
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
SDL_DestroyWindow(screen->window);
|
SDL_DestroyWindow(screen->window);
|
||||||
sc_fps_counter_destroy(&screen->fps_counter);
|
fps_counter_destroy(&screen->fps_counter);
|
||||||
sc_video_buffer_destroy(&screen->vb);
|
sc_video_buffer_destroy(&screen->vb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
resize_for_content(struct screen *screen, struct size old_content_size,
|
||||||
struct sc_size new_content_size) {
|
struct size new_content_size) {
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
struct sc_size target_size = {
|
struct size target_size = {
|
||||||
.width = (uint32_t) window_size.width * new_content_size.width
|
.width = (uint32_t) window_size.width * new_content_size.width
|
||||||
/ old_content_size.width,
|
/ old_content_size.width,
|
||||||
.height = (uint32_t) window_size.height * new_content_size.height
|
.height = (uint32_t) window_size.height * new_content_size.height
|
||||||
/ old_content_size.height,
|
/ old_content_size.height,
|
||||||
};
|
};
|
||||||
target_size = get_optimal_size(target_size, new_content_size, true);
|
target_size = get_optimal_size(target_size, new_content_size);
|
||||||
set_window_size(screen, target_size);
|
set_window_size(screen, target_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
set_content_size(struct screen *screen, struct size new_content_size) {
|
||||||
if (!screen->fullscreen && !screen->maximized) {
|
if (!screen->fullscreen && !screen->maximized) {
|
||||||
resize_for_content(screen, screen->content_size, new_content_size);
|
resize_for_content(screen, screen->content_size, new_content_size);
|
||||||
} else if (!screen->resize_pending) {
|
} else if (!screen->resize_pending) {
|
||||||
@@ -652,7 +537,7 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
apply_pending_resize(struct sc_screen *screen) {
|
apply_pending_resize(struct screen *screen) {
|
||||||
assert(!screen->fullscreen);
|
assert(!screen->fullscreen);
|
||||||
assert(!screen->maximized);
|
assert(!screen->maximized);
|
||||||
if (screen->resize_pending) {
|
if (screen->resize_pending) {
|
||||||
@@ -663,13 +548,13 @@ apply_pending_resize(struct sc_screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
screen_set_rotation(struct screen *screen, unsigned rotation) {
|
||||||
assert(rotation < 4);
|
assert(rotation < 4);
|
||||||
if (rotation == screen->rotation) {
|
if (rotation == screen->rotation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(screen->frame_size, rotation);
|
get_rotated_size(screen->frame_size, rotation);
|
||||||
|
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
@@ -677,12 +562,12 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
|||||||
screen->rotation = rotation;
|
screen->rotation = rotation;
|
||||||
LOGI("Display rotation set to %u", rotation);
|
LOGI("Display rotation set to %u", rotation);
|
||||||
|
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static bool
|
static bool
|
||||||
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||||
if (screen->frame_size.width != new_frame_size.width
|
if (screen->frame_size.width != new_frame_size.width
|
||||||
|| screen->frame_size.height != new_frame_size.height) {
|
|| screen->frame_size.height != new_frame_size.height) {
|
||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
@@ -690,17 +575,17 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
|||||||
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(new_frame_size, screen->rotation);
|
get_rotated_size(new_frame_size, screen->rotation);
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
sc_screen_update_content_rect(screen);
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
screen->texture = create_texture(screen);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,7 +595,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
|||||||
|
|
||||||
// write the frame into the texture
|
// write the frame into the texture
|
||||||
static void
|
static void
|
||||||
update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
update_texture(struct screen *screen, const AVFrame *frame) {
|
||||||
SDL_UpdateYUVTexture(screen->texture, NULL,
|
SDL_UpdateYUVTexture(screen->texture, NULL,
|
||||||
frame->data[0], frame->linesize[0],
|
frame->data[0], frame->linesize[0],
|
||||||
frame->data[1], frame->linesize[1],
|
frame->data[1], frame->linesize[1],
|
||||||
@@ -724,36 +609,59 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_screen_update_frame(struct sc_screen *screen) {
|
screen_update_frame(struct screen *screen) {
|
||||||
av_frame_unref(screen->frame);
|
av_frame_unref(screen->frame);
|
||||||
sc_video_buffer_consume(&screen->vb, screen->frame);
|
sc_video_buffer_consume(&screen->vb, screen->frame);
|
||||||
AVFrame *frame = screen->frame;
|
AVFrame *frame = screen->frame;
|
||||||
|
|
||||||
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||||
|
|
||||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
struct size new_frame_size = {frame->width, frame->height};
|
||||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
update_texture(screen, frame);
|
update_texture(screen, frame);
|
||||||
|
|
||||||
if (!screen->has_frame) {
|
screen_render(screen, false);
|
||||||
screen->has_frame = true;
|
|
||||||
// this is the very first frame, show the window
|
|
||||||
sc_screen_show_initial_window(screen);
|
|
||||||
|
|
||||||
if (sc_screen_is_relative_mode(screen)) {
|
|
||||||
// Capture mouse on start
|
|
||||||
sc_screen_set_mouse_capture(screen, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_screen_render(screen, false);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
screen_render(struct screen *screen, bool update_content_rect) {
|
||||||
|
if (update_content_rect) {
|
||||||
|
screen_update_content_rect(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_RenderClear(screen->renderer);
|
||||||
|
if (screen->rotation == 0) {
|
||||||
|
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
||||||
|
} else {
|
||||||
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
||||||
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
||||||
|
int cw_rotation = (4 - screen->rotation) % 4;
|
||||||
|
double angle = 90 * cw_rotation;
|
||||||
|
|
||||||
|
SDL_Rect *dstrect = NULL;
|
||||||
|
SDL_Rect rect;
|
||||||
|
if (screen->rotation & 1) {
|
||||||
|
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
|
||||||
|
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
|
||||||
|
rect.w = screen->rect.h;
|
||||||
|
rect.h = screen->rect.w;
|
||||||
|
dstrect = ▭
|
||||||
|
} else {
|
||||||
|
assert(screen->rotation == 2);
|
||||||
|
dstrect = &screen->rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
||||||
|
angle, NULL, 0);
|
||||||
|
}
|
||||||
|
SDL_RenderPresent(screen->renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_switch_fullscreen(struct screen *screen) {
|
||||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||||
@@ -766,20 +674,20 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
screen_resize_to_fit(struct screen *screen) {
|
||||||
if (screen->fullscreen || screen->maximized) {
|
if (screen->fullscreen || screen->maximized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point point = get_window_position(screen);
|
struct point point = get_window_position(screen);
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
|
|
||||||
struct sc_size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_size(window_size, screen->content_size, false);
|
get_optimal_size(window_size, screen->content_size);
|
||||||
|
|
||||||
// Center the window related to the device screen
|
// Center the window related to the device screen
|
||||||
assert(optimal_size.width <= window_size.width);
|
assert(optimal_size.width <= window_size.width);
|
||||||
@@ -794,7 +702,7 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||||
if (screen->fullscreen) {
|
if (screen->fullscreen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -804,40 +712,37 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
|||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||||
content_size.height);
|
content_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
bool
|
||||||
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
|
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||||
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|
||||||
bool relative_mode = sc_screen_is_relative_mode(screen);
|
|
||||||
|
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_NEW_FRAME: {
|
case EVENT_NEW_FRAME:
|
||||||
bool ok = sc_screen_update_frame(screen);
|
if (!screen->has_frame) {
|
||||||
|
screen->has_frame = true;
|
||||||
|
// this is the very first frame, show the window
|
||||||
|
screen_show_window(screen);
|
||||||
|
}
|
||||||
|
bool ok = screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Frame update failed\n");
|
LOGW("Frame update failed\n");
|
||||||
}
|
}
|
||||||
return;
|
return true;
|
||||||
}
|
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
if (!screen->has_frame) {
|
if (!screen->has_frame) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
switch (event->window.event) {
|
switch (event->window.event) {
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||||
screen->maximized = true;
|
screen->maximized = true;
|
||||||
@@ -853,79 +758,18 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
}
|
}
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
apply_pending_resize(screen);
|
apply_pending_resize(screen);
|
||||||
sc_screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
||||||
if (relative_mode) {
|
|
||||||
sc_screen_set_mouse_capture(screen, false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return true;
|
||||||
case SDL_KEYDOWN:
|
|
||||||
if (relative_mode) {
|
|
||||||
SDL_Keycode key = event->key.keysym.sym;
|
|
||||||
if (sc_screen_is_mouse_capture_key(key)) {
|
|
||||||
if (!screen->mouse_capture_key_pressed) {
|
|
||||||
screen->mouse_capture_key_pressed = key;
|
|
||||||
} else {
|
|
||||||
// Another mouse capture key has been pressed, cancel
|
|
||||||
// mouse (un)capture
|
|
||||||
screen->mouse_capture_key_pressed = 0;
|
|
||||||
}
|
|
||||||
// Mouse capture keys are never forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_KEYUP:
|
|
||||||
if (relative_mode) {
|
|
||||||
SDL_Keycode key = event->key.keysym.sym;
|
|
||||||
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
|
||||||
screen->mouse_capture_key_pressed = 0;
|
|
||||||
if (sc_screen_is_mouse_capture_key(key)) {
|
|
||||||
if (key == cap) {
|
|
||||||
// A mouse capture key has been pressed then released:
|
|
||||||
// toggle the capture mouse mode
|
|
||||||
sc_screen_toggle_mouse_capture(screen);
|
|
||||||
}
|
|
||||||
// Mouse capture keys are never forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEWHEEL:
|
|
||||||
case SDL_MOUSEMOTION:
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
|
||||||
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
|
|
||||||
// Do not forward to input manager, the mouse will be captured
|
|
||||||
// on SDL_MOUSEBUTTONUP
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_FINGERMOTION:
|
|
||||||
case SDL_FINGERDOWN:
|
|
||||||
case SDL_FINGERUP:
|
|
||||||
if (relative_mode) {
|
|
||||||
// Touch events are not compatible with relative mode
|
|
||||||
// (coordinates are not relative)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
|
||||||
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
|
|
||||||
sc_screen_set_mouse_capture(screen, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_input_manager_handle_event(&screen->im, event);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
unsigned rotation = screen->rotation;
|
unsigned rotation = screen->rotation;
|
||||||
assert(rotation < 4);
|
assert(rotation < 4);
|
||||||
|
|
||||||
@@ -937,7 +781,7 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
|||||||
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
struct sc_point result;
|
struct point result;
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
case 0:
|
case 0:
|
||||||
result.x = x;
|
result.x = x;
|
||||||
@@ -960,15 +804,15 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
sc_screen_hidpi_scale_coords(screen, &x, &y);
|
screen_hidpi_scale_coords(screen, &x, &y);
|
||||||
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
|
return screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
||||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||||
int ww, wh, dw, dh;
|
int ww, wh, dw, dh;
|
||||||
SDL_GetWindowSize(screen->window, &ww, &wh);
|
SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||||
|
|||||||
@@ -7,48 +7,33 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "input_manager.h"
|
|
||||||
#include "opengl.h"
|
#include "opengl.h"
|
||||||
#include "trait/key_processor.h"
|
|
||||||
#include "trait/frame_sink.h"
|
#include "trait/frame_sink.h"
|
||||||
#include "trait/mouse_processor.h"
|
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
|
|
||||||
struct sc_screen {
|
struct screen {
|
||||||
struct sc_frame_sink frame_sink; // frame sink trait
|
struct sc_frame_sink frame_sink; // frame sink trait
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
bool open; // track the open/close state to assert correct behavior
|
bool open; // track the open/close state to assert correct behavior
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct sc_input_manager im;
|
|
||||||
struct sc_video_buffer vb;
|
struct sc_video_buffer vb;
|
||||||
struct sc_fps_counter fps_counter;
|
struct fps_counter fps_counter;
|
||||||
|
|
||||||
// The initial requested window properties
|
|
||||||
struct {
|
|
||||||
int16_t x;
|
|
||||||
int16_t y;
|
|
||||||
uint16_t width;
|
|
||||||
uint16_t height;
|
|
||||||
bool fullscreen;
|
|
||||||
bool start_fps_counter;
|
|
||||||
} req;
|
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
struct sc_opengl gl;
|
struct sc_opengl gl;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
struct sc_size content_size; // rotated frame_size
|
struct size content_size; // rotated frame_size
|
||||||
|
|
||||||
bool resize_pending; // resize requested while fullscreen or maximized
|
bool resize_pending; // resize requested while fullscreen or maximized
|
||||||
// The content size the last time the window was not maximized or
|
// The content size the last time the window was not maximized or
|
||||||
// fullscreen (meaningful only when resize_pending is true)
|
// fullscreen (meaningful only when resize_pending is true)
|
||||||
struct sc_size windowed_content_size;
|
struct size windowed_content_size;
|
||||||
|
|
||||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||||
unsigned rotation;
|
unsigned rotation;
|
||||||
@@ -59,34 +44,18 @@ struct sc_screen {
|
|||||||
bool maximized;
|
bool maximized;
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
|
|
||||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
|
||||||
|
|
||||||
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
|
|
||||||
// RGUI) must be pressed. This variable tracks the pressed capture key.
|
|
||||||
SDL_Keycode mouse_capture_key_pressed;
|
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_screen_params {
|
struct screen_params {
|
||||||
struct sc_controller *controller;
|
|
||||||
struct sc_file_pusher *fp;
|
|
||||||
struct sc_key_processor *kp;
|
|
||||||
struct sc_mouse_processor *mp;
|
|
||||||
|
|
||||||
bool forward_all_clicks;
|
|
||||||
bool legacy_paste;
|
|
||||||
bool clipboard_autosync;
|
|
||||||
const struct sc_shortcut_mods *shortcut_mods;
|
|
||||||
|
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|
||||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
int16_t window_x;
|
||||||
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
int16_t window_y;
|
||||||
uint16_t window_width;
|
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
uint16_t window_height;
|
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
|
||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
|
|
||||||
@@ -94,72 +63,78 @@ struct sc_screen_params {
|
|||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
|
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool start_fps_counter;
|
|
||||||
|
|
||||||
sc_tick buffering_time;
|
sc_tick buffering_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
// initialize screen, create window, renderer and texture (window is hidden)
|
// initialize screen, create window, renderer and texture (window is hidden)
|
||||||
bool
|
bool
|
||||||
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
|
screen_init(struct screen *screen, const struct screen_params *params);
|
||||||
|
|
||||||
// request to interrupt any inner thread
|
// request to interrupt any inner thread
|
||||||
// must be called before screen_join()
|
// must be called before screen_join()
|
||||||
void
|
void
|
||||||
sc_screen_interrupt(struct sc_screen *screen);
|
screen_interrupt(struct screen *screen);
|
||||||
|
|
||||||
// join any inner thread
|
// join any inner thread
|
||||||
void
|
void
|
||||||
sc_screen_join(struct sc_screen *screen);
|
screen_join(struct screen *screen);
|
||||||
|
|
||||||
// destroy window, renderer and texture (if any)
|
// destroy window, renderer and texture (if any)
|
||||||
void
|
void
|
||||||
sc_screen_destroy(struct sc_screen *screen);
|
screen_destroy(struct screen *screen);
|
||||||
|
|
||||||
// hide the window
|
// hide the window
|
||||||
//
|
//
|
||||||
// It is used to hide the window immediately on closing without waiting for
|
// It is used to hide the window immediately on closing without waiting for
|
||||||
// screen_destroy()
|
// screen_destroy()
|
||||||
void
|
void
|
||||||
sc_screen_hide_window(struct sc_screen *screen);
|
screen_hide_window(struct screen *screen);
|
||||||
|
|
||||||
|
// render the texture to the renderer
|
||||||
|
//
|
||||||
|
// Set the update_content_rect flag if the window or content size may have
|
||||||
|
// changed, so that the content rectangle is recomputed
|
||||||
|
void
|
||||||
|
screen_render(struct screen *screen, bool update_content_rect);
|
||||||
|
|
||||||
// switch the fullscreen mode
|
// switch the fullscreen mode
|
||||||
void
|
void
|
||||||
sc_screen_switch_fullscreen(struct sc_screen *screen);
|
screen_switch_fullscreen(struct screen *screen);
|
||||||
|
|
||||||
// resize window to optimal size (remove black borders)
|
// resize window to optimal size (remove black borders)
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_fit(struct sc_screen *screen);
|
screen_resize_to_fit(struct screen *screen);
|
||||||
|
|
||||||
// resize window to 1:1 (pixel-perfect)
|
// resize window to 1:1 (pixel-perfect)
|
||||||
void
|
void
|
||||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
|
screen_resize_to_pixel_perfect(struct screen *screen);
|
||||||
|
|
||||||
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
|
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
|
||||||
void
|
void
|
||||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
|
screen_set_rotation(struct screen *screen, unsigned rotation);
|
||||||
|
|
||||||
// react to SDL events
|
// react to SDL events
|
||||||
void
|
bool
|
||||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
|
screen_handle_event(struct screen *screen, SDL_Event *event);
|
||||||
|
|
||||||
// convert point from window coordinates to frame coordinates
|
// convert point from window coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
// convert point from drawable coordinates to frame coordinates
|
// convert point from drawable coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
// Convert coordinates from window to drawable.
|
// Convert coordinates from window to drawable.
|
||||||
// Events are expressed in window coordinates, but content is expressed in
|
// Events are expressed in window coordinates, but content is expressed in
|
||||||
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
|
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
|
||||||
// otherwise.
|
// otherwise.
|
||||||
void
|
void
|
||||||
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y);
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
1157
app/src/server.c
1157
app/src/server.c
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user