Compare commits

..

6 Commits

Author SHA1 Message Date
c37d455fa2 wip 2021-05-02 18:27:13 +02:00
6e8df74c41 serial in server_params 2021-05-02 17:22:03 +02:00
52f5c6d4c1 server_params_copy 2021-05-02 17:22:03 +02:00
efb531943d reorder server server_params 2021-05-02 17:22:03 +02:00
4dcda82582 ARRAY_LEN 2021-05-02 17:22:03 +02:00
5369c4f754 Serialize clean-up configuration
This avoids to pass each option as individual parameter and parse them
manually (it's still "manual" in the Parcelable implementation).

Refs #824 <https://github.com/Genymobile/scrcpy/pull/824#issuecomment-780319422>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
2021-05-01 14:14:32 +02:00
227 changed files with 9018 additions and 18852 deletions

164
BUILD.md
View File

@ -2,51 +2,18 @@
Here are the instructions to build _scrcpy_ (client and server).
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
## Simple
If you just want to install the latest release from `master`, follow this
simplified process.
First, you need to install the required packages:
```bash
# for Debian/Ubuntu
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0 libusb-1.0-0-dev
```
Then clone the repo and execute the installation script
([source](install_release.sh)):
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
./install_release.sh
```
When a new release is out, update the repo and reinstall:
```bash
git pull
./install_release.sh
```
To uninstall:
```bash
sudo ninja -Cbuild-auto uninstall
```
[prebuilt server]: #prebuilt-server
## Branches
### `master`
The `master` branch concerns the latest release, and is the home page of the
project on GitHub.
project on Github.
### `dev`
@ -89,15 +56,15 @@ Install the required packages from your package manager.
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
sudo apt install ffmpeg libsdl2-2.0-0 adb
# client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0-dev
sudo apt install gcc git pkg-config meson ninja-build \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-11-jdk
sudo apt install openjdk-8-jdk
```
On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install
@ -116,7 +83,7 @@ pip3 install meson
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java-devel
@ -139,13 +106,13 @@ sudo apt install mingw-w64 mingw-w64-tools
You also need the JDK to build the server:
```bash
sudo apt install openjdk-11-jdk
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
./release.sh
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
@ -161,8 +128,7 @@ install the required packages:
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg \
mingw-w64-x86_64-libusb
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
@ -176,8 +142,7 @@ For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg \
mingw-w64-i686-libusb
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
@ -201,19 +166,19 @@ Install the packages with [Homebrew]:
```bash
# runtime dependencies
brew install sdl2 ffmpeg libusb
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it available from the `PATH`:
make it avaliable from the `PATH`:
```bash
brew tap homebrew/cask-versions
brew install adoptopenjdk/openjdk/adoptopenjdk11
export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)"
brew cask install adoptopenjdk/openjdk/adoptopenjdk8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
@ -224,27 +189,8 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps
**As a non-root user**, clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
### Build
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #option-2-use-prebuilt-server
#### Option 1: Build everything from sources
Install the [Android SDK] (_Android Studio_), and set `ANDROID_SDK_ROOT` to its
directory. For example:
If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_SDK_ROOT` to its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
@ -257,11 +203,20 @@ export ANDROID_SDK_ROOT=~/Library/Android/sdk
set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
```
If you don't want to build the server, use the [prebuilt server].
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson setup x --buildtype=release --strip -Db_lto=true
ninja -Cx # DO NOT RUN AS ROOT
meson x --buildtype release --strip -Db_lto=true
ninja -Cx
```
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
@ -270,27 +225,9 @@ install` must be run as root)._
[ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a
#### Option 2: Use prebuilt server
### Run
- [`scrcpy-server-v1.25`][direct-scrcpy-server]
<sub>SHA-256: `ce0306c7bbd06ae72f6d06f7ec0ee33774995a65de71e0a83813ecb67aec9bdb`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.25/scrcpy-server-v1.25
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson setup x --buildtype=release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx # DO NOT RUN AS ROOT
```
The server only works with a matching client version (this server works with the
`master` branch).
### Run without installing:
To run without installing:
```bash
./run x [options]
@ -305,19 +242,32 @@ After a successful build, you can install _scrcpy_ on the system:
sudo ninja -Cx install # without sudo on Windows
```
This installs several files:
This installs two files:
- `/usr/local/bin/scrcpy` (main app)
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server`
You can then [run](README.md#run) `scrcpy`.
Just remove them to "uninstall" the application.
### Uninstall
You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.17`][direct-scrcpy-server]
_(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
sudo ninja -Cx uninstall # without sudo on Windows
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server
ninja -Cx
sudo ninja -Cx install
```
The server only works with a matching client version (this server works with the
`master` branch).

View File

@ -76,7 +76,7 @@ The server uses 3 threads:
- the **main** thread, encoding and streaming the video to the client;
- the **controller** thread, listening for _control messages_ (typically,
keyboard and mouse events) from the client;
- the **receiver** thread (managed by the controller), sending _device messages_
- the **receiver** thread (managed by the controller), sending _device messges_
to the clients (currently, it is only used to send the device clipboard
content).
@ -211,7 +211,7 @@ There are two [frames][video_buffer] simultaneously in memory:
- the **rendering** frame, rendered in a texture from the main thread.
When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediately starts
rendering frame (with proper synchronization). Thus, it immediatly starts
to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw
@ -277,7 +277,7 @@ The server is pushed to the device by the client on startup.
To debug it, enable the server debugger during configuration:
```bash
meson setup x -Dserver_debugger=true
meson x -Dserver_debugger=true
# or, if x is already configured
meson configure x -Dserver_debugger=true
```
@ -286,7 +286,7 @@ If your device runs Android 8 or below, set the `server_debugger_method` to
`old` in addition:
```bash
meson setup x -Dserver_debugger=true -Dserver_debugger_method=old
meson x -Dserver_debugger=true -Dserver_debugger_method=old
# or, if x is already configured
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
```

84
FAQ.ko.md Normal file
View File

@ -0,0 +1,84 @@
# 자주하는 질문 (FAQ)
다음은 자주 제보되는 문제들과 그들의 현황입니다.
### Windows 운영체제에서, 디바이스가 발견되지 않습니다.
가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다.
다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요:
adb devices
Windows는 당신의 디바이스를 감지하기 위해 [드라이버]가 필요할 수도 있습니다.
[드라이버]: https://developer.android.com/studio/run/oem-usb.html
### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.
일부 디바이스에서는, [simulating input]을 허용하기 위해서 한가지 옵션을 활성화해야 할 수도 있습니다.
개발자 옵션에서 (developer options) 다음을 활성화 하세요:
> **USB debugging (Security settings)**
> _권한 부여와 USB 디버깅을 통한 simulating input을 허용한다_
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### 마우스 클릭이 다른 곳에 적용됩니다.
Mac 운영체제에서, HiDPI support 와 여러 스크린 창이 있는 경우, 입력 위치가 잘못 파악될 수 있습니다.
[issue 15]를 참고하세요.
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
차선책은 HiDPI support을 비활성화 하고 build하는 방법입니다:
```bash
meson x --buildtype release -Dhidpi_support=false
```
하지만, 동영상은 낮은 해상도로 재생될 것 입니다.
### HiDPI display의 화질이 낮습니다.
Windows에서는, [scaling behavior] 환경을 설정해야 할 수도 있습니다.
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor가 실행되지 않습니다
Plasma Desktop에서는,_scrcpy_ 가 실행중에는 compositor가 비활성화 됩니다.
차석책으로는, ["Block compositing"를 비활성화하세요][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
###비디오 스트림을 열 수 없는 에러가 발생합니다.(Could not open video stream).
여러가지 원인이 있을 수 있습니다. 가장 흔한 원인은 디바이스의 하드웨어 인코더(hardware encoder)가
주어진 해상도를 인코딩할 수 없는 경우입니다.
```
ERROR: Exception on thread Thread[main,5,main]
android.media.MediaCodec$CodecException: Error 0xfffffc0e
...
Exit due to uncaughtException in main thread:
ERROR: Could not open video stream
INFO: Initial texture: 1080x2336
```
더 낮은 해상도로 시도 해보세요:
```
scrcpy -m 1920
scrcpy -m 1024
scrcpy -m 800
```

188
FAQ.md
View File

@ -1,19 +1,24 @@
# Frequently Asked Questions
[Read in another language](#translations)
Here are the common reported problems and their status.
If you encounter any error, the first step is to upgrade to the latest version.
## `adb` issues
`scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work.
In that case, it will print this error:
> ERROR: "adb push" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment.
To find out the cause, execute:
```bash
adb devices
```
### `adb` not found
@ -23,63 +28,35 @@ On Windows, the current directory is in your `PATH`, and `adb.exe` is included
in the release, so it should work out-of-the-box.
### Device not detected
> ERROR: Could not find any ADB device
Check that you correctly enabled [adb debugging][enable-adb].
Your device must be detected by `adb`:
```
adb devices
```
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[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].
Check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
> adb: error: failed to get feature set: no devices/emulators found
If your device is not detected, you may need some [drivers] (on Windows).
[drivers]: https://developer.android.com/studio/run/oem-usb.html
### Several devices connected
If several devices are connected, you will encounter this error:
> ERROR: Multiple (2) ADB devices:
> ERROR: --> (usb) 0123456789abcdef device Nexus_5
> ERROR: --> (tcpip) 192.168.1.5:5555 device GM1913
> ERROR: Select a device via -s (--serial), -d (--select-usb) or -e (--select-tcpip)
> adb: error: failed to get feature set: more than one device/emulator
In that case, you can either provide the identifier of the device you want to
mirror:
the identifier of the device you want to mirror must be provided:
```bash
scrcpy -s 0123456789abcdef
scrcpy -s 01234567890abcdef
```
Or request the single USB (or TCP/IP) device:
```bash
scrcpy -d # USB device
scrcpy -e # TCP/IP device
```
Note that if your device is connected over TCP/IP, you might get this message:
Note that if your device is connected over TCP/IP, you'll get this message:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
@ -103,20 +80,7 @@ You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to
use a specific `adb` binary, by setting the `ADB` environment variable:
```bash
# in bash
export ADB=/path/to/your/adb
scrcpy
```
```cmd
:: in cmd
set ADB=C:\path\to\your\adb.exe
scrcpy
```
```powershell
# in PowerShell
$env:ADB = 'C:\path\to\your\adb.exe'
set ADB=/path/to/your/adb
scrcpy
```
@ -147,19 +111,25 @@ In developer options, enable:
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [#15].
[#15]: https://github.com/Genymobile/scrcpy/issues/15
Open _scrcpy_ directly on the monitor you use it.
### Special characters do not work
The default text injection method is [limited to ASCII characters][text-input].
A trick allows to also inject some [accented characters][accented-characters],
but that's all. See [#37].
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
keyboard][hid] (HID).
Injecting text input is [limited to ASCII characters][text-input]. A trick
allows to also inject some [accented characters][accented-characters], but
that's all. See [#37].
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## Client issues
@ -171,44 +141,22 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40
This problem should be fixed in scrcpy v1.22: **update to the latest version**.
To improve downscaling quality, trilinear filtering is enabled automatically
if the renderer is OpenGL and if it supports mipmapping.
On older versions, you must configure the [scaling behavior]:
On Windows, you might want to force OpenGL:
```
scrcpy --render-driver=opengl
```
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
Also, to improve downscaling quality, trilinear filtering is enabled
automatically if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL to enable mipmapping:
```
scrcpy --render-driver=opengl
```
### Issue with Wayland
By default, SDL uses x11 on Linux. The [video driver] can be changed via the
`SDL_VIDEODRIVER` environment variable:
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
On some distributions (at least Fedora), the package `libdecor` must be
installed manually.
See issues [#2554] and [#2559].
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### KWin compositor crashes
@ -252,44 +200,13 @@ scrcpy -m 1024
scrcpy -m 800
```
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
before failing. This behavior can be disabled with `--no-downsize-on-error`.
You could also try another [encoder](README.md#encoder).
If you encounter this exception on Android 12, then just upgrade to scrcpy >=
1.18 (see [#2129]):
```
> ERROR: Exception on thread Thread[main,5,main]
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
... 7 more
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
... 9 more
```
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
## Command line on Windows
Since v1.22, a "shortcut" has been added to directly open a terminal in the
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
command. For example:
```
scrcpy --record file.mkv
```
You could also open a terminal and go to the scrcpy folder manually:
Some Windows users are not familiar with the command line. Here is how to open a
terminal and run `scrcpy` with arguments:
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.
@ -320,12 +237,3 @@ You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments.
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
## Translations
Translations of this FAQ in other languages are available in the [wiki].
[wiki]: https://github.com/Genymobile/scrcpy/wiki
Only this README file is guaranteed to be up-to-date.

View File

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

699
README.id.md Normal file
View File

@ -0,0 +1,699 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.16)
Aplikasi ini menyediakan tampilan dan kontrol perangkat Android yang terhubung pada USB (atau [melalui TCP/IP][article-tcpip]). Ini tidak membutuhkan akses _root_ apa pun. Ini bekerja pada _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Ini berfokus pada:
- **keringanan** (asli, hanya menampilkan layar perangkat)
- **kinerja** (30~60fps)
- **kualitas** (1920×1080 atau lebih)
- **latensi** rendah ([35~70ms][lowlatency])
- **waktu startup rendah** (~1 detik untuk menampilkan gambar pertama)
- **tidak mengganggu** (tidak ada yang terpasang di perangkat)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Persyaratan
Perangkat Android membutuhkan setidaknya API 21 (Android 5.0).
Pastikan Anda [mengaktifkan debugging adb][enable-adb] pada perangkat Anda.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Di beberapa perangkat, Anda juga perlu mengaktifkan [opsi tambahan][control] untuk mengontrolnya menggunakan keyboard dan mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Dapatkan aplikasinya
### Linux
Di Debian (_testing_ dan _sid_ untuk saat ini) dan Ubuntu (20.04):
```
apt install scrcpy
```
Paket [Snap] tersedia: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Untuk Fedora, paket [COPR] tersedia: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Untuk Arch Linux, paket [AUR] tersedia: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Untuk Gentoo, tersedia [Ebuild]: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak terlalu sulit).
### Windows
Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia :
- [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
Ini juga tersedia di [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # jika Anda belum memilikinya
```
Dan di [Scoop]:
```bash
scoop install scrcpy
scoop install adb # jika Anda belum memilikinya
```
[Scoop]: https://scoop.sh
Anda juga dapat [membangun aplikasi secara manual][BUILD].
### macOS
Aplikasi ini tersedia di [Homebrew]. Instal saja:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Anda membutuhkan `adb`, dapat diakses dari `PATH` Anda. Jika Anda belum memilikinya:
```bash
brew cask install android-platform-tools
```
Anda juga dapat [membangun aplikasi secara manual][BUILD].
## Menjalankan
Pasang perangkat Android, dan jalankan:
```bash
scrcpy
```
Ini menerima argumen baris perintah, didaftarkan oleh:
```bash
scrcpy --help
```
## Fitur
### Menangkap konfigurasi
#### Mengurangi ukuran
Kadang-kadang, berguna untuk mencerminkan perangkat Android dengan definisi yang lebih rendah untuk meningkatkan kinerja.
Untuk membatasi lebar dan tinggi ke beberapa nilai (mis. 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versi pendek
```
Dimensi lain dihitung agar rasio aspek perangkat dipertahankan.
Dengan begitu, perangkat 1920×1080 akan dicerminkan pada 1024×576.
#### Ubah kecepatan bit
Kecepatan bit default adalah 8 Mbps. Untuk mengubah bitrate video (mis. Menjadi 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versi pendek
```
#### Batasi frekuensi gambar
Kecepatan bingkai pengambilan dapat dibatasi:
```bash
scrcpy --max-fps 15
```
Ini secara resmi didukung sejak Android 10, tetapi dapat berfungsi pada versi sebelumnya.
#### Memotong
Layar perangkat dapat dipotong untuk mencerminkan hanya sebagian dari layar.
Ini berguna misalnya untuk mencerminkan hanya satu mata dari Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 Mengimbangi (0,0)
```
Jika `--max-size` juga ditentukan, pengubahan ukuran diterapkan setelah pemotongan.
#### Kunci orientasi video
Untuk mengunci orientasi pencerminan:
```bash
scrcpy --lock-video-orientation 0 # orientasi alami
scrcpy --lock-video-orientation 1 # 90° berlawanan arah jarum jam
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° searah jarum jam
```
Ini mempengaruhi orientasi perekaman.
### Rekaman
Anda dapat merekam layar saat melakukan mirroring:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Untuk menonaktifkan pencerminan saat merekam:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# berhenti merekam dengan Ctrl+C
```
"Skipped frames" are recorded, even if they are not displayed in real time (for
performance reasons). Frames are _timestamped_ on the device, so [packet delay
variation] does not impact the recorded file.
"Frame yang dilewati" direkam, meskipun tidak ditampilkan secara real time (untuk alasan performa). Bingkai *diberi stempel waktu* pada perangkat, jadi [variasi penundaan paket] tidak memengaruhi file yang direkam.
[variasi penundaan paket]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Koneksi
#### Wireless
_Scrcpy_ menggunakan `adb` untuk berkomunikasi dengan perangkat, dan` adb` dapat [terhubung] ke perangkat melalui TCP / IP:
1. Hubungkan perangkat ke Wi-Fi yang sama dengan komputer Anda.
2. Dapatkan alamat IP perangkat Anda (dalam Pengaturan → Tentang ponsel → Status).
3. Aktifkan adb melalui TCP / IP pada perangkat Anda: `adb tcpip 5555`.
4. Cabut perangkat Anda.
5. Hubungkan ke perangkat Anda: `adb connect DEVICE_IP: 5555` (*ganti* *`DEVICE_IP`*).
6. Jalankan `scrcpy` seperti biasa.
Mungkin berguna untuk menurunkan kecepatan bit dan definisi:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versi pendek
```
[terhubung]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Multi-perangkat
Jika beberapa perangkat dicantumkan di `adb devices`, Anda harus menentukan _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versi pendek
```
If the device is connected over TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versi pendek
```
Anda dapat memulai beberapa contoh _scrcpy_ untuk beberapa perangkat.
#### Mulai otomatis pada koneksi perangkat
Anda bisa menggunakan [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Koneksi via SSH tunnel
Untuk menyambung ke perangkat jarak jauh, dimungkinkan untuk menghubungkan klien `adb` lokal ke server `adb` jarak jauh (asalkan mereka menggunakan versi yang sama dari _adb_ protocol):
```bash
adb kill-server # matikan server adb lokal di 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 komputer_jarak_jauh_anda
# jaga agar tetap terbuka
```
Dari terminal lain:
```bash
scrcpy
```
Untuk menghindari mengaktifkan penerusan port jarak jauh, Anda dapat memaksa sambungan maju sebagai gantinya (perhatikan `-L`, bukan` -R`):
```bash
adb kill-server # matikan server adb lokal di 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 komputer_jarak_jauh_anda
# jaga agar tetap terbuka
```
Dari terminal lain:
```bash
scrcpy --force-adb-forward
```
Seperti koneksi nirkabel, mungkin berguna untuk mengurangi kualitas:
```
scrcpy -b2M -m800 --max-fps 15
```
### Konfigurasi Jendela
#### Judul
Secara default, judul jendela adalah model perangkat. Itu bisa diubah:
```bash
scrcpy --window-title 'Perangkat Saya'
```
#### Posisi dan ukuran
Posisi dan ukuran jendela awal dapat ditentukan:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Jendela tanpa batas
Untuk menonaktifkan dekorasi jendela:
```bash
scrcpy --window-borderless
```
#### Selalu di atas
Untuk menjaga jendela scrcpy selalu di atas:
```bash
scrcpy --always-on-top
```
#### Layar penuh
Aplikasi dapat dimulai langsung dalam layar penuh::
```bash
scrcpy --fullscreen
scrcpy -f # versi pendek
```
Layar penuh kemudian dapat diubah secara dinamis dengan <kbd>MOD</kbd>+<kbd>f</kbd>.
#### Rotasi
Jendela mungkin diputar:
```bash
scrcpy --rotation 1
```
Nilai yang mungkin adalah:
- `0`: tidak ada rotasi
- `1`: 90 derajat berlawanan arah jarum jam
- `2`: 180 derajat
- `3`: 90 derajat searah jarum jam
Rotasi juga dapat diubah secara dinamis dengan <kbd>MOD</kbd>+<kbd></kbd>
_(kiri)_ and <kbd>MOD</kbd>+<kbd></kbd> _(kanan)_.
Perhatikan bahwa _scrcpy_ mengelola 3 rotasi berbeda::
- <kbd>MOD</kbd>+<kbd>r</kbd> meminta perangkat untuk beralih antara potret dan lanskap (aplikasi yang berjalan saat ini mungkin menolak, jika mendukung orientasi yang diminta).
- `--lock-video-orientation` mengubah orientasi pencerminan (orientasi video yang dikirim dari perangkat ke komputer). Ini mempengaruhi rekaman.
- `--rotation` (atau <kbd>MOD</kbd>+<kbd></kbd>/<kbd>MOD</kbd>+<kbd></kbd>)
memutar hanya konten jendela. Ini hanya mempengaruhi tampilan, bukan rekaman.
### Opsi pencerminan lainnya
#### Hanya-baca
Untuk menonaktifkan kontrol (semua yang dapat berinteraksi dengan perangkat: tombol input, peristiwa mouse, seret & lepas file):
```bash
scrcpy --no-control
scrcpy -n
```
#### Layar
Jika beberapa tampilan tersedia, Anda dapat memilih tampilan untuk cermin:
```bash
scrcpy --display 1
```
Daftar id tampilan dapat diambil dengan::
```
adb shell dumpsys display # cari "mDisplayId=" di keluaran
```
Tampilan sekunder hanya dapat dikontrol jika perangkat menjalankan setidaknya Android 10 (jika tidak maka akan dicerminkan dalam hanya-baca).
#### Tetap terjaga
Untuk mencegah perangkat tidur setelah beberapa penundaan saat perangkat dicolokkan:
```bash
scrcpy --stay-awake
scrcpy -w
```
Keadaan awal dipulihkan ketika scrcpy ditutup.
#### Matikan layar
Dimungkinkan untuk mematikan layar perangkat saat pencerminan mulai dengan opsi baris perintah:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Atau dengan menekan <kbd>MOD</kbd>+<kbd>o</kbd> kapan saja.
Untuk menyalakannya kembali, tekan <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
Di Android, tombol `POWER` selalu menyalakan layar. Untuk kenyamanan, jika `POWER` dikirim melalui scrcpy (melalui klik kanan atau<kbd>MOD</kbd>+<kbd>p</kbd>), itu akan memaksa untuk mematikan layar setelah penundaan kecil (atas dasar upaya terbaik).
Tombol fisik `POWER` masih akan menyebabkan layar dihidupkan.
Ini juga berguna untuk mencegah perangkat tidur:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Render frame kedaluwarsa
Secara default, untuk meminimalkan latensi, _scrcpy_ selalu menampilkan frame yang terakhir didekodekan tersedia, dan menghapus frame sebelumnya.
Untuk memaksa rendering semua frame (dengan kemungkinan peningkatan latensi), gunakan:
```bash
scrcpy --render-expired-frames
```
#### Tunjukkan sentuhan
Untuk presentasi, mungkin berguna untuk menunjukkan sentuhan fisik (pada perangkat fisik).
Android menyediakan fitur ini di _Opsi Pengembang_.
_Scrcpy_ menyediakan opsi untuk mengaktifkan fitur ini saat mulai dan mengembalikan nilai awal saat keluar:
```bash
scrcpy --show-touches
scrcpy -t
```
Perhatikan bahwa ini hanya menunjukkan sentuhan _fisik_ (dengan jari di perangkat).
#### Nonaktifkan screensaver
Secara default, scrcpy tidak mencegah screensaver berjalan di komputer.
Untuk menonaktifkannya:
```bash
scrcpy --disable-screensaver
```
### Kontrol masukan
#### Putar layar perangkat
Tekan <kbd>MOD</kbd>+<kbd>r</kbd> untuk beralih antara mode potret dan lanskap.
Perhatikan bahwa itu berputar hanya jika aplikasi di latar depan mendukung orientasi yang diminta.
#### Salin-tempel
Setiap kali papan klip Android berubah, secara otomatis disinkronkan ke papan klip komputer.
Apa saja <kbd>Ctrl</kbd> pintasan diteruskan ke perangkat. Khususnya:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> biasanya salinan
- <kbd>Ctrl</kbd>+<kbd>x</kbd> biasanya memotong
- <kbd>Ctrl</kbd>+<kbd>v</kbd> biasanya menempel (setelah sinkronisasi papan klip komputer-ke-perangkat)
Ini biasanya berfungsi seperti yang Anda harapkan.
Perilaku sebenarnya tergantung pada aplikasi yang aktif. Sebagai contoh,
_Termux_ mengirim SIGINT ke <kbd>Ctrl</kbd>+<kbd>c</kbd> sebagai gantinya, dan _K-9 Mail_ membuat pesan baru.
Untuk menyalin, memotong dan menempel dalam kasus seperti itu (tetapi hanya didukung di Android> = 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> injeksi `COPY` _(salin)_
- <kbd>MOD</kbd>+<kbd>x</kbd> injeksi `CUT` _(potong)_
- <kbd>MOD</kbd>+<kbd>v</kbd> injeksi `PASTE` (setelah sinkronisasi papan klip komputer-ke-perangkat)
Tambahan, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> memungkinkan untuk memasukkan teks papan klip komputer sebagai urutan peristiwa penting. Ini berguna ketika komponen tidak menerima penempelan teks (misalnya di _Termux_), tetapi dapat merusak konten non-ASCII.
**PERINGATAN:** Menempelkan papan klip komputer ke perangkat (baik melalui
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) menyalin konten ke clipboard perangkat. Akibatnya, aplikasi Android apa pun dapat membaca kontennya. Anda harus menghindari menempelkan konten sensitif (seperti kata sandi) seperti itu.
#### Cubit untuk memperbesar/memperkecil
Untuk mensimulasikan "cubit-untuk-memperbesar/memperkecil": <kbd>Ctrl</kbd>+_klik-dan-pindah_.
Lebih tepatnya, tahan <kbd>Ctrl</kbd> sambil menekan tombol klik kiri. Hingga tombol klik kiri dilepaskan, semua gerakan mouse berskala dan memutar konten (jika didukung oleh aplikasi) relatif ke tengah layar.
Secara konkret, scrcpy menghasilkan kejadian sentuh tambahan dari "jari virtual" di lokasi yang dibalik melalui bagian tengah layar.
#### Preferensi injeksi teks
Ada dua jenis [peristiwa][textevents] dihasilkan saat mengetik teks:
- _peristiwa penting_, menandakan bahwa tombol ditekan atau dilepaskan;
- _peristiwa teks_, menandakan bahwa teks telah dimasukkan.
Secara default, huruf dimasukkan menggunakan peristiwa kunci, sehingga keyboard berperilaku seperti yang diharapkan dalam game (biasanya untuk tombol WASD).
Tapi ini mungkin [menyebabkan masalah][prefertext]. Jika Anda mengalami masalah seperti itu, Anda dapat menghindarinya dengan:
```bash
scrcpy --prefer-text
```
(tapi ini akan merusak perilaku keyboard dalam game)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Ulangi kunci
Secara default, menahan tombol akan menghasilkan peristiwa kunci yang berulang. Ini dapat menyebabkan masalah kinerja di beberapa game, di mana acara ini tidak berguna.
Untuk menghindari penerusan peristiwa penting yang berulang:
```bash
scrcpy --no-key-repeat
```
### Seret/jatuhkan file
#### Pasang APK
Untuk menginstal APK, seret & lepas file APK (diakhiri dengan `.apk`) ke jendela _scrcpy_.
Tidak ada umpan balik visual, log dicetak ke konsol.
#### Dorong file ke perangkat
Untuk mendorong file ke `/sdcard/` di perangkat, seret & jatuhkan file (non-APK) ke jendela _scrcpy_.
Tidak ada umpan balik visual, log dicetak ke konsol.
Direktori target dapat diubah saat mulai:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### Penerusan audio
Audio tidak diteruskan oleh _scrcpy_. Gunakan [sndcpy].
Lihat juga [Masalah #14].
[sndcpy]: https://github.com/rom1v/sndcpy
[Masalah #14]: https://github.com/Genymobile/scrcpy/issues/14
## Pintasan
Dalam daftar berikut, <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:
```bash
# gunakan RCtrl untuk jalan pintas
scrcpy --shortcut-mod=rctrl
# gunakan baik LCtrl+LAlt atau LSuper untuk jalan pintas
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> biasanya adalah <kbd>Windows</kbd> atau <kbd>Cmd</kbd> key._
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Aksi | Pintasan
| ------------------------------------------------------|:-----------------------------
| Alihkan mode layar penuh | <kbd>MOD</kbd>+<kbd>f</kbd>
| Putar layar kiri | <kbd>MOD</kbd>+<kbd></kbd> _(kiri)_
| Putar layar kanan | <kbd>MOD</kbd>+<kbd></kbd> _(kanan)_
| Ubah ukuran jendela menjadi 1:1 (piksel-sempurna) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ubah ukuran jendela menjadi hapus batas hitam | <kbd>MOD</kbd>+<kbd>w</kbd> \| _klik-dua-kali¹_
| Klik `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Klik-tengah_
| Klik `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Klik-kanan²_
| Klik `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Klik `MENU` (buka kunci layar) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Klik `VOLUME_UP` | <kbd>MOD</kbd>+<kbd></kbd> _(naik)_
| Klik `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd></kbd> _(turun)_
| Klik `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Menyalakan | _Klik-kanan²_
| Matikan layar perangkat (tetap mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Hidupkan layar perangkat | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Putar layar perangkat | <kbd>MOD</kbd>+<kbd>r</kbd>
| Luaskan panel notifikasi | <kbd>MOD</kbd>+<kbd>n</kbd>
| Ciutkan panel notifikasi | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Menyalin ke papan klip³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Potong ke papan klip³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sinkronkan papan klip dan tempel³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Masukkan teks papan klip komputer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Mengaktifkan/menonaktifkan penghitung FPS (di stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Cubit-untuk-memperbesar/memperkecil | <kbd>Ctrl</kbd>+_klik-dan-pindah_
_¹Klik-dua-kali pada batas hitam untuk menghapusnya._
_²Klik-kanan akan menghidupkan layar jika mati, tekan BACK jika tidak._
_³Hanya di Android >= 7._
Semua <kbd>Ctrl</kbd>+_key_ pintasan diteruskan ke perangkat, demikian adanya
ditangani oleh aplikasi aktif.
## Jalur kustom
Untuk menggunakan biner _adb_ tertentu, konfigurasikan jalurnya di variabel lingkungan `ADB`:
ADB=/path/to/adb scrcpy
Untuk mengganti jalur file `scrcpy-server`, konfigurasikan jalurnya di
`SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Mengapa _scrcpy_?
Seorang kolega menantang saya untuk menemukan nama yang tidak dapat diucapkan seperti [gnirehtet].
[`strcpy`] menyalin sebuah **str**ing; `scrcpy` menyalin sebuah **scr**een.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Bagaimana Cara membangun?
Lihat [BUILD].
[BUILD]: BUILD.md
## Masalah umum
Lihat [FAQ](FAQ.md).
## Pengembang
Baca [halaman pengembang].
[halaman pengembang]: DEVELOP.md
## Lisensi
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Artikel
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

500
README.ko.md Normal file
View File

@ -0,0 +1,500 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.11)
This document will be updated frequently along with the original Readme file
이 문서는 원어 리드미 파일의 업데이트에 따라 종종 업데이트 될 것입니다
이 어플리케이션은 UBS ( 혹은 [TCP/IP][article-tcpip] ) 로 연결된 Android 디바이스를 화면에 보여주고 관리하는 것을 제공합니다.
_GNU/Linux_, _Windows__macOS_ 상에서 작동합니다.
(아래 설명에서 디바이스는 안드로이드 핸드폰을 의미합니다.)
[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
![screenshot](https://github.com/Genymobile/scrcpy/blob/master/assets/screenshot-debian-600.jpg?raw=true)
주요 기능은 다음과 같습니다.
- **가벼움** (기본적이며 디바이스의 화면만을 보여줌)
- **뛰어난 성능** (30~60fps)
- **높은 품질** (1920×1080 이상의 해상도)
- **빠른 반응 속도** ([35~70ms][lowlatency])
- **짧은 부팅 시간** (첫 사진을 보여주는데 최대 1초 소요됨)
- **장치 설치와는 무관함** (디바이스에 설치하지 않아도 됨)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 요구사항
안드로이드 장치는 최소 API 21 (Android 5.0) 을 필요로 합니다.
디바이스에 [adb debugging][enable-adb]이 가능한지 확인하십시오.
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
어떤 디바이스에서는, 키보드와 마우스를 사용하기 위해서 [추가 옵션][control] 이 필요하기도 합니다.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 앱 설치하기
### Linux (리눅스)
리눅스 상에서는 보통 [어플을 직접 설치][BUILD] 해야합니다. 어렵지 않으므로 걱정하지 않아도 됩니다.
[BUILD]:https://github.com/Genymobile/scrcpy/blob/master/BUILD.md
[Snap] 패키지가 가능합니다 : [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Arch Linux에서, [AUR] 패키지가 가능합니다 : [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Windows (윈도우)
윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) :
해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다.
- [`scrcpy-win`][direct-win]
[direct-win]: https://github.com/Genymobile/scrcpy/blob/master/README.md#windows
[어플을 직접 설치][BUILD] 할 수도 있습니다.
### macOS (맥 OS)
이 어플리케이션은 아래 사항을 따라 설치한다면 [Homebrew] 에서도 사용 가능합니다 :
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`PATH` 로부터 접근 가능한 `adb` 가 필요합니다. 아직 설치하지 않았다면 다음을 따라 설치해야 합니다 :
```bash
brew cask install android-platform-tools
```
[어플을 직접 설치][BUILD] 할 수도 있습니다.
## 실행
안드로이드 디바이스를 연결하고 실행하십시오:
```bash
scrcpy
```
다음과 같이 명령창 옵션 기능도 제공합니다.
```bash
scrcpy --help
```
## 기능
### 캡쳐 환경 설정
###사이즈 재정의
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
너비와 높이를 제한하기 위해 특정 값으로 지정할 수 있습니다 (e.g. 1024) :
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 축약 버전
```
이 외의 크기도 디바이스의 가로 세로 비율이 유지된 상태에서 계산됩니다.
이러한 방식으로 디바이스 상에서 1920×1080 는 모니터 상에서1024×576로 미러링될 것 입니다.
### bit-rate 변경
기본 bit-rate 는 8 Mbps입니다. 비디오 bit-rate 를 변경하기 위해선 다음과 같이 입력하십시오 (e.g. 2 Mbps로 변경):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 축약 버전
```
###프레임 비율 제한
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:
```bash
scrcpy --max-fps 15
```
### Crop (잘라내기)
디바이스 화면은 화면의 일부만 미러링하기 위해 잘라질 것입니다.
예를 들어, *Oculus Go* 의 한 쪽 눈만 미러링할 때 유용합니다 :
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
scrcpy -c 1224:1440:0:0 # 축약 버전
```
만약 `--max-size` 도 지정하는 경우, 잘라낸 다음에 재정의된 크기가 적용될 것입니다.
### 화면 녹화
미러링하는 동안 화면 녹화를 할 수 있습니다 :
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
녹화하는 동안 미러링을 멈출 수 있습니다 :
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+C 로 녹화를 중단할 수 있습니다.
# 윈도우 상에서 Ctrl+C 는 정상정으로 종료되지 않을 수 있으므로, 디바이스 연결을 해제하십시오.
```
"skipped frames" 은 모니터 화면에 보여지지 않았지만 녹화되었습니다 ( 성능 문제로 인해 ). 프레임은 디바이스 상에서 _타임 스탬프 ( 어느 시점에 데이터가 존재했다는 사실을 증명하기 위해 특정 위치에 시각을 표시 )_ 되었으므로, [packet delay
variation] 은 녹화된 파일에 영향을 끼치지 않습니다.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
## 연결
### 무선연결
_Scrcpy_ 장치와 정보를 주고받기 위해 `adb` 를 사용합니다. `adb` 는 TCIP/IP 를 통해 디바이스와 [연결][connect] 할 수 있습니다 :
1. 컴퓨터와 디바이스를 동일한 Wi-Fi 에 연결합니다.
2. 디바이스의 IP address 를 확인합니다 (설정 → 내 기기 → 상태 / 혹은 인터넷에 '내 IP'검색 시 확인 가능합니다. ).
3. TCP/IP 를 통해 디바이스에서 adb 를 사용할 수 있게 합니다: `adb tcpip 5555`.
4. 디바이스 연결을 해제합니다.
5. adb 를 통해 디바이스에 연결을 합니다\: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` 대신)_.
6. `scrcpy` 실행합니다.
다음은 bit-rate 와 해상도를 줄이는데 유용합니다 :
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 축약 버전
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### 여러 디바이스 사용 가능
만약에 여러 디바이스들이 `adb devices` 목록에 표시되었다면, _serial_ 을 명시해야합니다:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 축약 버전
```
_scrcpy_ 로 여러 디바이스를 연결해 사용할 수 있습니다.
#### SSH tunnel
떨어져 있는 디바이스와 연결하기 위해서는, 로컬 `adb` client와 떨어져 있는 `adb` 서버를 연결해야 합니다. (디바이스와 클라이언트가 동일한 버전의 _adb_ protocol을 사용할 경우에 제공됩니다.):
```bash
adb kill-server # 5037의 로컬 local adb server를 중단
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 실행 유지
```
다른 터미널에서는 :
```bash
scrcpy
```
무선 연결과 동일하게, 화질을 줄이는 것이 나을 수 있습니다:
```
scrcpy -b2M -m800 --max-fps 15
```
## Window에서의 배치
### 맞춤형 window 제목
기본적으로, window의 이름은 디바이스의 모델명 입니다.
다음의 명령어를 통해 변경하세요.
```bash
scrcpy --window-title 'My device'
```
### 배치와 크기
초기 window창의 배치와 크기는 다음과 같이 설정할 수 있습니다:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
### 경계 없애기
윈도우 장식(경계선 등)을 다음과 같이 제거할 수 있습니다:
```bash
scrcpy --window-borderless
```
### 항상 모든 윈도우 위에 실행창 고정
이 어플리케이션의 윈도우 창은 다음의 명령어로 다른 window 위에 디스플레이 할 수 있습니다:
```bash
scrcpy --always-on-top
scrcpy -T # 축약 버전
```
### 전체 화면
이 어플리케이션은 전체화면으로 바로 시작할 수 있습니다.
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
전체 화면은 `Ctrl`+`f`키로 끄거나 켤 수 있습니다.
## 다른 미러링 옵션
### 읽기 전용(Read-only)
권한을 제한하기 위해서는 (디바이스와 관련된 모든 것: 입력 키, 마우스 이벤트 , 파일의 드래그 앤 드랍(drag&drop)):
```bash
scrcpy --no-control
scrcpy -n
```
### 화면 끄기
미러링을 실행하는 와중에 디바이스의 화면을 끌 수 있게 하기 위해서는
다음의 커맨드 라인 옵션을(command line option) 입력하세요:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
혹은 `Ctrl`+`o`을 눌러 언제든지 디바이스의 화면을 끌 수 있습니다.
다시 화면을 켜기 위해서는`POWER` (혹은 `Ctrl`+`p`)를 누르세요.
### 유효기간이 지난 프레임 제공 (Render expired frames)
디폴트로, 대기시간을 최소화하기 위해 _scrcpy_ 는 항상 마지막으로 디코딩된 프레임을 제공합니다
과거의 프레임은 하나씩 삭제합니다.
모든 프레임을 강제로 렌더링하기 위해서는 (대기 시간이 증가될 수 있습니다)
다음의 명령어를 사용하세요:
```bash
scrcpy --render-expired-frames
```
### 화면에 터치 나타내기
발표를 할 때, 물리적인 기기에 한 물리적 터치를 나타내는 것이 유용할 수 있습니다.
안드로이드 운영체제는 이런 기능을 _Developers options_에서 제공합니다.
_Scrcpy_ 는 이런 기능을 시작할 때와 종료할 때 옵션으로 제공합니다.
```bash
scrcpy --show-touches
scrcpy -t
```
화면에 _물리적인 터치만_ 나타나는 것에 유의하세요 (손가락을 디바이스에 대는 행위).
### 입력 제어
#### 복사-붙여넣기
컴퓨터와 디바이스 양방향으로 클립보드를 복사하는 것이 가능합니다:
- `Ctrl`+`c` 디바이스의 클립보드를 컴퓨터로 복사합니다;
- `Ctrl`+`Shift`+`v` 컴퓨터의 클립보드를 디바이스로 복사합니다;
- `Ctrl`+`v` 컴퓨터의 클립보드를 text event 로써 _붙여넣습니다_ ( 그러나, ASCII 코드가 아닌 경우 실행되지 않습니다 )
#### 텍스트 삽입 우선 순위
텍스트를 입력할 때 생성되는 두 가지의 [events][textevents] 가 있습니다:
- _key events_, 키가 눌려있는 지에 대한 신호;
- _text events_, 텍스트가 입력되었는지에 대한 신호.
기본적으로, 글자들은 key event 를 이용해 입력되기 때문에, 키보드는 게임에서처럼 처리합니다 ( 보통 WASD 키에 대해서 ).
그러나 이는 [issues 를 발생][prefertext]시킵니다. 이와 관련된 문제를 접할 경우, 아래와 같이 피할 수 있습니다:
```bash
scrcpy --prefer-text
```
( 그러나 이는 게임에서의 처리를 중단할 수 있습니다 )
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
### 파일 드랍
### APK 실행하기
APK를 실행하기 위해서는, APK file(파일명이`.apk`로 끝나는 파일)을 드래그하고 _scrcpy_ window에 드랍하세요 (drag and drop)
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
### 디바이스에 파일 push하기
디바이스의`/sdcard/`에 파일을 push하기 위해서는,
APK파일이 아닌 파일을_scrcpy_ window에 드래그하고 드랍하세요.(drag and drop).
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
해당 디렉토리는 시작할 때 변경이 가능합니다:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### 오디오의 전달
_scrcpy_는 오디오를 직접 전달해주지 않습니다. [USBaudio] (Linux-only)를 사용하세요.
추가적으로 [issue #14]를 참고하세요.
[USBaudio]: https://github.com/rom1v/usbaudio
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 단축키
| 실행내용 | 단축키 | 단축키 (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| 전체화면 모드로 전환 | `Ctrl`+`f` | `Cmd`+`f`
| window를 1:1비율로 전환하기(픽셀 맞춤) | `Ctrl`+`g` | `Cmd`+`g`
| 검은 공백 제거 위한 window 크기 조정 | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|`HOME` 클릭 | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| `BACK` 클릭 | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| `APP_SWITCH` 클릭 | `Ctrl`+`s` | `Cmd`+`s`
| `MENU` 클릭 | `Ctrl`+`m` | `Ctrl`+`m`
| `VOLUME_UP` 클릭 | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
| `VOLUME_DOWN` 클릭 | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| `POWER` 클릭 | `Ctrl`+`p` | `Cmd`+`p`
| 전원 켜기 | _Right-click²_ | _Right-click²_
| 미러링 중 디바이스 화면 끄기 | `Ctrl`+`o` | `Cmd`+`o`
| 알림 패널 늘리기 | `Ctrl`+`n` | `Cmd`+`n`
| 알림 패널 닫기 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| 디바이스의 clipboard 컴퓨터로 복사하기 | `Ctrl`+`c` | `Cmd`+`c`
| 컴퓨터의 clipboard 디바이스에 붙여넣기 | `Ctrl`+`v` | `Cmd`+`v`
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
_¹검은 공백을 제거하기 위해서는 그 부분을 더블 클릭하세요_
_²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상태에서는 뒤로 돌아갑니다.
## 맞춤 경로 (custom path)
특정한 _adb_ binary를 사용하기 위해서는, 그것의 경로를 환경변수로 설정하세요.
`ADB`:
ADB=/path/to/adb scrcpy
`scrcpy-server.jar`파일의 경로에 오버라이드 하기 위해서는, 그것의 경로를 `SCRCPY_SERVER_PATH`에 저장하세요.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## _scrcpy_ 인 이유?
한 동료가 [gnirehtet]와 같이 발음하기 어려운 이름을 찾을 수 있는지 도발했습니다.
[`strcpy`] 는 **str**ing을 copy하고; `scrcpy`는 **scr**een을 copy합니다.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 빌드하는 방법?
[BUILD]을 참고하세요.
[BUILD]: BUILD.md
## 흔한 issue
[FAQ](FAQ.md)을 참고하세요.
## 개발자들
[developers page]를 참고하세요.
[developers page]: DEVELOP.md
## 라이선스
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 관련 글 (articles)
- [scrcpy 소개][article-intro]
- [무선으로 연결하는 Scrcpy][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

630
README.md

File diff suppressed because it is too large Load Diff

531
README.pt-br.md Normal file
View File

@ -0,0 +1,531 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.12.1)
Esta aplicação fornece visualização e controle de dispositivos Android conectados via
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root.
Funciona em _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Foco em:
- **leveza** (Nativo, mostra apenas a tela do dispositivo)
- **performance** (30~60fps)
- **qualidade** (1920×1080 ou acima)
- **baixa latência** ([35~70ms][lowlatency])
- **baixo tempo de inicialização** (~1 segundo para mostrar a primeira imagem)
- **não intrusivo** (nada é deixado instalado no dispositivo)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## Requisitos
O Dispositivo Android requer pelo menos a API 21 (Android 5.0).
Tenha certeza de ter [ativado a depuração USB][enable-adb] no(s) seu(s) dispositivo(s).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Em alguns dispositivos, você também precisará ativar [uma opção adicional][control] para controlá-lo usando o teclado e mouse.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Obtendo o app
### Linux
No Debian (_em testes_ e _sid_ por enquanto):
```
apt install scrcpy
```
O pacote [Snap] está disponível: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil).
### Windows
Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências
(incluindo `adb`) está disponível:
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
Também disponível em [Chocolatey]:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # se você ainda não o tem
```
E no [Scoop]:
```bash
scoop install scrcpy
scoop install adb # se você ainda não o tem
```
[Scoop]: https://scoop.sh
Você também pode [compilar a aplicação manualmente][BUILD].
### macOS
A aplicação está disponível em [Homebrew]. Apenas a instale:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem:
```bash
brew cask install android-platform-tools
```
Você também pode [compilar a aplicação manualmente][BUILD].
## Executar
Plugue um dispositivo Android e execute:
```bash
scrcpy
```
Também aceita argumentos de linha de comando, listados por:
```bash
scrcpy --help
```
## Funcionalidades
### Configuração de captura
#### Redução de tamanho
Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para
aumentar performance.
Para limitar ambos(largura e altura) para algum valor (ex: 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # versão reduzida
```
A outra dimensão é calculada para que a proporção do dispositivo seja preservada.
Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576.
#### Mudanças no bit-rate
O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # versão reduzida
```
#### Limitar frame rates
Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada:
```bash
scrcpy --max-fps 15
```
#### Cortar
A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela.
Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0)
```
Se `--max-size` também for especificado, redimensionar é aplicado após os cortes.
### Gravando
É possível gravar a tela enquanto ocorre o espelhamento:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
Para desativar o espelhamento durante a gravação:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrompe a gravação com Ctrl+C
# Ctrl+C não encerrar propriamente no Windows, então desconecte o dispositivo
```
"Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance).
Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo.
[Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Conexão
#### Wireless/Sem fio
_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP:
1. Conecte o dispositivo a mesma rede Wi-Fi do seu computador.
2. Pegue o endereço de IP do seu dispositivo (Em Configurações → Sobre o Telefone → Status).
3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`.
4. Desplugue seu dispositivo.
5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_.
6. Execute `scrcpy` como de costume.
Pode ser útil diminuir o bit-rate e a resolução:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versão reduzida
```
[conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless
#### N-dispositivos
Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versão reduzida
```
Se o dispositivo está conectado via TCP/IP:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versão reduzida
```
Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos.
#### Conexão via SSH
Para conectar-se à um dispositivo remoto, é possível se conectar um cliente local `adb` à um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo _adb_):
```bash
adb kill-server # encerra o servidor local na 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# mantém isso aberto
```
De outro terminal:
```bash
scrcpy
```
Igual para conexões sem fio, pode ser útil reduzir a qualidade:
```
scrcpy -b2M -m800 --max-fps 15
```
### Configurações de Janela
#### Título
Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado:
```bash
scrcpy --window-title 'Meu dispositivo'
```
#### Posição e tamanho
A posição e tamanho iniciais da janela podem ser especificados:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### Sem bordas
Para desativar decorações da janela:
```bash
scrcpy --window-borderless
```
#### Sempre visível
Para manter a janela do scrcpy sempre visível:
```bash
scrcpy --always-on-top
```
#### Tela cheia
A aplicação pode ser iniciada diretamente em tela cheia:
```bash
scrcpy --fullscreen
scrcpy -f # versão reduzida
```
Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`.
### Outras opções de espelhamento
#### Apenas leitura
Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos):
```bash
scrcpy --no-control
scrcpy -n
```
#### Desligar a tela
É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
Ou apertando `Ctrl`+`o` durante qualquer momento.
Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`).
#### Frames expirados de renderização
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 aumento de latência), use:
```bash
scrcpy --render-expired-frames
```
#### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico).
Android fornece esta funcionalidade nas _Opções do Desenvolvedor_.
_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento:
```bash
scrcpy --show-touches
scrcpy -t
```
Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo).
### Controle de entrada
#### Rotacionar a tela do dispositivo
Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem.
Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado.
#### Copiar-Colar
É possível sincronizar áreas de transferência entre computador e o dispositivo,
para ambas direções:
- `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador;
- `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo;
- `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas
quebra caracteres não-ASCII).
#### Preferências de injeção de texto
Existe dois tipos de [eventos][textevents] gerados ao digitar um texto:
- _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta;
- _eventos de texto_, sinalizando que o texto foi inserido.
Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se
como esperado em jogos (normalmente para tecladas WASD)
Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema,
pode evitá-lo usando:
```bash
scrcpy --prefer-text
```
(mas isto vai quebrar o comportamento do teclado em jogos)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
### Transferência de arquivo
#### Instalar APK
Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_.
Não existe feedback visual, um log é imprimido no console.
#### Enviar arquivo para o dispositivo
Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do
_scrcpy_.
Não existe feedback visual, um log é imprimido no console.
O diretório alvo pode ser mudado ao iniciar:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### Encaminhamento de áudio
Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux).
Também veja [issue #14].
[USBaudio]: https://github.com/rom1v/usbaudio
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Atalhos
| Ação | Atalho | Atalho (macOS)
| ------------------------------------------------------------- |:------------------------------- |:-----------------------------
| Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f`
| Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g`
| Redimensionar janela para tirar as bordas pretas | `Ctrl`+`x` \| _Clique-duplo¹_ | `Cmd`+`x` \| _Clique-duplo¹_
| Clicar em `HOME` | `Ctrl`+`h` \| _Clique-central_ | `Ctrl`+`h` \| _Clique-central_
| Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_
| Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_
| Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_
| Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Ligar | _Clique-direito²_ | _Clique-direito²_
| Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o`
| Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r`
| Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n`
| Esconder painel de notificação | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copiar área de transferência do dispositivo para o computador | `Ctrl`+`c` | `Cmd`+`c`
| Colar área de transferência do computador para o dispositivo | `Ctrl`+`v` | `Cmd`+`v`
| Copiar área de transferência do computador para dispositivo | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i`
_¹Clique-duplo em bordas pretas para removê-las._
_²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._
## Caminhos personalizados
Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`:
ADB=/caminho/para/adb scrcpy
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
`SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Por quê _scrcpy_?
Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet].
[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een.
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## Como compilar?
Veja [BUILD].
[BUILD]: BUILD.md
## Problemas comuns
Veja [FAQ](FAQ.md).
## Desenvolvedores
Leia a [developers page].
[developers page]: DEVELOP.md
## Licença
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Artigos
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

726
README.zh-Hans.md Normal file
View File

@ -0,0 +1,726 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
只有原版的[README](README.md)会保持最新。
本文根据[479d10d]进行翻译。
[479d10d]: https://github.com/Genymobile/scrcpy/commit/479d10dc22b70272187e0963c6ad24d754a669a2#diff-04c6e90faac2675aa89e2176d2eec7d8
# scrcpy (v1.16)
本应用程序可以通过USB或 [TCP/IP][article-tcpip] )连接用于显示或控制安卓设备。这不需要获取 _root_ 权限。
该应用程序可以在 _GNU/Linux_, _Windows__macOS_ 环境下运行。
[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
![screenshot](assets/screenshot-debian-600.jpg)
它专注于:
- **轻量** (原生,仅显示设备屏幕)
- **性能** 30~60fps
- **质量** 分辨率可达1920x1080或更高
- **低延迟** (35-70ms)
- **快速启动** (数秒内即能开始显示)
- **无侵入性** (不需要在安卓设备上安装任何程序)
## 使用要求
安卓设备系统版本需要在Android 5.0API 21)或以上。
确保您在设备上开启了[adb调试]。
[adb调试]: https://developer.android.com/studio/command-line/adb.html#Enabling
在某些设备上,你还需要开启[额外的选项]以用鼠标和键盘进行控制。
[额外的选项]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 获取scrcpy
<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
在Debian目前仅测试版和不稳定版_testing__sid_ 版本和Ubuntu 20.04)上:
```
apt install scrcpy
```
[Snap]包也是可用的: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
对于Fedora用户我们提供[COPR]包: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
对于Arch Linux用户我们提供[AUR]包: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
对于Gentoo用户我们提供[Ebuild]包:[`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行编译][编译](不必担心,这并不困难)。
### Windows
在Windows上简便起见我们准备了包含所有依赖项包括adb的程序包。
- [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
您也可以在[Chocolatey]下载:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # 如果你没有adb
```
也可以使用 [Scoop]:
```bash
scoop install scrcpy
scoop install adb # 如果你没有adb
```
[Scoop]: https://scoop.sh
您也可以[自行编译][编译]。
### macOS
您可以使用[Homebrew]下载scrcpy。直接安装就可以了
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
您需要 `adb`以使用scrcpy并且它需要可以通过 `PATH`被访问。如果您没有:
```bash
brew cask install android-platform-tools
```
您也可以[自行编译][编译]。
## 运行scrcpy
用USB链接电脑和安卓设备并执行
```bash
scrcpy
```
支持带命令行参数执行,查看参数列表:
```bash
scrcpy --help
```
## 功能介绍
### 画面设置
#### 缩小分辨率
有时候,将设备屏幕镜像分辨率降低可以有效地提升性能。
我们可以将高度和宽度都限制在一定大小内(如 1024
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # short version
```
较短的一边会被按比例缩小以保持设备的显示比例。
这样1920x1080 的设备会以 1024x576 的分辨率显示。
#### 修改画面比特率
默认的比特率是8Mbps。如果要改变画面的比特率 (比如说改成2Mbps)
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # short version
```
#### 限制画面帧率
画面的帧率可以通过下面的命令被限制:
```bash
scrcpy --max-fps 15
```
这个功能仅在Android 10和以后的版本被Android官方支持但也有可能在更早的版本可用。
#### 画面裁剪
设备画面可在裁切后进行镜像,以显示部分屏幕。
这项功能可以用于例如只显示Oculus Go的一只眼睛。
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
如果`--max-size`在同时被指定,分辨率的改变将在画面裁切后进行。
#### 锁定屏幕朝向
可以使用如下命令锁定屏幕朝向:
```bash
scrcpy --lock-video-orientation 0 # 自然朝向
scrcpy --lock-video-orientation 1 # 90° 逆时针旋转
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° 顺时针旋转
```
该设定影响录制。
### 屏幕录制
可以在屏幕镜像的同时录制视频:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
在不开启屏幕镜像的同时录制:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# 按Ctrl+C以停止录制
```
在显示中“被跳过的帧”会被录制,虽然它们由于性能原因没有实时显示。
在传输中每一帧都有 _时间戳_ ,所以 [包时延变化] 并不影响录制的文件。
[包时延变化]: https://en.wikipedia.org/wiki/Packet_delay_variation
### 连接方式
#### 无线
_Scrcpy_ 使用`adb`来与安卓设备连接。同时,`adb`能够通过TCP/IP[连接]到安卓设备:
1. 将您的安卓设备和电脑连接至同一Wi-Fi。
2. 获取安卓设备的IP地址在设置-关于手机-状态信息)。
3. 打开安卓设备的网络adb功能`adb tcpip 5555`
4. 将您的设备与电脑断开连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(用设备IP替换 `DEVICE_IP`)_.
6. 运行`scrcpy`
降低比特率和分辨率可能有助于性能:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # short version
```
[连接]: https://developer.android.com/studio/command-line/adb.html#wireless
#### 多设备
如果多个设备在执行`adb devices`后被列出,您必须指定设备的 _序列号_
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
```
如果设备是通过TCP/IP方式连接到电脑的
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # short version
```
您可以同时启动多个 _scrcpy_ 实例以同时显示多个设备的画面。
#### 在设备连接时自动启动
您可以使用 [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH 连接
本地的 adb 可以远程连接到另一个 adb 服务器假设两者的adb版本相同来远程连接到设备
```bash
adb kill-server # 关闭本地5037端口上的adb服务器
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
从另一个终端:
```bash
scrcpy
```
为了避免启动远程端口转发,你可以强制启动一个转发连接(注意`-L``-R`的区别:
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
从另一个终端:
```bash
scrcpy --force-adb-forward
```
和无线网络连接类似,下列设置可能对改善性能有帮助:
```
scrcpy -b2M -m800 --max-fps 15
```
### 窗口设置
#### 标题
窗口的标题默认为设备型号。您可以通过如下命令修改它:
```bash
scrcpy --window-title 'My device'
```
#### 位置和大小
您可以指定初始的窗口位置和大小:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### 无边框
关闭边框:
```bash
scrcpy --window-borderless
```
#### 保持窗口在最前
您可以通过如下命令保持窗口在最前面:
```bash
scrcpy --always-on-top
```
#### 全屏
您可以通过如下命令直接全屏启动scrcpy
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
全屏状态可以通过<kbd>MOD</kbd>+<kbd>f</kbd>实时改变。
#### 旋转
通过如下命令,窗口可以旋转:
```bash
scrcpy --rotation 1
```
可选的值有:
- `0`: 无旋转
- `1`: 逆时针旋转90°
- `2`: 旋转180°
- `3`: 顺时针旋转90°
这同样可以使用<kbd>MOD</kbd>+<kbd></kbd>
_(左)_<kbd>MOD</kbd>+<kbd></kbd> _(右)_ 的快捷键实时更改。
需要注意的是, _scrcpy_ 控制三个不同的朝向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换(如果前台应用程序不支持所请求的朝向,可能会拒绝该请求)。
- `--lock-video-orientation` 改变镜像的朝向(设备镜像到电脑的画面朝向)。这会影响录制。
- `--rotation` (或<kbd>MOD</kbd>+<kbd></kbd>/<kbd>MOD</kbd>+<kbd></kbd>
只旋转窗口的画面。这只影响显示,不影响录制。
### 其他镜像设置
#### 只读
关闭电脑对设备的控制(如键盘输入、鼠标移动和文件传输):
```bash
scrcpy --no-control
scrcpy -n
```
#### 显示屏
如果有多个显示屏可用,您可以选择特定显示屏进行镜像:
```bash
scrcpy --display 1
```
您可以通过如下命令找到显示屏的id
```
adb shell dumpsys display # 在回显中搜索“mDisplayId=”
```
第二显示屏可能只能在设备运行Android 10或以上的情况下被控制它可能会在电脑上显示但无法通过电脑操作
#### 保持常亮
防止设备在已连接的状态下休眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
程序关闭后,设备设置会恢复原样。
#### 关闭设备屏幕
在启动屏幕镜像时,可以通过如下命令关闭设备的屏幕:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
或者在需要的时候按<kbd>MOD</kbd>+<kbd>o</kbd>
要重新打开屏幕的话,需要按<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
在Android上,`电源`按钮始终能把屏幕打开。
为了方便,如果按下`电源`按钮的事件是通过 _scrcpy_ 发出的(通过点按鼠标右键或<kbd>MOD</kbd>+<kbd>p</kbd>),它会在短暂的延迟后将屏幕关闭。
物理的`电源`按钮仍然能打开设备屏幕。
同时,这项功能还能被用于防止设备休眠:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 渲染超时帧
为了降低延迟, _scrcpy_ 默认渲染解码成功的最近一帧,并跳过前面任意帧。
强制渲染所有帧(可能导致延迟变高):
```bash
scrcpy --render-expired-frames
```
#### 显示触摸
在展示时,有些时候可能会用到显示触摸点这项功能(在设备上显示)。
Android在 _开发者设置_ 中提供了这项功能。
_Scrcpy_ 提供一个选项可以在启动时开启这项功能并在退出时恢复初始设置:
```bash
scrcpy --show-touches
scrcpy -t
```
请注意这项功能只能显示 _物理_ 触摸(要用手在屏幕上触摸)。
#### 关闭屏保
_Scrcpy_ 不会默认关闭屏幕保护。
关闭屏幕保护:
```bash
scrcpy --disable-screensaver
```
### 输入控制
#### 旋转设备屏幕
使用<kbd>MOD</kbd>+<kbd>r</kbd>以在竖屏和横屏模式之间切换。
需要注意的是,只有在前台应用程序支持所要求的模式时,才会进行切换。
#### 复制黏贴
每次Android的剪贴板变化的时候它都会被自动同步到电脑的剪贴板上。
所有的 <kbd>Ctrl</kbd> 快捷键都会被转发至设备。其中:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> 复制
- <kbd>Ctrl</kbd>+<kbd>x</kbd> 剪切
- <kbd>Ctrl</kbd>+<kbd>v</kbd> 黏贴 (在电脑到设备的剪贴板同步完成之后)
这通常如您所期望的那样运作。
但实际的行为取决于设备上的前台程序。
例如 _Termux_<kbd>Ctrl</kbd>+<kbd>c</kbd>被按下时发送 SIGINT
又如 _K-9 Mail_ 会新建一封新邮件。
在这种情况下剪切复制黏贴仅在Android >= 7时可用
- <kbd>MOD</kbd>+<kbd>c</kbd> 注入 `COPY`(复制)
- <kbd>MOD</kbd>+<kbd>x</kbd> 注入 `CUT`(剪切)
- <kbd>MOD</kbd>+<kbd>v</kbd> 注入 `PASTE`(黏贴)(在电脑到设备的剪贴板同步完成之后)
另外,<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>可以将电脑的剪贴板内容转换为一串按键事件输入到设备。
在应用程序不接受黏贴时(比如 _Termux_ ),这项功能可以排上一定的用场。
需要注意的是这项功能可能会导致非ASCII编码的内容出现错误。
**警告:** 将电脑剪贴板的内容黏贴至设备(无论是通过<kbd>Ctrl</kbd>+<kbd>v</kbd>还是<kbd>MOD</kbd>+<kbd>v</kbd>
都需要将内容保存至设备的剪贴板。如此,任何一个应用程序都可以读取它。
您应当避免将敏感内容通过这种方式传输(如密码)。
#### 捏拉缩放
模拟 “捏拉缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
更准确的说,您需要在按住<kbd>Ctrl</kbd>的同时按住并移动鼠标。
在鼠标左键松开之后,光标的任何操作都会相对于屏幕的中央进行。
具体来说, _scrcpy_ 使用“虚拟手指”以在相对于屏幕中央相反的位置产生触摸事件。
#### 文字注入偏好
打字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下/松开。
- _文本事件_ ,代表一个文本被输入。
程序默认使用按键事件来输入字母。只有这样键盘才会在游戏中正常运作尤其WASD键
但这也有可能[造成问题][prefertext]。如果您遇到了这样的问题,您可以通过下列操作避免它:
```bash
scrcpy --prefer-text
```
(这会导致键盘在游戏中工作不正常)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 按键重复
当你一直按着一个按键不放时,程序默认产生多个按键事件。
在某些游戏中这可能会导致性能问题。
避免转发重复按键事件:
```bash
scrcpy --no-key-repeat
```
### 文件传输
#### 安装APK
如果您要要安装APK请拖放APK文件文件名以`.apk`结尾)到 _scrcpy_ 窗口。
该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。
#### 将文件推送至设备
如果您要推送文件到设备的 `/sdcard/`请拖放文件至不能是APK文件_scrcpy_ 窗口。
该操作没有可见的响应,只会在控制台输出日志。
在启动时可以修改目标目录:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### 音频转发
_scrcpy_ 不支持音频。请使用 [sndcpy].
另外请阅读 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 热键
在下列表格中, <kbd>MOD</kbd> 是热键的修饰键。
默认是(左)<kbd>Alt</kbd>或者(左)<kbd>Super</kbd>
您可以使用 `--shortcut-mod`后缀来修改它。可选的按键有`lctrl``rctrl`
`lalt``ralt``lsuper``rsuper`。如下例:
```bash
# 使用右侧的Ctrl键
scrcpy --shortcut-mod=rctrl
# 使用左侧的Ctrl键、Alt键或Super键
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_一般来说<kbd>[Super]</kbd>就是<kbd>Windows</kbd>或者<kbd>Cmd</kbd>。_
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| 操作 | 快捷键
| ------------------------------------------- |:-----------------------------
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd></kbd> _(左)_
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd></kbd> _(右)_
| 将窗口大小重置为1:1 (像素优先) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击¹_
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _点击鼠标中键_
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _点击鼠标右键²_
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd>
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd></kbd> _(up)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd></kbd> _(down)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
| 打开屏幕 | _点击鼠标右键²_
| 关闭设备屏幕(但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd>
| 展开快捷操作 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并黏贴³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 导入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_点按并移动鼠标_
_¹双击黑色边界以关闭黑色边界_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下 返回键 。_
_³需要安卓版本 Android >= 7。_
所有的 <kbd>Ctrl</kbd>+_按键_ 的热键都是被转发到设备进行处理的,所以实际上会由当前应用程序对其做出响应。
## 自定义路径
为了使用您想使用的 _adb_ ,您可以在环境变量
`ADB`中设置它的路径:
ADB=/path/to/adb scrcpy
如果需要覆盖`scrcpy-server`的路径,您可以在
`SCRCPY_SERVER_PATH`中设置它。
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## 为什么叫 _scrcpy_
一个同事让我找出一个和[gnirehtet]一样难以发音的名字。
[`strcpy`] 可以复制**str**ing `scrcpy` 可以复制**scr**een。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 如何编译?
请查看[编译]。
[编译]: BUILD.md
## 常见问题
请查看[FAQ](FAQ.md).
## 开发者
请查看[开发者页面]。
[开发者页面]: DEVELOP.md
## 许可协议
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相关文章
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

705
README.zh-Hant.md Normal file
View File

@ -0,0 +1,705 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
_只有原版的 [README](README.md)是保證最新的。_
本文件翻譯時點: [521f2fe](https://github.com/Genymobile/scrcpy/commit/521f2fe994019065e938aa1a54b56b4f10a4ac4a#diff-04c6e90faac2675aa89e2176d2eec7d8)
# scrcpy (v1.15)
Scrcpy 可以透過 USB、或是 [TCP/IP][article-tcpip] 來顯示或控制 Android 裝置。且 scrcpy 不需要 _root_ 權限。
Scrcpy 目前支援 _GNU/Linux_、_Windows_ 和 _macOS_
![screenshot](assets/screenshot-debian-600.jpg)
特色:
- **輕量** (只顯示裝置螢幕)
- **效能** (30~60fps)
- **品質** (1920×1080 或更高)
- **低延遲** ([35~70ms][lowlatency])
- **快速啟動** (~1 秒就可以顯示第一個畫面)
- **非侵入性** (不安裝、留下任何東西在裝置上)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 需求
Android 裝置必須是 API 21+ (Android 5.0+)。
請確認裝置上的 [adb 偵錯 (通常位於開發者模式內)][enable-adb] 已啟用。
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
在部分的裝置上,你也必須啟用[特定的額外選項][control]才能使用鍵盤和滑鼠控制。
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## 下載/獲取軟體
### Linux
Debian (目前支援 _testing__sid_) 和 Ubuntu (20.04):
```
apt install scrcpy
```
[Snap] 上也可以下載: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
在 Fedora 上也可以使用 [COPR] 下載: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
在 Arch Linux 上也可以使用 [AUR] 下載: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
在 Gentoo 上也可以使用 [Ebuild] 下載: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
你也可以自己[編譯 _Scrcpy_][BUILD]。別擔心,並沒有想像中的難。
### Windows
為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包:
- [`scrcpy-win64-v1.15.zip`][direct-win64]
_(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip
[Chocolatey] 上也可以下載:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # 如果你還沒有安裝的話
```
[Scoop] 上也可以下載:
```bash
scoop install scrcpy
scoop install adb # 如果你還沒有安裝的話
```
[Scoop]: https://scoop.sh
你也可以自己[編譯 _Scrcpy_][BUILD]。
### macOS
_Scrcpy_ 可以在 [Homebrew] 上直接安裝:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
由於執行期間需要可以藉由 `PATH` 存取 `adb` 。如果還沒有安裝 `adb` 可以使用下列方式安裝:
```bash
brew cask install android-platform-tools
```
你也可以自己[編譯 _Scrcpy_][BUILD]。
## 執行
將電腦和你的 Android 裝置連線,然後執行:
```bash
scrcpy
```
_Scrcpy_ 可以接受命令列參數。輸入下列指令就可以瀏覽可以使用的命令列參數:
```bash
scrcpy --help
```
## 功能
> 以下說明中,有關快捷鍵的說明可能會出現 <kbd>MOD</kbd> 按鈕。相關說明請參見[快捷鍵]內的說明。
[快捷鍵]: #快捷鍵
### 畫面擷取
#### 縮小尺寸
使用比較低的解析度來投放 Android 裝置在某些情況可以提升效能。
限制寬和高的最大值(例如: 1024):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 縮短版本
```
比較小的參數會根據螢幕比例重新計算。
根據上面的範例1920x1080 會被縮小成 1024x576。
#### 更改 bit-rate
預設的 bit-rate 是 8 Mbps。如果要更改 bit-rate (例如: 2 Mbps):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 縮短版本
```
#### 限制 FPS
限制畫面最高的 FPS:
```bash
scrcpy --max-fps 15
```
僅在 Android 10 後正式支援,不過也有可能可以在 Android 10 以前的版本使用。
#### 裁切
裝置的螢幕可以裁切。如此一來,鏡像出來的螢幕就只會是原本的一部份。
假如只要鏡像 Oculus Go 的其中一隻眼睛:
```bash
scrcpy --crop 1224:1440:0:0 # 位於 (0,0)大小1224x1440
```
如果 `--max-size` 也有指定的話,裁切後才會縮放。
#### 鎖定影像方向
如果要鎖定鏡像影像方向:
```bash
scrcpy --lock-video-orientation 0 # 原本的方向
scrcpy --lock-video-orientation 1 # 逆轉 90°
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 順轉 90°
```
這會影響錄影結果的影像方向。
### 錄影
鏡像投放螢幕的同時也可以錄影:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
如果只要錄影,不要投放螢幕鏡像的話:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# 用 Ctrl+C 停止錄影
```
就算有些幀為了效能而被跳過,它們還是一樣會被錄製。
裝置上的每一幀都有時間戳記,所以 [封包延遲 (Packet Delay Variation, PDV)][packet delay variation] 並不會影響錄影的檔案。
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
### 連線
#### 無線
_Scrcpy_ 利用 `adb` 和裝置通訊,而 `adb` 可以[透過 TCP/IP 連結][connect]:
1. 讓電腦和裝置連到同一個 Wi-Fi。
2. 獲取手機的 IP 位址(設定 → 關於手機 → 狀態).
3. 啟用裝置上的 `adb over TCP/IP`: `adb tcpip 5555`.
4. 拔掉裝置上的線。
5. 透過 TCP/IP 連接裝置: `adb connect DEVICE_IP:5555` _(把 `DEVICE_IP` 換成裝置的IP位址)_.
6. 和平常一樣執行 `scrcpy`
如果效能太差,可以降低 bit-rate 和解析度:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 縮短版本
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
#### 多裝置
如果 `adb devices` 內有多個裝置,則必須附上 _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 縮短版本
```
如果裝置是透過 TCP/IP 連線:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # 縮短版本
```
你可以啟用復數個對應不同裝置的 _scrcpy_
#### 裝置連結後自動啟動
你可以使用 [AutoAdb]:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH tunnel
本地的 `adb` 可以連接到遠端的 `adb` 伺服器(假設兩者使用相同版本的 _adb_ 通訊協定),以連接到遠端裝置:
```bash
adb kill-server # 停止在 Port 5037 的本地 adb 伺服
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持開啟
```
從另外一個終端機:
```bash
scrcpy
```
如果要避免啟用 remote port forwarding你可以強制它使用 forward connection (注意 `-L``-R` 的差別):
```bash
adb kill-server # 停止在 Port 5037 的本地 adb 伺服
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持開啟
```
從另外一個終端機:
```bash
scrcpy --force-adb-forward
```
和無線連接一樣,有時候降低品質會比較好:
```
scrcpy -b2M -m800 --max-fps 15
```
### 視窗調整
#### 標題
預設標題是裝置的型號,不過可以透過以下方式修改:
```bash
scrcpy --window-title 'My device'
```
#### 位置 & 大小
初始的視窗位置和大小也可以指定:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### 無邊框
如果要停用視窗裝飾:
```bash
scrcpy --window-borderless
```
#### 保持最上層
如果要保持 `scrcpy` 的視窗在最上層:
```bash
scrcpy --always-on-top
```
#### 全螢幕
這個軟體可以直接在全螢幕模式下起動:
```bash
scrcpy --fullscreen
scrcpy -f # 縮短版本
```
全螢幕可以使用 <kbd>MOD</kbd>+<kbd>f</kbd> 開關。
#### 旋轉
視窗可以旋轉:
```bash
scrcpy --rotation 1
```
可用的數值:
- `0`: 不旋轉
- `1`: 90 度**逆**轉
- `2`: 180 度
- `3`: 90 度**順**轉
旋轉方向也可以使用 <kbd>MOD</kbd>+<kbd></kbd> _(左方向鍵)_<kbd>MOD</kbd>+<kbd></kbd> _(右方向鍵)_ 調整。
_scrcpy_ 有 3 種不同的旋轉:
- <kbd>MOD</kbd>+<kbd>r</kbd> 要求裝置在垂直、水平之間旋轉 (目前運行中的 App 有可能會因為不支援而拒絕)。
- `--lock-video-orientation` 修改鏡像的方向 (裝置傳給電腦的影像)。這會影響錄影結果的影像方向。
- `--rotation` (或是 <kbd>MOD</kbd>+<kbd></kbd> / <kbd>MOD</kbd>+<kbd></kbd>) 只旋轉視窗的內容。這只會影響鏡像結果,不會影響錄影結果。
### 其他鏡像選項
#### 唯讀
停用所有控制,包含鍵盤輸入、滑鼠事件、拖放檔案:
```bash
scrcpy --no-control
scrcpy -n
```
#### 顯示螢幕
如果裝置有複數個螢幕,可以指定要鏡像哪個螢幕:
```bash
scrcpy --display 1
```
可以透過下列指令獲取螢幕 ID:
```
adb shell dumpsys display # 找輸出結果中的 "mDisplayId="
```
第二螢幕只有在 Android 10+ 時可以控制。如果不是 Android 10+,螢幕就會在唯讀狀態下投放。
#### 保持清醒
如果要避免裝置在連接狀態下進入睡眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
_scrcpy_ 關閉後就會回復成原本的設定。
#### 關閉螢幕
鏡像開始時,可以要求裝置關閉螢幕:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
或是在任何時候輸入 <kbd>MOD</kbd>+<kbd>o</kbd>
如果要開啟螢幕,輸入 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
在 Android 上,`POWER` 按鈕總是開啟螢幕。
為了方便,如果 `POWER` 是透過 scrcpy 轉送 (右鍵 或 <kbd>MOD</kbd>+<kbd>p</kbd>)的話,螢幕將會在短暫的延遲後關閉。
實際在手機上的 `POWER` 還是會開啟螢幕。
防止裝置進入睡眠狀態:
```bash
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 顯示過期的幀
為了降低延遲, _scrcpy_ 預設只會顯示最後解碼的幀,並且拋棄所有在這之前的幀。
如果要強制顯示所有的幀 (有可能會拉高延遲),輸入:
```bash
scrcpy --render-expired-frames
```
#### 顯示觸控點
對於要報告的人來說,顯示裝置上的實際觸控點有時候是有幫助的。
Android 在_開發者選項_中有提供這個功能。
_Scrcpy_ 可以在啟動時啟用這個功能,並且在停止後恢復成原本的設定:
```bash
scrcpy --show-touches
scrcpy -t
```
這個選項只會顯示**實際觸碰在裝置上的觸碰點**。
### 輸入控制
#### 旋轉裝置螢幕
輸入 <kbd>MOD</kbd>+<kbd>r</kbd> 以在垂直、水平之間切換。
如果使用中的程式不支援,則不會切換。
#### 複製/貼上
如果 Android 剪貼簿上的內容有任何更動,電腦的剪貼簿也會一起更動。
任何與 <kbd>Ctrl</kbd> 相關的快捷鍵事件都會轉送到裝置上。特別來說:
- <kbd>Ctrl</kbd>+<kbd>c</kbd> 通常是複製
- <kbd>Ctrl</kbd>+<kbd>x</kbd> 通常是剪下
- <kbd>Ctrl</kbd>+<kbd>v</kbd> 通常是貼上 (在電腦的剪貼簿與裝置上的剪貼簿同步之後)
這些跟你通常預期的行為一樣。
但是,實際上的行為是根據目前運行中的應用程式而定。
舉例來說, _Termux_ 在收到 <kbd>Ctrl</kbd>+<kbd>c</kbd> 後,會傳送 SIGINT_K-9 Mail_ 則是建立新訊息。
如果在這情況下,要剪下、複製或貼上 (只有在Android 7+時才支援):
- <kbd>MOD</kbd>+<kbd>c</kbd> 注入 `複製`
- <kbd>MOD</kbd>+<kbd>x</kbd> 注入 `剪下`
- <kbd>MOD</kbd>+<kbd>v</kbd> 注入 `貼上` (在電腦的剪貼簿與裝置上的剪貼簿同步之後)
另外,<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> 則是以一連串的按鍵事件貼上電腦剪貼簿中的內容。當元件不允許文字貼上 (例如 _Termux_) 時,這就很有用。不過,這在非 ASCII 內容上就無法使用。
**警告:** 貼上電腦的剪貼簿內容 (無論是從 <kbd>Ctrl</kbd>+<kbd>v</kbd><kbd>MOD</kbd>+<kbd>v</kbd>) 時,會複製剪貼簿中的內容至裝置的剪貼簿上。這會讓所有 Android 程式讀取剪貼簿的內容。請避免貼上任何敏感內容 (像是密碼)。
#### 文字輸入偏好
輸入文字時,有兩種[事件][textevents]會被觸發:
- _鍵盤事件 (key events)_代表有一個按鍵被按下或放開
- _文字事件 (text events)_代表有一個文字被輸入
預設上,文字是被以鍵盤事件 (key events) 輸入的,所以鍵盤和遊戲內所預期的一樣 (通常是指 WASD)。
但是這可能造成[一些問題][prefertext]。如果在這輸入這方面遇到了問題,你可以試試:
```bash
scrcpy --prefer-text
```
(不過遊戲內鍵盤就會不可用)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 重複輸入
通常來說,長時間按住一個按鍵會重複觸發按鍵事件。這會在一些遊戲中造成效能問題,而且這個重複的按鍵事件是沒有意義的。
如果不要轉送這些重複的按鍵事件:
```bash
scrcpy --no-key-repeat
```
### 檔案
#### 安裝 APK
如果要安裝 APK ,拖放一個 APK 檔案 (以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。
視窗上不會有任何反饋;結果會顯示在命令列中。
#### 推送檔案至裝置
如果要推送檔案到裝置上的 `/sdcard/` ,拖放一個非 APK 檔案 (**不**以 `.apk` 為副檔名) 到 _scrcpy_ 的視窗上。
視窗上不會有任何反饋;結果會顯示在命令列中。
推送檔案的目標路徑可以在啟動時指定:
```bash
scrcpy --push-target /sdcard/foo/bar/
```
### 音訊轉送
_scrcpy_ **不**轉送音訊。請使用 [sndcpy]。另外,參見 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## 快捷鍵
在以下的清單中,<kbd>MOD</kbd> 是快捷鍵的特殊按鍵。通常來說,這個按鍵是 (左) <kbd>Alt</kbd> 或是 (左) <kbd>Super</kbd>
這個是可以使用 `--shortcut-mod` 更改的。可以用的選項有:
- `lctrl`: 左邊的 <kbd>Ctrl</kbd>
- `rctrl`: 右邊的 <kbd>Ctrl</kbd>
- `lalt`: 左邊的 <kbd>Alt</kbd>
- `ralt`: 右邊的 <kbd>Alt</kbd>
- `lsuper`: 左邊的 <kbd>Super</kbd>
- `rsuper`: 右邊的 <kbd>Super</kbd>
```bash
# 以 右邊的 Ctrl 為快捷鍵特殊按鍵
scrcpy --shortcut-mod=rctrl
# 以 左邊的 Ctrl 和左邊的 Alt 或是 左邊的 Super 為快捷鍵特殊按鍵
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> 通常是 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 鍵。_
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Action | Shortcut
| ------------------------------------------------- |:-----------------------------
| 切換至全螢幕 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 左旋顯示螢幕 | <kbd>MOD</kbd>+<kbd></kbd> _(左)_
| 右旋顯示螢幕 | <kbd>MOD</kbd>+<kbd></kbd> _(右)_
| 縮放視窗成 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 縮放視窗到沒有黑邊框為止 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _雙擊¹_
| 按下 `首頁` 鍵 | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中鍵_
| 按下 `返回` 鍵 | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右鍵²_
| 按下 `切換 APP` 鍵 | <kbd>MOD</kbd>+<kbd>s</kbd>
| 按下 `選單` 鍵 (或解鎖螢幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 按下 `音量+` 鍵 | <kbd>MOD</kbd>+<kbd></kbd> _(上)_
| 按下 `音量-` 鍵 | <kbd>MOD</kbd>+<kbd></kbd> _(下)_
| 按下 `電源` 鍵 | <kbd>MOD</kbd>+<kbd>p</kbd>
| 開啟 | _右鍵²_
| 關閉裝置螢幕(持續鏡像) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 開啟裝置螢幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋轉裝置螢幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 開啟通知列 | <kbd>MOD</kbd>+<kbd>n</kbd>
| 關閉通知列 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 複製至剪貼簿³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪下至剪貼簿³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪貼簿並貼上³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 複製電腦剪貼簿中的文字至裝置並貼上 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 啟用/停用 FPS 計數器(顯示於 stdout - 通常是命令列) | <kbd>MOD</kbd>+<kbd>i</kbd>
_¹在黑邊框上雙擊以移除它們。_
_²右鍵會返回。如果螢幕是關閉狀態則會打開螢幕。_
_³只支援 Android 7+。_
所有 <kbd>Ctrl</kbd>+_按鍵_ 快捷鍵都會傳送到裝置上,所以它們是由目前運作的應用程式處理的。
## 自訂路徑
如果要使用特定的 _adb_ ,將它設定到環境變數中的 `ADB`:
ADB=/path/to/adb scrcpy
如果要覆寫 `scrcpy-server` 檔案的路徑,則將路徑設定到環境變數中的 `SCRCPY_SERVER_PATH`
[相關連結][useful]
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## 為何叫 _scrcpy_
有一個同事要我找一個跟 [gnirehtet] 一樣難念的名字。
[`strcpy`] 複製一個字串 (**str**ing)`scrcpy` 複製一個螢幕 (**scr**een)。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## 如何編譯?
請看[這份文件 (英文)][BUILD]。
[BUILD]: BUILD.md
## 常見問題
請看[這份文件 (英文)][FAQ]。
[FAQ]: FAQ.md
## 開發者文件
請看[這個頁面 (英文)][developers page].
[developers page]: DEVELOP.md
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 相關文章
- [Scrcpy 簡介 (英文)][article-intro]
- [Scrcpy 可以無線連線了 (英文)][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

View File

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

View File

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1 +0,0 @@
@cmd

View File

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

View File

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

View File

@ -1,70 +0,0 @@
#compdef -N scrcpy -N scrcpy.exe
#
# name: scrcpy
# auth: hltdev [hltdev8642@gmail.com]
# desc: completion file for scrcpy (all OSes)
#
local arguments
arguments=(
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
{-b,--bit-rate=}'[Encode the video at the given bit-rate]'
'--codec-options=[Set a list of comma-separated key\:type=value options for the device encoder]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display=[Specify the display id to mirror]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
{-e,--select-tcpip}'[Use TCP/IP device]'
'--encoder=[Use a specific MediaCodec encoder \(must be a H.264 encoder\)]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-f,--fullscreen}'[Start in fullscreen]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
'--max-fps=[Limit the frame rate of screen capture]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
{-m,--max-size=}'[Limit both the width and height of the video to value]'
'--no-cleanup[Disable device cleanup actions on exit]'
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]'
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
'--power-off-on-close[Turn the device screen off when closing scrcpy]'
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
'--print-fps[Start FPS counter, to print frame logs to the console]'
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
{-r,--record=}'[Record screen to file]:record file:_files'
'--record-format=[Force recording format]:format:(mp4 mkv)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]: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

View File

@ -1,120 +1,70 @@
src = [
'src/main.c',
'src/adb/adb.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/adb/adb_tunnel.c',
'src/adb.c',
'src/cli.c',
'src/clock.c',
'src/compat.c',
'src/control_msg.c',
'src/controller.c',
'src/decoder.c',
'src/demuxer.c',
'src/device.c',
'src/device_msg.c',
'src/icon.c',
'src/file_pusher.c',
'src/event_converter.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/opengl.c',
'src/options.c',
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/version.c',
'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.c',
'src/util/acksync.c',
'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c',
'src/util/log.c',
'src/util/net.c',
'src/util/net_intr.c',
'src/util/process.c',
'src/util/process_intr.c',
'src/util/rand.c',
'src/util/strbuf.c',
'src/util/str.c',
'src/util/term.c',
'src/util/str_util.c',
'src/util/thread.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'
windows = import('windows')
src += [
'src/sys/win/file.c',
'src/sys/win/process.c',
windows.compile_resources('scrcpy-windows.rc'),
]
conf.set('_WIN32_WINNT', '0x0600')
conf.set('WINVER', '0x0600')
src += [ 'src/sys/win/process.c' ]
else
src += [
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
if host_machine.system() == 'darwin'
conf.set('_DARWIN_C_SOURCE', true)
endif
src += [ 'src/sys/unix/process.c' ]
endif
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux'
v4l2_support = host_machine.system() == 'linux'
if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif
usb_support = get_option('usb')
if usb_support
src += [
'src/usb/aoa_hid.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',
]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
if not crossbuild_windows
if not get_option('crossbuild_windows')
# native build
dependencies = [
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('sdl2', version: '>= 2.0.5'),
dependency('sdl2'),
]
if v4l2_support
dependencies += dependency('libavdevice')
endif
if usb_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
@ -124,40 +74,22 @@ else
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
ffmpeg = declare_dependency(
dependencies: [
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [
ffmpeg,
sdl2,
libusb,
cc.find_library('mingw32')
]
@ -167,13 +99,7 @@ if host_machine.system() == 'windows'
dependencies += cc.find_library('ws2_32')
endif
check_functions = [
'strdup',
'asprintf',
'vasprintf',
'nrand48',
'jrand48',
]
conf = configuration_data()
foreach f : check_functions
if cc.has_function(f)
@ -182,9 +108,6 @@ foreach f : check_functions
endif
endforeach
conf.set('HAVE_SOCK_CLOEXEC', host_machine.system() != 'windows' and
cc.has_header_symbol('sys/socket.h', 'SOCK_CLOEXEC'))
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -213,9 +136,6 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
# enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support)
# enable HID over AOA support (linux only)
conf.set('HAVE_USB', usb_support)
configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src')
@ -226,26 +146,7 @@ executable('scrcpy', src,
install: true,
c_args: [])
# <https://mesonbuild.com/Builtin-options.html#directories>
datadir = get_option('datadir') # by default 'share'
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
@ -253,15 +154,8 @@ endif
# do not build tests in release (assertions would not be executed at all)
if get_option('buildtype') == 'debug'
tests = [
['test_adb_parser', [
'tests/test_adb_parser.c',
'src/adb/adb_device.c',
'src/adb/adb_parser.c',
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_binary', [
'tests/test_binary.c',
['test_buffer_util', [
'tests/test_buffer_util.c'
]],
['test_cbuf', [
'tests/test_cbuf.c',
@ -269,22 +163,12 @@ if get_option('buildtype') == 'debug'
['test_cli', [
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/log.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',
]],
['test_clock', [
'tests/test_clock.c',
'src/clock.c',
'src/util/str_util.c',
]],
['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c',
'src/control_msg.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/str_util.c',
]],
['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c',
@ -293,17 +177,9 @@ if get_option('buildtype') == 'debug'
['test_queue', [
'tests/test_queue.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',
]],
['test_str', [
'tests/test_str.c',
'src/util/str.c',
'src/util/strbuf.c',
]],
['test_vector', [
'tests/test_vector.c',
['test_strutil', [
'tests/test_strutil.c',
'src/util/str_util.c',
]],
]

View File

@ -1 +0,0 @@
/data

View File

@ -1,22 +0,0 @@
PREBUILT_DATA_DIR=data
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

View File

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-33.0.3
FILENAME=platform-tools_r33.0.3-windows.zip
SHA256SUM=1e59afd40a74c5c0eab0a9fad3f0faf8a674267106e0b19921be9f67081808c2
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://dl.google.com/android/repository/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

View File

@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win32-4.3.1
FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip
SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2
FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip
SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \
"$FILENAME_SHARED" "$SHA256SUM_SHARED"
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \
"$FILENAME_DEV" "$SHA256SUM_DEV"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared
unzip "../$FILENAME_SHARED" \
"$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \
"$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \
"$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \
"$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \
"$ZIP_PREFIX_SHARED"/bin/swscale-5.dll
ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev
unzip "../$FILENAME_DEV" \
"$ZIP_PREFIX_DEV/include/*"
mv "$ZIP_PREFIX_SHARED"/* .
mv "$ZIP_PREFIX_DEV"/* .
rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV"

View File

@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=5.1.2
DEP_DIR=ffmpeg-win64-$VERSION
FILENAME=ffmpeg-$VERSION-full_build-shared.7z
SHA256SUM=d9eb97b72d7cfdae4d0f7eaea59ccffb8c364d67d88018ea715d5e2e193f00e9
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/GyanD/codexffmpeg/releases/download/$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg-$VERSION-full_build-shared
7z x "../$FILENAME" \
"$ZIP_PREFIX"/bin/avutil-57.dll \
"$ZIP_PREFIX"/bin/avcodec-59.dll \
"$ZIP_PREFIX"/bin/avformat-59.dll \
"$ZIP_PREFIX"/bin/swresample-4.dll \
"$ZIP_PREFIX"/bin/swscale-6.dll \
"$ZIP_PREFIX"/include
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

View File

@ -1,34 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.26
FILENAME=libusb-1.0.26-binaries.7z
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
# include/ is the same in all folders of the archive
7z x "../$FILENAME" \
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
mv libusb-1.0.26-binaries/libusb-MinGW-Win32/bin MinGW-Win32
mv libusb-1.0.26-binaries/libusb-MinGW-x64/bin MinGW-x64
mv libusb-1.0.26-binaries/libusb-MinGW-x64/include .
rm -rf libusb-1.0.26-binaries

View File

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=SDL2-2.26.1
FILENAME=SDL2-devel-2.26.1-mingw.tar.gz
SHA256SUM=aa43e1531a89551f9f9e14b27953a81d4ac946a9e574b5813cd0f2b36e83cc1c
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
tar xf "../$FILENAME" --strip-components=1 \
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \

View File

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

View File

@ -1,23 +0,0 @@
#include <winuser.h>
0 ICON "data/icon.ico"
1 RT_MANIFEST "scrcpy-windows.manifest"
2 VERSIONINFO
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Display and control your Android device"
VALUE "InternalName", "scrcpy"
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "1.25"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View File

@ -26,11 +26,7 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
Default is 8000000.
.TP
.BI "\-\-codec " name
Select a video codec (h264, h265 or av1).
.TP
.BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
.BI "\-\-codec\-options " key[:type]=value[,...]
Set a list of comma-separated key:type=value options for the device encoder.
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
@ -47,12 +43,6 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size
value is computed on the cropped size.
.TP
.B \-d, \-\-select\-usb
Use USB device (if there is exactly one, like adb -d).
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.TP
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
@ -66,18 +56,6 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0.
.TP
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP
.B \-e, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one, like adb -e).
Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
.TP
.BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder).
@ -98,22 +76,6 @@ Start in fullscreen.
.B \-h, \-\-help
Print this help.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-hid\-mouse\fR.
.TP
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -121,8 +83,8 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
.BI "\-\-lock\-video\-orientation " [value]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
Default is "unlocked".
@ -138,36 +100,6 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB.
Also see \fB\-\-hid\-keyboard\fR.
.TP
.B \-\-no\-cleanup
By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit.
This option disables this cleanup.
.TP
.B \-\-no\-clipboard\-autosync
By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes.
This option disables this automatic synchronization.
.TP
.B \-\-no\-downsize\-on\-error
By default, on MediaCodec error, scrcpy automatically tries again with a lower definition.
This option disables this behavior.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
@ -185,33 +117,11 @@ Do not forward repeated key events when a key is held down.
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP
.B \-\-no\-power\-on
Do not power on the device on start.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
In this mode, adb (USB debugging) is not necessary, and mirroring is disabled.
LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mouse back to the computer.
If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both.
It may only work over USB.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
.TP
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.
Default is 27183:27199.
.TP
.B \-\-power\-off\-on\-close
Turn the device screen off when closing scrcpy.
.TP
.B \-\-prefer\-text
Inject alpha characters and space as text events instead of key events.
@ -219,19 +129,11 @@ Inject alpha characters and space as text events instead of key events.
This avoids issues when combining multiple keys to enter special characters,
but breaks the expected behavior of alpha keys in games (typically WASD).
.TP
.B "\-\-print\-fps
Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i.
.TP
.BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
Default is "/sdcard/".
.TP
.BI "\-r, \-\-record " file
@ -264,7 +166,7 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
.BI "\-\-shortcut\-mod " key[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
@ -273,14 +175,6 @@ For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctr
Default is "lalt,lsuper" (left-Alt or left-Super).
.TP
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
@ -291,35 +185,15 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP
.BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device.
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR).
.TP
.BI "\-\-v4l2-buffer " ms
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
Default is 0 (no buffering).
It requires to lock the video orientation (see --lock-video-orientation).
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("verbose", "debug", "info", "warn" or "error").
Set the log level ("debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds.
@ -343,36 +217,30 @@ Set a custom window title.
.BI "\-\-window\-x " value
Set the initial window horizontal position.
Default is "auto".
Default is "auto".\n
.TP
.BI "\-\-window\-y " value
Set the initial window vertical position.
Default is "auto".
Default is "auto".\n
.TP
.BI "\-\-window\-width " value
Set the initial window width.
Default is 0 (automatic).
Default is 0 (automatic).\n
.TP
.BI "\-\-window\-height " value
Set the initial window height.
Default is 0 (automatic).
.SH EXIT STATUS
.B scrcpy
will exit with code 0 on normal program termination. If an initial
connection cannot be established, the exit code 1 will be returned. If the
device disconnects while a session is active, exit code 2 will be returned.
Default is 0 (automatic).\n
.SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \fB\-\-shortcut\-mod\fR (see above).
Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
.TP
.B MOD+f
@ -474,28 +342,16 @@ Pinch-to-zoom from the center of the screen
.B Drag & drop APK file
Install APK from computer
.TP
.B Drag & drop non-APK file
Push file to device (see \fB\-\-push\-target\fR)
.SH Environment variables
.TP
.B ADB
Path to adb.
.TP
.B ANDROID_SERIAL
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
.TP
.B SCRCPY_ICON_PATH
Path to the program icon.
Specify the path to adb.
.TP
.B SCRCPY_SERVER_PATH
Path to the server binary.
Specify the path to server binary.
.SH AUTHORS
@ -520,7 +376,7 @@ Copyright \(co 2018 Genymobile
Genymobile
.UE
Copyright \(co 2018\-2022
Copyright \(co 2018\-2020
.MT rom@rom1v.com
Romain Vimont
.ME

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

@ -0,0 +1,211 @@
#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[]) {
char buf[512];
switch (err) {
case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, sizeof(buf));
LOGE("Failed to execute: %s", buf);
break;
case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, sizeof(buf));
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;
}
}
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
const char *cmd[len + 4];
int i;
process_t process;
cmd[0] = get_adb_command();
if (serial) {
cmd[1] = "-s";
cmd[2] = serial;
i = 3;
} else {
i = 1;
}
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL;
enum process_result r = process_execute(cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd);
return PROCESS_NONE;
}
return process;
}
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;
}

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

@ -0,0 +1,34 @@
#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_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);
#endif

View File

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

View File

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

View File

@ -1,43 +0,0 @@
#include "adb_device.h"
#include <stdlib.h>
#include <string.h>
void
sc_adb_device_destroy(struct sc_adb_device *device) {
free(device->serial);
free(device->state);
free(device->model);
}
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src) {
*dst = *src;
src->serial = NULL;
src->state = NULL;
src->model = NULL;
}
void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices) {
for (size_t i = 0; i < devices->size; ++i) {
sc_adb_device_destroy(&devices->data[i]);
}
sc_vector_destroy(devices);
}
enum sc_adb_device_type
sc_adb_device_get_type(const char *serial) {
// Starts with "emulator-"
if (!strncmp(serial, "emulator-", sizeof("emulator-") - 1)) {
return SC_ADB_DEVICE_TYPE_EMULATOR;
}
// If the serial contains a ':', then it is a TCP/IP device (it is
// sufficient to distinguish an ip:port from a real USB serial)
if (strchr(serial, ':')) {
return SC_ADB_DEVICE_TYPE_TCPIP;
}
return SC_ADB_DEVICE_TYPE_USB;
}

View File

@ -1,50 +0,0 @@
#ifndef SC_ADB_DEVICE_H
#define SC_ADB_DEVICE_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include "util/vector.h"
struct sc_adb_device {
char *serial;
char *state;
char *model;
bool selected;
};
enum sc_adb_device_type {
SC_ADB_DEVICE_TYPE_USB,
SC_ADB_DEVICE_TYPE_TCPIP,
SC_ADB_DEVICE_TYPE_EMULATOR,
};
struct sc_vec_adb_devices SC_VECTOR(struct sc_adb_device);
void
sc_adb_device_destroy(struct sc_adb_device *device);
/**
* Move src to dst
*
* After this call, the content of src is undefined, except that
* sc_adb_device_destroy() can be called.
*
* This is useful to take a device from a list that will be destroyed, without
* making unnecessary copies.
*/
void
sc_adb_device_move(struct sc_adb_device *dst, struct sc_adb_device *src);
void
sc_adb_devices_destroy(struct sc_vec_adb_devices *devices);
/**
* Deduce the device type from the serial
*/
enum sc_adb_device_type
sc_adb_device_get_type(const char *serial);
#endif

View File

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

View File

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

View File

@ -1,172 +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"
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,
const char *device_socket_name,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!sc_adb_reverse(intr, serial, device_socket_name, port,
SC_ADB_NO_STDOUT)) {
// the command itself failed, it will fail on any port
return false;
}
// 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, device_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,
const char *device_socket_name,
struct sc_port_range port_range) {
tunnel->forward = true;
uint16_t port = port_range.first;
for (;;) {
if (sc_adb_forward(intr, serial, port, device_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, const char *device_socket_name,
struct sc_port_range port_range, bool force_adb_forward) {
assert(!tunnel->enabled);
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(tunnel, intr, serial,
device_socket_name, 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,
device_socket_name, port_range);
}
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name) {
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, device_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;
}

View File

@ -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, const char *device_socket_name,
struct sc_port_range port_range, bool force_adb_forward);
/**
* Close the tunnel
*/
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name);
#endif

View File

@ -21,7 +21,7 @@
#define _ANDROID_INPUT_H
/**
* Meta key / modifier state.
* Meta key / modifer state.
*/
enum android_metastate {
/** No meta keys are pressed. */

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,111 +0,0 @@
#include "clock.h"
#include "util/log.h"
#define SC_CLOCK_NDEBUG // comment to debug
void
sc_clock_init(struct sc_clock *clock) {
clock->count = 0;
clock->head = 0;
clock->left_sum.system = 0;
clock->left_sum.stream = 0;
clock->right_sum.system = 0;
clock->right_sum.stream = 0;
}
// Estimate the affine function f(stream) = slope * stream + offset
static void
sc_clock_estimate(struct sc_clock *clock,
double *out_slope, sc_tick *out_offset) {
assert(clock->count > 1); // two points are necessary
struct sc_clock_point left_avg = {
.system = clock->left_sum.system / (clock->count / 2),
.stream = clock->left_sum.stream / (clock->count / 2),
};
struct sc_clock_point right_avg = {
.system = clock->right_sum.system / ((clock->count + 1) / 2),
.stream = clock->right_sum.stream / ((clock->count + 1) / 2),
};
double slope = (double) (right_avg.system - left_avg.system)
/ (right_avg.stream - left_avg.stream);
if (clock->count < SC_CLOCK_RANGE) {
/* The first frames are typically received and decoded with more delay
* than the others, causing a wrong slope estimation on start. To
* compensate, assume an initial slope of 1, then progressively use the
* estimated slope. */
slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count))
/ SC_CLOCK_RANGE;
}
struct sc_clock_point global_avg = {
.system = (clock->left_sum.system + clock->right_sum.system)
/ clock->count,
.stream = (clock->left_sum.stream + clock->right_sum.stream)
/ clock->count,
};
sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope);
*out_slope = slope;
*out_offset = offset;
}
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
struct sc_clock_point *point = &clock->points[clock->head];
if (clock->count == SC_CLOCK_RANGE || clock->count & 1) {
// One point passes from the right sum to the left sum
unsigned mid;
if (clock->count == SC_CLOCK_RANGE) {
mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE;
} else {
// Only for the first frames
mid = clock->count / 2;
}
struct sc_clock_point *mid_point = &clock->points[mid];
clock->left_sum.system += mid_point->system;
clock->left_sum.stream += mid_point->stream;
clock->right_sum.system -= mid_point->system;
clock->right_sum.stream -= mid_point->stream;
}
if (clock->count == SC_CLOCK_RANGE) {
// The current point overwrites the previous value in the circular
// array, update the left sum accordingly
clock->left_sum.system -= point->system;
clock->left_sum.stream -= point->stream;
} else {
++clock->count;
}
point->system = system;
point->stream = stream;
clock->right_sum.system += system;
clock->right_sum.stream += stream;
clock->head = (clock->head + 1) % SC_CLOCK_RANGE;
if (clock->count > 1) {
// Update estimation
sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %f * pts + %" PRItick,
clock->slope, clock->offset);
#endif
}
}
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) {
assert(clock->count > 1); // sc_clock_update() must have been called
return (sc_tick) (stream * clock->slope) + clock->offset;
}

View File

@ -1,70 +0,0 @@
#ifndef SC_CLOCK_H
#define SC_CLOCK_H
#include "common.h"
#include <assert.h>
#include "util/tick.h"
#define SC_CLOCK_RANGE 32
static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even");
struct sc_clock_point {
sc_tick system;
sc_tick stream;
};
/**
* The clock aims to estimate the affine relation between the stream (device)
* time and the system time:
*
* f(stream) = slope * stream + offset
*
* To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps
* of a frame expressed both in stream time and system time) in a circular
* array.
*
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
* point"). The slope of the estimated affine function is that of the line
* passing through these two points.
*
* To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE
* points. The resulting affine function passes by this centroid.
*
* With a circular array, the rolling sums (and average) are quick to compute.
* In practice, the estimation is stable and the evolution is smooth.
*/
struct sc_clock {
// Circular array
struct sc_clock_point points[SC_CLOCK_RANGE];
// Number of points in the array (count <= SC_CLOCK_RANGE)
unsigned count;
// Index of the next point to write
unsigned head;
// Sum of the first count/2 points
struct sc_clock_point left_sum;
// Sum of the last (count+1)/2 points
struct sc_clock_point right_sum;
// Estimated slope and offset
// (computed on sc_clock_update(), used by sc_clock_to_system_time())
double slope;
sc_tick offset;
};
void
sc_clock_init(struct sc_clock *clock);
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream);
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream);
#endif

View File

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

View File

@ -2,12 +2,6 @@
#include "config.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
@ -18,80 +12,3 @@ char *strdup(const char *s) {
return dup;
}
#endif
#ifndef HAVE_ASPRINTF
int asprintf(char **strp, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
int ret = vasprintf(strp, fmt, va);
va_end(va);
return ret;
}
#endif
#ifndef HAVE_VASPRINTF
int vasprintf(char **strp, const char *fmt, va_list ap) {
va_list va;
va_copy(va, ap);
int len = vsnprintf(NULL, 0, fmt, va);
va_end(va);
char *str = malloc(len + 1);
if (!str) {
return -1;
}
va_copy(va, ap);
int len2 = vsnprintf(str, len + 1, fmt, va);
(void) len2;
assert(len == len2);
va_end(va);
*strp = str;
return len;
}
#endif
#if !defined(HAVE_NRAND48) || !defined(HAVE_JRAND48)
#define SC_RAND48_MASK UINT64_C(0xFFFFFFFFFFFF) // 48 bits
#define SC_RAND48_A UINT64_C(0x5DEECE66D)
#define SC_RAND48_C 0xB
static inline uint64_t rand_iter48(uint64_t x) {
assert((x & ~SC_RAND48_MASK) == 0);
return (x * SC_RAND48_A + SC_RAND48_C) & SC_RAND48_MASK;
}
static uint64_t rand_iter48_xsubi(unsigned short xsubi[3]) {
uint64_t x = ((uint64_t) xsubi[0] << 32)
| ((uint64_t) xsubi[1] << 16)
| xsubi[2];
x = rand_iter48(x);
xsubi[0] = (x >> 32) & 0XFFFF;
xsubi[1] = (x >> 16) & 0XFFFF;
xsubi[2] = x & 0XFFFF;
return x;
}
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]) {
// range [0, 2^31)
return rand_iter48_xsubi(xsubi) >> 17;
}
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]) {
// range [-2^31, 2^31)
union {
uint32_t u;
int32_t i;
} v;
v.u = rand_iter48_xsubi(xsubi) >> 16;
return v.i;
}
#endif
#endif

View File

@ -1,19 +1,16 @@
#ifndef SC_COMPAT_H
#define SC_COMPAT_H
#ifndef COMPAT_H
#define COMPAT_H
#include "config.h"
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
#ifndef __WIN32
# define PRIu64_ PRIu64
# define SC_PRIsizet "zu"
#else
# define PRIu64_ "I64u" // Windows...
# define SC_PRIsizet "Iu"
#endif
// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
@ -25,21 +22,13 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// In ffmpeg/doc/APIchanges:
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
// Deprecate AVFormatContext filename field which had limited length, use the
// new dynamically allocated url field instead.
//
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
// Add url field to AVFormatContext and add ff_format_set_url helper function.
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
@ -51,20 +40,4 @@
char *strdup(const char *s);
#endif
#ifndef HAVE_ASPRINTF
int asprintf(char **strp, const char *fmt, ...);
#endif
#ifndef HAVE_VASPRINTF
int vasprintf(char **strp, const char *fmt, va_list ap);
#endif
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]);
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]);
#endif
#endif

View File

@ -1,155 +1,90 @@
#include "control_msg.h"
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "util/binary.h"
#include "util/buffer_util.h"
#include "util/log.h"
#include "util/str.h"
/**
* Map an enum value to a string based on an array, without crashing on an
* out-of-bounds index.
*/
#define ENUM_TO_LABEL(labels, value) \
((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???")
#define KEYEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_keyevent_action_labels, value)
#define MOTIONEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_motionevent_action_labels, value)
#define SCREEN_POWER_MODE_LABEL(value) \
ENUM_TO_LABEL(screen_power_mode_labels, value)
static const char *const android_keyevent_action_labels[] = {
"down",
"up",
"multi",
};
static const char *const android_motionevent_action_labels[] = {
"down",
"up",
"move",
"cancel",
"outside",
"pointer-down",
"pointer-up",
"hover-move",
"scroll",
"hover-enter",
"hover-exit",
"btn-press",
"btn-release",
};
static const char *const screen_power_mode_labels[] = {
"off",
"doze",
"normal",
"doze-suspend",
"suspend",
};
static const char *const copy_key_labels[] = {
"none",
"copy",
"cut",
};
static inline const char *
get_well_known_pointer_id_name(uint64_t pointer_id) {
switch (pointer_id) {
case POINTER_ID_MOUSE:
return "mouse";
case POINTER_ID_GENERIC_FINGER:
return "finger";
case POINTER_ID_VIRTUAL_MOUSE:
return "vmouse";
case POINTER_ID_VIRTUAL_FINGER:
return "vfinger";
default:
return NULL;
}
}
#include "util/str_util.h"
static void
write_position(uint8_t *buf, const struct sc_position *position) {
sc_write32be(&buf[0], position->point.x);
sc_write32be(&buf[4], position->point.y);
sc_write16be(&buf[8], position->screen_size.width);
sc_write16be(&buf[10], position->screen_size.height);
write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height);
}
// write length (4 bytes) + string (non null-terminated)
// write length (2 bytes) + string (non nul-terminated)
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
sc_write32be(buf, len);
size_t len = utf8_truncation_index(utf8, max_len);
buffer_write32be(buf, len);
memcpy(&buf[4], utf8, 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
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buf[0] = msg->type;
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
sc_write32be(&buf[2], msg->inject_keycode.keycode);
sc_write32be(&buf[6], msg->inject_keycode.repeat);
sc_write32be(&buf[10], msg->inject_keycode.metastate);
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len =
write_string(msg->inject_text.text,
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len;
}
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action;
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position);
uint16_t pressure =
sc_float_to_u16fp(msg->inject_touch_event.pressure);
sc_write16be(&buf[22], pressure);
sc_write32be(&buf[24], msg->inject_touch_event.action_button);
sc_write32be(&buf[28], msg->inject_touch_event.buttons);
return 32;
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
to_fixed_point_16(msg->inject_touch_event.pressure);
buffer_write16be(&buf[22], pressure);
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position);
int16_t hscroll =
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
int16_t vscroll =
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
sc_write16be(&buf[13], (uint16_t) hscroll);
sc_write16be(&buf[15], (uint16_t) vscroll);
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
buffer_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll);
buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
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;
return 2;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy_key;
return 2;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
sc_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[10]);
return 10 + len;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[2]);
return 2 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
return 2;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;
default:
@ -159,102 +94,12 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
}
void
sc_control_msg_log(const struct sc_control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
control_msg_destroy(struct control_msg *msg) {
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate);
break;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text);
break;
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
const char *pointer_name = get_well_known_pointer_id_name(id);
if (pointer_name) {
// string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%f action_button=%06lx buttons=%06lx",
pointer_name,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.action_button,
(long) msg->inject_touch_event.buttons);
} else {
// numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%f action_button=%06lx"
" buttons=%06lx",
id,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.action_button,
(long) msg->inject_touch_event.buttons);
}
break;
}
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
" vscroll=%f buttons=%06lx",
msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll,
(long) msg->inject_scroll_event.buttons);
break;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break;
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s",
copy_key_labels[msg->get_clipboard.copy_key]);
break;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text);
break;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel");
break;
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel");
break;
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;
}
}
void
sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text);
break;
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text);
break;
default:

View File

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

View File

@ -5,11 +5,10 @@
#include "util/log.h"
bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
bool ok = receiver_init(&controller->receiver, control_socket);
if (!ok) {
return false;
}
@ -34,25 +33,21 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
}
void
sc_controller_destroy(struct sc_controller *controller) {
controller_destroy(struct controller *controller) {
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct sc_control_msg msg;
struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
sc_control_msg_destroy(&msg);
control_msg_destroy(&msg);
}
receiver_destroy(&controller->receiver);
}
bool
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_control_msg_log(msg);
}
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg);
@ -64,21 +59,20 @@ sc_controller_push_msg(struct sc_controller *controller,
}
static bool
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
process_msg(struct controller *controller,
const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
ssize_t w =
net_send_all(controller->control_socket, serialized_msg, length);
int w = net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length;
}
static int
run_controller(void *data) {
struct sc_controller *controller = data;
struct controller *controller = data;
for (;;) {
sc_mutex_lock(&controller->mutex);
@ -90,14 +84,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex);
break;
}
struct sc_control_msg msg;
struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
sc_control_msg_destroy(&msg);
control_msg_destroy(&msg);
if (!ok) {
LOGD("Could not write msg to socket");
break;
@ -107,18 +101,18 @@ run_controller(void *data) {
}
bool
sc_controller_start(struct sc_controller *controller) {
controller_start(struct controller *controller) {
LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller,
"scrcpy-ctl", controller);
"controller", controller);
if (!ok) {
LOGE("Could not start controller thread");
LOGC("Could not start controller thread");
return false;
}
if (!receiver_start(&controller->receiver)) {
sc_controller_stop(controller);
controller_stop(controller);
sc_thread_join(&controller->thread, NULL);
return false;
}
@ -127,7 +121,7 @@ sc_controller_start(struct sc_controller *controller) {
}
void
sc_controller_stop(struct sc_controller *controller) {
controller_stop(struct controller *controller) {
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
sc_cond_signal(&controller->msg_cond);
@ -135,7 +129,7 @@ sc_controller_stop(struct sc_controller *controller) {
}
void
sc_controller_join(struct sc_controller *controller) {
controller_join(struct controller *controller) {
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

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

View File

@ -3,22 +3,22 @@
#include <stdint.h>
struct sc_size {
struct size {
uint16_t width;
uint16_t height;
};
struct sc_point {
struct point {
int32_t x;
int32_t y;
};
struct sc_position {
struct position {
// The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it
// accordingly.
struct sc_size screen_size;
struct sc_point point;
struct size screen_size;
struct point point;
};
#endif

View File

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

View File

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

View File

@ -1,309 +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 enum AVCodecID
sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) {
uint8_t data[4];
ssize_t r = net_recv_all(demuxer->socket, data, 4);
if (r < 4) {
return false;
}
#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
uint32_t codec_id = sc_read32be(data);
switch (codec_id) {
case SC_CODEC_ID_H264:
return AV_CODEC_ID_H264;
case SC_CODEC_ID_H265:
return AV_CODEC_ID_HEVC;
case SC_CODEC_ID_AV1:
return AV_CODEC_ID_AV1;
default:
LOGE("Unknown codec id 0x%08" PRIx32, codec_id);
return AV_CODEC_ID_NONE;
}
}
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) {
if (demuxer->pending) {
size_t offset = demuxer->pending->size;
if (av_grow_packet(demuxer->pending, packet->size)) {
LOG_OOM();
return false;
}
memcpy(demuxer->pending->data + offset, packet->data, packet->size);
} else {
demuxer->pending = av_packet_alloc();
if (!demuxer->pending) {
LOG_OOM();
return false;
}
if (av_packet_ref(demuxer->pending, packet)) {
LOG_OOM();
av_packet_free(&demuxer->pending);
return false;
}
}
if (!is_config) {
// prepare the concat packet to send to the decoder
demuxer->pending->pts = packet->pts;
demuxer->pending->dts = packet->dts;
demuxer->pending->flags = packet->flags;
packet = demuxer->pending;
}
}
bool ok = push_packet_to_sinks(demuxer, packet);
if (!is_config && demuxer->pending) {
// the pending packet must be discarded (consumed or error)
av_packet_free(&demuxer->pending);
}
if (!ok) {
LOGE("Could not process packet");
return false;
}
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;
enum AVCodecID codec_id = sc_demuxer_recv_codec_id(demuxer);
if (codec_id == AV_CODEC_ID_NONE) {
// Error already logged
goto end;
}
const AVCodec *codec = avcodec_find_decoder(codec_id);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
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(codec_id);
if (!demuxer->parser) {
LOGE("Could not initialize parser");
goto finally_close_sinks;
}
// We must only pass complete frames to av_parser_parse2()!
// It's more complicated, but this allows to reduce the latency by 1 frame!
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
goto finally_close_parser;
}
for (;;) {
bool ok = sc_demuxer_recv_packet(demuxer, packet);
if (!ok) {
// end of stream
break;
}
ok = sc_demuxer_push_packet(demuxer, packet);
av_packet_unref(packet);
if (!ok) {
// cannot process packet (error already logged)
break;
}
}
LOGD("End of frames");
if (demuxer->pending) {
av_packet_free(&demuxer->pending);
}
av_packet_free(&packet);
finally_close_parser:
av_parser_close(demuxer->parser);
finally_close_sinks:
sc_demuxer_close_sinks(demuxer);
finally_free_codec_ctx:
avcodec_free_context(&demuxer->codec_ctx);
end:
demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata);
return 0;
}
void
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
demuxer->socket = socket;
demuxer->pending = NULL;
demuxer->sink_count = 0;
assert(cbs && cbs->on_eos);
demuxer->cbs = cbs;
demuxer->cbs_userdata = cbs_userdata;
}
void
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS);
assert(sink);
assert(sink->ops);
demuxer->sinks[demuxer->sink_count++] = sink;
}
bool
sc_demuxer_start(struct sc_demuxer *demuxer) {
LOGD("Starting demuxer thread");
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
demuxer);
if (!ok) {
LOGE("Could not start demuxer thread");
return false;
}
return true;
}
void
sc_demuxer_join(struct sc_demuxer *demuxer) {
sc_thread_join(&demuxer->thread, NULL);
}

View File

@ -1,51 +0,0 @@
#ifndef SC_DEMUXER_H
#define SC_DEMUXER_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"
#include "util/net.h"
#include "util/thread.h"
#define SC_DEMUXER_MAX_SINKS 2
struct sc_demuxer {
sc_socket socket;
sc_thread thread;
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
AVPacket *pending;
const struct sc_demuxer_callbacks *cbs;
void *cbs_userdata;
};
struct sc_demuxer_callbacks {
void (*on_eos)(struct sc_demuxer *demuxer, void *userdata);
};
void
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
void
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink);
bool
sc_demuxer_start(struct sc_demuxer *demuxer);
void
sc_demuxer_join(struct sc_demuxer *demuxer);
#endif

23
app/src/device.c Normal file
View File

@ -0,0 +1,23 @@
#include "device.h"
#include "util/log.h"
bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information");
return false;
}
// in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
// strcpy is safe here, since name contains at least
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}

17
app/src/device.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef DEVICE_H
#define DEVICE_H
#include "common.h"
#include <stdbool.h>
#include "coords.h"
#include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
bool
device_read_info(socket_t device_socket, char *device_name, struct size *size);
#endif

View File

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

View File

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

195
app/src/event_converter.c Normal file
View File

@ -0,0 +1,195 @@
#include "event_converter.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
FAIL;
}
}
static enum android_metastate
autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
enum android_metastate
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead
switch(from) {
MAP(SDLK_KP_0, AKEYCODE_INSERT);
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
}
}
if (prefer_text && !(mod & KMOD_CTRL)) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false;
}
// if ALT and META are not pressed, also handle letters and space
switch (from) {
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
}
}
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}

30
app/src/event_converter.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef CONVERT_H
#define CONVERT_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "control_msg.h"
bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
enum android_metastate
convert_meta_state(SDL_Keymod mod);
bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text);
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state);
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
#endif

View File

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

197
app/src/file_handler.c Normal file
View 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/"
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
View 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

View File

@ -1,178 +0,0 @@
#include "file_pusher.h"
#include <assert.h>
#include <string.h>
#include "adb/adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) {
free(req->file);
}
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&fp->queue);
bool ok = sc_mutex_init(&fp->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&fp->event_cond);
if (!ok) {
sc_mutex_destroy(&fp->mutex);
return false;
}
ok = sc_intr_init(&fp->intr);
if (!ok) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
fp->serial = strdup(serial);
if (!fp->serial) {
LOG_OOM();
sc_intr_destroy(&fp->intr);
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
// lazy initialization
fp->initialized = false;
fp->stopped = false;
fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
return true;
}
void
sc_file_pusher_destroy(struct sc_file_pusher *fp) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
sc_intr_destroy(&fp->intr);
free(fp->serial);
struct sc_file_pusher_request req;
while (cbuf_take(&fp->queue, &req)) {
sc_file_pusher_request_destroy(&req);
}
}
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file) {
// start file_pusher if it's used for the first time
if (!fp->initialized) {
if (!sc_file_pusher_start(fp)) {
return false;
}
fp->initialized = true;
}
LOGI("Request to %s %s", action == SC_FILE_PUSHER_ACTION_INSTALL_APK
? "install" : "push",
file);
struct sc_file_pusher_request req = {
.action = action,
.file = file,
};
sc_mutex_lock(&fp->mutex);
bool was_empty = cbuf_is_empty(&fp->queue);
bool res = cbuf_push(&fp->queue, req);
if (was_empty) {
sc_cond_signal(&fp->event_cond);
}
sc_mutex_unlock(&fp->mutex);
return res;
}
static int
run_file_pusher(void *data) {
struct sc_file_pusher *fp = data;
struct sc_intr *intr = &fp->intr;
const char *serial = fp->serial;
assert(serial);
const char *push_target = fp->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&fp->mutex);
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
sc_cond_wait(&fp->event_cond, &fp->mutex);
}
if (fp->stopped) {
// stop immediately, do not process further events
sc_mutex_unlock(&fp->mutex);
break;
}
struct sc_file_pusher_request req;
bool non_empty = cbuf_take(&fp->queue, &req);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&fp->mutex);
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = sc_adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = sc_adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
sc_file_pusher_request_destroy(&req);
}
return 0;
}
bool
sc_file_pusher_start(struct sc_file_pusher *fp) {
LOGD("Starting file_pusher thread");
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
if (!ok) {
LOGE("Could not start file_pusher thread");
return false;
}
return true;
}
void
sc_file_pusher_stop(struct sc_file_pusher *fp) {
sc_mutex_lock(&fp->mutex);
fp->stopped = true;
sc_cond_signal(&fp->event_cond);
sc_intr_interrupt(&fp->intr);
sc_mutex_unlock(&fp->mutex);
}
void
sc_file_pusher_join(struct sc_file_pusher *fp) {
sc_thread_join(&fp->thread, NULL);
}

View File

@ -1,58 +0,0 @@
#ifndef SC_FILE_PUSHER_H
#define SC_FILE_PUSHER_H
#include "common.h"
#include <stdbool.h>
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
enum sc_file_pusher_action {
SC_FILE_PUSHER_ACTION_INSTALL_APK,
SC_FILE_PUSHER_ACTION_PUSH_FILE,
};
struct sc_file_pusher_request {
enum sc_file_pusher_action action;
char *file;
};
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
struct sc_file_pusher {
char *serial;
const char *push_target;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
struct sc_file_pusher_request_queue queue;
struct sc_intr intr;
};
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target);
void
sc_file_pusher_destroy(struct sc_file_pusher *fp);
bool
sc_file_pusher_start(struct sc_file_pusher *fp);
void
sc_file_pusher_stop(struct sc_file_pusher *fp);
void
sc_file_pusher_join(struct sc_file_pusher *fp);
// take ownership of file, and will free() it
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file);
#endif

View File

@ -1,13 +1,14 @@
#include "fps_counter.h"
#include <assert.h>
#include <SDL2/SDL_timer.h>
#include "util/log.h"
#define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
#define FPS_COUNTER_INTERVAL_MS 1000
bool
sc_fps_counter_init(struct sc_fps_counter *counter) {
fps_counter_init(struct fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex);
if (!ok) {
return false;
@ -27,26 +28,26 @@ sc_fps_counter_init(struct sc_fps_counter *counter) {
}
void
sc_fps_counter_destroy(struct sc_fps_counter *counter) {
fps_counter_destroy(struct fps_counter *counter) {
sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex);
}
static inline bool
is_started(struct sc_fps_counter *counter) {
is_started(struct fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire);
}
static inline void
set_started(struct sc_fps_counter *counter, bool started) {
set_started(struct fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release);
}
// must be called with mutex locked
static void
display_fps(struct sc_fps_counter *counter) {
display_fps(struct fps_counter *counter) {
unsigned rendered_per_second =
counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL;
counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped);
@ -57,7 +58,7 @@ display_fps(struct sc_fps_counter *counter) {
// must be called with mutex locked
static void
check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
check_interval_expired(struct fps_counter *counter, uint32_t now) {
if (now < counter->next_timestamp) {
return;
}
@ -67,13 +68,13 @@ check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
counter->nr_skipped = 0;
// add a multiple of the interval
uint32_t elapsed_slices =
(now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices;
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
}
static int
run_fps_counter(void *data) {
struct sc_fps_counter *counter = data;
struct fps_counter *counter = data;
sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) {
@ -81,12 +82,14 @@ run_fps_counter(void *data) {
sc_cond_wait(&counter->state_cond, &counter->mutex);
}
while (!counter->interrupted && is_started(counter)) {
sc_tick now = sc_tick_now();
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway
sc_cond_timedwait(&counter->state_cond, &counter->mutex,
counter->next_timestamp);
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
}
}
sc_mutex_unlock(&counter->mutex);
@ -94,9 +97,9 @@ run_fps_counter(void *data) {
}
bool
sc_fps_counter_start(struct sc_fps_counter *counter) {
fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL;
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex);
@ -108,7 +111,7 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
// same thread, no need to lock
if (!counter->thread_started) {
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
"scrcpy-fps", counter);
"fps counter", counter);
if (!ok) {
LOGE("Could not start FPS counter thread");
return false;
@ -117,24 +120,22 @@ sc_fps_counter_start(struct sc_fps_counter *counter) {
counter->thread_started = true;
}
LOGI("FPS counter started");
return true;
}
void
sc_fps_counter_stop(struct sc_fps_counter *counter) {
fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false);
sc_cond_signal(&counter->state_cond);
LOGI("FPS counter stopped");
}
bool
sc_fps_counter_is_started(struct sc_fps_counter *counter) {
fps_counter_is_started(struct fps_counter *counter) {
return is_started(counter);
}
void
sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread_started) {
return;
}
@ -147,37 +148,33 @@ sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
}
void
sc_fps_counter_join(struct sc_fps_counter *counter) {
fps_counter_join(struct fps_counter *counter) {
if (counter->thread_started) {
// interrupted must be set by the thread calling join(), so no need to
// lock for the assertion
assert(counter->interrupted);
sc_thread_join(&counter->thread, NULL);
}
}
void
sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
fps_counter_add_rendered_frame(struct fps_counter *counter) {
if (!is_started(counter)) {
return;
}
sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now();
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_rendered;
sc_mutex_unlock(&counter->mutex);
}
void
sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) {
fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!is_started(counter)) {
return;
}
sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now();
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex);

View File

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

View File

@ -1,90 +0,0 @@
#include "frame_buffer.h"
#include <assert.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/log.h"
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
fb->pending_frame = av_frame_alloc();
if (!fb->pending_frame) {
LOG_OOM();
return false;
}
fb->tmp_frame = av_frame_alloc();
if (!fb->tmp_frame) {
LOG_OOM();
av_frame_free(&fb->pending_frame);
return false;
}
bool ok = sc_mutex_init(&fb->mutex);
if (!ok) {
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
return false;
}
// there is initially no frame, so consider it has already been consumed
fb->pending_frame_consumed = true;
return true;
}
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb) {
sc_mutex_destroy(&fb->mutex);
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
}
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) {
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
sc_mutex_lock(&fb->mutex);
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame);
av_frame_unref(fb->tmp_frame);
if (previous_frame_skipped) {
*previous_frame_skipped = !fb->pending_frame_consumed;
}
fb->pending_frame_consumed = false;
sc_mutex_unlock(&fb->mutex);
return true;
}
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) {
sc_mutex_lock(&fb->mutex);
assert(!fb->pending_frame_consumed);
fb->pending_frame_consumed = true;
av_frame_move_ref(dst, fb->pending_frame);
// av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
sc_mutex_unlock(&fb->mutex);
}

View File

@ -1,44 +0,0 @@
#ifndef SC_FRAME_BUFFER_H
#define SC_FRAME_BUFFER_H
#include "common.h"
#include <stdbool.h>
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* A frame buffer holds 1 pending frame, which is the last frame received from
* the producer (typically, the decoder).
*
* If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*/
struct sc_frame_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
sc_mutex mutex;
bool pending_frame_consumed;
};
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb);
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb);
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *skipped);
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst);
#endif

View File

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

View File

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

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

View File

@ -1,454 +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;
uint64_t pointer_id;
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;
uint64_t pointer_id;
int32_t xrel;
int32_t yrel;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_touch_event {
struct sc_position position;
enum sc_touch_action action;
uint64_t pointer_id;
float pressure;
};
static inline uint16_t
sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
assert(buttons_state < 0x100); // fits in uint8_t
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) {
mask |= SC_MOUSE_BUTTON_RIGHT
| SC_MOUSE_BUTTON_MIDDLE
| SC_MOUSE_BUTTON_X1
| SC_MOUSE_BUTTON_X2;
}
return buttons_state & mask;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#ifndef SC_INPUTMANAGER_H
#define SC_INPUTMANAGER_H
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#include "common.h"
@ -8,23 +8,24 @@
#include <SDL2/SDL.h>
#include "controller.h"
#include "file_pusher.h"
#include "fps_counter.h"
#include "options.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
#include "scrcpy.h"
#include "screen.h"
struct sc_input_manager {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct input_manager {
struct controller *controller;
struct fps_counter *fps_counter;
struct screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool control;
bool forward_key_repeat;
bool prefer_text;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
@ -39,28 +40,13 @@ struct sc_input_manager {
unsigned key_repeat;
SDL_Keycode last_keycode;
uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
};
struct sc_input_manager_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
};
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
input_manager_init(struct input_manager *im,
const struct scrcpy_options *options);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
#endif

View File

@ -1,344 +0,0 @@
#include "keyboard_inject.h"
#include <assert.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
static enum android_keyevent_action
convert_keycode_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AKEY_EVENT_ACTION_DOWN;
}
assert(action == SC_ACTION_UP);
return AKEY_EVENT_ACTION_UP;
}
static bool
convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
enum sc_key_inject_mode key_inject_mode) {
// Navigation keys and ENTER.
// Used in all modes.
static const struct sc_intmap_entry special_keys[] = {
{SC_KEYCODE_RETURN, AKEYCODE_ENTER},
{SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
{SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE},
{SC_KEYCODE_BACKSPACE, AKEYCODE_DEL},
{SC_KEYCODE_TAB, AKEYCODE_TAB},
{SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP},
{SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL},
{SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME},
{SC_KEYCODE_END, AKEYCODE_MOVE_END},
{SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN},
{SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT},
{SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT},
{SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN},
{SC_KEYCODE_UP, AKEYCODE_DPAD_UP},
{SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT},
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
{SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
{SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
};
// Numpad navigation keys.
// Used in all modes, when NumLock and Shift are disabled.
static const struct sc_intmap_entry kp_nav_keys[] = {
{SC_KEYCODE_KP_0, AKEYCODE_INSERT},
{SC_KEYCODE_KP_1, AKEYCODE_MOVE_END},
{SC_KEYCODE_KP_2, AKEYCODE_DPAD_DOWN},
{SC_KEYCODE_KP_3, AKEYCODE_PAGE_DOWN},
{SC_KEYCODE_KP_4, AKEYCODE_DPAD_LEFT},
{SC_KEYCODE_KP_6, AKEYCODE_DPAD_RIGHT},
{SC_KEYCODE_KP_7, AKEYCODE_MOVE_HOME},
{SC_KEYCODE_KP_8, AKEYCODE_DPAD_UP},
{SC_KEYCODE_KP_9, AKEYCODE_PAGE_UP},
{SC_KEYCODE_KP_PERIOD, AKEYCODE_FORWARD_DEL},
};
// Letters and space.
// Used in non-text mode.
static const struct sc_intmap_entry alphaspace_keys[] = {
{SC_KEYCODE_a, AKEYCODE_A},
{SC_KEYCODE_b, AKEYCODE_B},
{SC_KEYCODE_c, AKEYCODE_C},
{SC_KEYCODE_d, AKEYCODE_D},
{SC_KEYCODE_e, AKEYCODE_E},
{SC_KEYCODE_f, AKEYCODE_F},
{SC_KEYCODE_g, AKEYCODE_G},
{SC_KEYCODE_h, AKEYCODE_H},
{SC_KEYCODE_i, AKEYCODE_I},
{SC_KEYCODE_j, AKEYCODE_J},
{SC_KEYCODE_k, AKEYCODE_K},
{SC_KEYCODE_l, AKEYCODE_L},
{SC_KEYCODE_m, AKEYCODE_M},
{SC_KEYCODE_n, AKEYCODE_N},
{SC_KEYCODE_o, AKEYCODE_O},
{SC_KEYCODE_p, AKEYCODE_P},
{SC_KEYCODE_q, AKEYCODE_Q},
{SC_KEYCODE_r, AKEYCODE_R},
{SC_KEYCODE_s, AKEYCODE_S},
{SC_KEYCODE_t, AKEYCODE_T},
{SC_KEYCODE_u, AKEYCODE_U},
{SC_KEYCODE_v, AKEYCODE_V},
{SC_KEYCODE_w, AKEYCODE_W},
{SC_KEYCODE_x, AKEYCODE_X},
{SC_KEYCODE_y, AKEYCODE_Y},
{SC_KEYCODE_z, AKEYCODE_Z},
{SC_KEYCODE_SPACE, AKEYCODE_SPACE},
};
// Numbers and punctuation keys.
// Used in raw mode only.
static const struct sc_intmap_entry numbers_punct_keys[] = {
{SC_KEYCODE_HASH, AKEYCODE_POUND},
{SC_KEYCODE_PERCENT, AKEYCODE_PERIOD},
{SC_KEYCODE_QUOTE, AKEYCODE_APOSTROPHE},
{SC_KEYCODE_ASTERISK, AKEYCODE_STAR},
{SC_KEYCODE_PLUS, AKEYCODE_PLUS},
{SC_KEYCODE_COMMA, AKEYCODE_COMMA},
{SC_KEYCODE_MINUS, AKEYCODE_MINUS},
{SC_KEYCODE_PERIOD, AKEYCODE_PERIOD},
{SC_KEYCODE_SLASH, AKEYCODE_SLASH},
{SC_KEYCODE_0, AKEYCODE_0},
{SC_KEYCODE_1, AKEYCODE_1},
{SC_KEYCODE_2, AKEYCODE_2},
{SC_KEYCODE_3, AKEYCODE_3},
{SC_KEYCODE_4, AKEYCODE_4},
{SC_KEYCODE_5, AKEYCODE_5},
{SC_KEYCODE_6, AKEYCODE_6},
{SC_KEYCODE_7, AKEYCODE_7},
{SC_KEYCODE_8, AKEYCODE_8},
{SC_KEYCODE_9, AKEYCODE_9},
{SC_KEYCODE_SEMICOLON, AKEYCODE_SEMICOLON},
{SC_KEYCODE_EQUALS, AKEYCODE_EQUALS},
{SC_KEYCODE_AT, AKEYCODE_AT},
{SC_KEYCODE_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
{SC_KEYCODE_BACKSLASH, AKEYCODE_BACKSLASH},
{SC_KEYCODE_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
{SC_KEYCODE_BACKQUOTE, AKEYCODE_GRAVE},
{SC_KEYCODE_KP_1, AKEYCODE_NUMPAD_1},
{SC_KEYCODE_KP_2, AKEYCODE_NUMPAD_2},
{SC_KEYCODE_KP_3, AKEYCODE_NUMPAD_3},
{SC_KEYCODE_KP_4, AKEYCODE_NUMPAD_4},
{SC_KEYCODE_KP_5, AKEYCODE_NUMPAD_5},
{SC_KEYCODE_KP_6, AKEYCODE_NUMPAD_6},
{SC_KEYCODE_KP_7, AKEYCODE_NUMPAD_7},
{SC_KEYCODE_KP_8, AKEYCODE_NUMPAD_8},
{SC_KEYCODE_KP_9, AKEYCODE_NUMPAD_9},
{SC_KEYCODE_KP_0, AKEYCODE_NUMPAD_0},
{SC_KEYCODE_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
{SC_KEYCODE_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
{SC_KEYCODE_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
{SC_KEYCODE_KP_PLUS, AKEYCODE_NUMPAD_ADD},
{SC_KEYCODE_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
{SC_KEYCODE_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
{SC_KEYCODE_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
{SC_KEYCODE_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
};
const struct sc_intmap_entry *entry =
SC_INTMAP_FIND_ENTRY(special_keys, from);
if (entry) {
*to = entry->value;
return true;
}
if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) {
// Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
if (entry) {
*to = entry->value;
return true;
}
}
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT &&
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}
if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) {
return false;
}
// if ALT and META are not pressed, also handle letters and space
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
if (entry) {
*to = entry->value;
return true;
}
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
autocomplete_metastate(enum android_metastate metastate) {
// fill dependent flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
static enum android_metastate
convert_meta_state(uint16_t mod) {
enum android_metastate metastate = 0;
if (mod & SC_MOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & SC_MOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & SC_MOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & SC_MOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & SC_MOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & SC_MOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & SC_MOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & SC_MOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & SC_MOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & SC_MOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
static bool
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
return false;
}
msg->inject_keycode.action = convert_keycode_action(event->action);
msg->inject_keycode.repeat = repeat;
msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
// The device clipboard synchronization and the key event messages are
// serialized, there is nothing special to do to ensure that the clipboard
// is set before injecting Ctrl+v.
(void) ack_to_wait;
struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (event->repeat) {
if (!ki->forward_key_repeat) {
return;
}
++ki->repeat;
} else {
ki->repeat = 0;
}
struct sc_control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
}
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
// Never inject text events
return;
}
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
// Letters and space are handled as raw key events
return;
}
}
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!sc_controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
ki->controller = controller;
ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = forward_key_repeat;
ki->repeat = 0;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
.process_text = sc_key_processor_process_text,
};
// Key injection and clipboard synchronization are serialized
ki->key_processor.async_paste = false;
ki->key_processor.ops = &ops;
}

View File

@ -1,31 +0,0 @@
#ifndef SC_KEYBOARD_INJECT_H
#define SC_KEYBOARD_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait
struct sc_controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
enum sc_key_inject_mode key_inject_mode;
bool forward_key_repeat;
};
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
#endif

View File

@ -1,13 +1,11 @@
#include "scrcpy.h"
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
@ -15,26 +13,60 @@
#include <SDL2/SDL.h>
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"
#include "version.h"
static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
SDL_PATCHLEVEL);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
#ifdef HAVE_V4L2
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
}
static SDL_LogPriority
convert_log_level_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
int
main_scrcpy(int argc, char *argv[]) {
#ifdef _WIN32
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL);
setbuf(stderr, NULL);
#endif
printf("scrcpy " SCRCPY_VERSION
" <https://github.com/Genymobile/scrcpy>\n");
struct scrcpy_cli_args args = {
.opts = scrcpy_options_default,
.opts = SCRCPY_OPTIONS_DEFAULT,
.help = false,
.version = false,
};
@ -44,21 +76,24 @@ main_scrcpy(int argc, char *argv[]) {
#endif
if (!scrcpy_parse_args(&args, argc, argv)) {
return SCRCPY_EXIT_FAILURE;
return 1;
}
sc_set_log_level(args.opts.log_level);
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);
return SCRCPY_EXIT_SUCCESS;
return 0;
}
if (args.version) {
scrcpy_print_version();
return SCRCPY_EXIT_SUCCESS;
print_version();
return 0;
}
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all();
#endif
@ -70,66 +105,12 @@ main_scrcpy(int argc, char *argv[]) {
#endif
if (avformat_network_init()) {
return SCRCPY_EXIT_FAILURE;
return 1;
}
#ifdef HAVE_USB
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
#else
enum scrcpy_exit_code ret = scrcpy(&args.opts);
#endif
int res = scrcpy(&args.opts) ? 0 : 1;
avformat_network_deinit(); // ignore failure
return ret;
}
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
return res;
}

View File

@ -1,162 +0,0 @@
#include "mouse_inject.h"
#include <assert.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SC_MOUSE_BUTTON_LEFT) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SC_MOUSE_BUTTON_RIGHT) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SC_MOUSE_BUTTON_MIDDLE) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SC_MOUSE_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SC_MOUSE_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
static enum android_motionevent_action
convert_mouse_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AMOTION_EVENT_ACTION_DOWN;
}
assert(action == SC_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
static enum android_motionevent_action
convert_touch_action(enum sc_touch_action action) {
switch (action) {
case SC_TOUCH_ACTION_MOVE:
return AMOTION_EVENT_ACTION_MOVE;
case SC_TOUCH_ACTION_DOWN:
return AMOTION_EVENT_ACTION_DOWN;
default:
assert(action == SC_TOUCH_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
if (!event->buttons_state) {
// Do not send motion events when no click is pressed
return;
}
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = event->pointer_id,
.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'");
}
}
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 = event->pointer_id,
.position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.action_button = convert_mouse_buttons(event->button),
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
.vscroll = event->vscroll,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_touch_action(event->action),
.pointer_id = event->pointer_id,
.position = event->position,
.pressure = event->pressure,
.buttons = 0,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
mi->controller = controller;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
.process_touch = sc_mouse_processor_process_touch,
};
mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
}

View File

@ -1,22 +0,0 @@
#ifndef SC_MOUSE_INJECT_H
#define SC_MOUSE_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "screen.h"
#include "trait/mouse_processor.h"
struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller);
#endif

View File

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

View File

@ -1,69 +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,
.codec = SC_CODEC_H264,
.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,
};

View File

@ -1,154 +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_codec {
SC_CODEC_H264,
SC_CODEC_H265,
SC_CODEC_AV1,
};
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_codec codec;
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

View File

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

View File

@ -1,27 +1,23 @@
#ifndef SC_RECEIVER_H
#define SC_RECEIVER_H
#ifndef RECEIVER_H
#define RECEIVER_H
#include "common.h"
#include <stdbool.h>
#include "util/acksync.h"
#include "util/net.h"
#include "util/thread.h"
// receive events from the device
// managed by the controller
struct receiver {
sc_socket control_socket;
socket_t control_socket;
sc_thread thread;
sc_mutex mutex;
struct sc_acksync *acksync;
};
bool
receiver_init(struct receiver *receiver, sc_socket control_socket,
struct sc_acksync *acksync);
receiver_init(struct receiver *receiver, socket_t control_socket);
void
receiver_destroy(struct receiver *receiver);

View File

@ -1,17 +1,13 @@
#include "recorder.h"
#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include "util/log.h"
#include "util/str.h"
#include "util/str_util.h"
/** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink)
#define SC_PTS_ORIGIN_NONE UINT64_C(-1)
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -28,27 +24,22 @@ find_muxer(const char *name) {
oformat = av_oformat_next(oformat);
#endif
// 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;
}
static struct sc_record_packet *
sc_record_packet_new(const AVPacket *packet) {
struct sc_record_packet *rec = malloc(sizeof(*rec));
static struct record_packet *
record_packet_new(const AVPacket *packet) {
struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) {
LOG_OOM();
return NULL;
}
rec->packet = av_packet_alloc();
if (!rec->packet) {
LOG_OOM();
free(rec);
return NULL;
}
// av_packet_ref() does not initialize all fields in old FFmpeg versions
// See <https://github.com/Genymobile/scrcpy/issues/707>
av_init_packet(&rec->packet);
if (av_packet_ref(rec->packet, packet)) {
av_packet_free(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) {
free(rec);
return NULL;
}
@ -56,22 +47,22 @@ sc_record_packet_new(const AVPacket *packet) {
}
static void
sc_record_packet_delete(struct sc_record_packet *rec) {
av_packet_free(&rec->packet);
record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet);
free(rec);
}
static void
sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
while (!sc_queue_is_empty(queue)) {
struct sc_record_packet *rec;
sc_queue_take(queue, next, &rec);
sc_record_packet_delete(rec);
recorder_queue_clear(struct recorder_queue *queue) {
while (!queue_is_empty(queue)) {
struct record_packet *rec;
queue_take(queue, next, &rec);
record_packet_delete(rec);
}
}
static const char *
sc_recorder_get_format_name(enum sc_record_format format) {
recorder_get_format_name(enum sc_record_format format) {
switch (format) {
case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska";
@ -80,12 +71,12 @@ sc_recorder_get_format_name(enum sc_record_format format) {
}
static bool
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOG_OOM();
LOGC("Could not allocate extradata");
return false;
}
@ -105,19 +96,19 @@ sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
}
static void
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) {
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
static bool
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) {
LOGE("The first packet is not a config packet");
return false;
}
bool ok = sc_recorder_write_header(recorder, packet);
bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return false;
}
@ -130,61 +121,49 @@ sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
return true;
}
sc_recorder_rescale_packet(recorder, packet);
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0;
}
static int
run_recorder(void *data) {
struct sc_recorder *recorder = data;
struct recorder *recorder = data;
for (;;) {
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex);
struct sc_record_packet *last = recorder->previous;
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
last->packet->duration = 100000;
bool ok = sc_recorder_write(recorder, last->packet);
last->packet.duration = 100000;
bool ok = recorder_write(recorder, &last->packet);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
// will still be valid
LOGW("Could not record last packet");
}
sc_record_packet_delete(last);
record_packet_delete(last);
}
break;
}
struct sc_record_packet *rec;
sc_queue_take(&recorder->queue, next, &rec);
struct record_packet *rec;
queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex);
if (recorder->pts_origin == SC_PTS_ORIGIN_NONE
&& rec->packet->pts != AV_NOPTS_VALUE) {
// First PTS received
recorder->pts_origin = rec->packet->pts;
}
if (rec->packet->pts != AV_NOPTS_VALUE) {
// Set PTS relatve to the origin
rec->packet->pts -= recorder->pts_origin;
rec->packet->dts = rec->packet->pts;
}
// recorder->previous is only written from this thread, no need to lock
struct sc_record_packet *previous = recorder->previous;
struct record_packet *previous = recorder->previous;
recorder->previous = rec;
if (!previous) {
@ -193,22 +172,21 @@ run_recorder(void *data) {
}
// config packets have no PTS, we must ignore them
if (rec->packet->pts != AV_NOPTS_VALUE
&& previous->packet->pts != AV_NOPTS_VALUE) {
if (rec->packet.pts != AV_NOPTS_VALUE
&& previous->packet.pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet
previous->packet->duration =
rec->packet->pts - previous->packet->pts;
previous->packet.duration = rec->packet.pts - previous->packet.pts;
}
bool ok = sc_recorder_write(recorder, previous->packet);
sc_record_packet_delete(previous);
bool ok = recorder_write(recorder, &previous->packet);
record_packet_delete(previous);
if (!ok) {
LOGE("Could not record packet");
sc_mutex_lock(&recorder->mutex);
recorder->failed = true;
// discard pending packets
sc_recorder_queue_clear(&recorder->queue);
recorder_queue_clear(&recorder->queue);
sc_mutex_unlock(&recorder->mutex);
break;
}
@ -230,9 +208,8 @@ run_recorder(void *data) {
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = sc_recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name,
recorder->filename);
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
LOGD("Recorder thread ended");
@ -241,25 +218,26 @@ run_recorder(void *data) {
}
static bool
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
return false;
}
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
goto error_mutex_destroy;
}
sc_queue_init(&recorder->queue);
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->header_written = false;
recorder->previous = NULL;
recorder->pts_origin = SC_PTS_ORIGIN_NONE;
const char *format_name = sc_recorder_get_format_name(recorder->format);
const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
@ -269,7 +247,7 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOG_OOM();
LOGE("Could not allocate output context");
goto error_cond_destroy;
}
@ -302,10 +280,10 @@ sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
}
LOGD("Starting recorder thread");
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
recorder);
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder);
if (!ok) {
LOGE("Could not start recorder thread");
LOGC("Could not start recorder thread");
goto error_avio_close;
}
@ -326,7 +304,7 @@ error_mutex_destroy:
}
static void
sc_recorder_close(struct sc_recorder *recorder) {
recorder_close(struct recorder *recorder) {
sc_mutex_lock(&recorder->mutex);
recorder->stopped = true;
sc_cond_signal(&recorder->queue_cond);
@ -341,7 +319,7 @@ sc_recorder_close(struct sc_recorder *recorder) {
}
static bool
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped);
@ -351,14 +329,14 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
return false;
}
struct sc_record_packet *rec = sc_record_packet_new(packet);
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOG_OOM();
LOGC("Could not allocate record packet");
sc_mutex_unlock(&recorder->mutex);
return false;
}
sc_queue_push(&recorder->queue, next, rec);
queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
@ -366,33 +344,31 @@ sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
}
static bool
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
const AVCodec *codec) {
struct sc_recorder *recorder = DOWNCAST(sink);
return sc_recorder_open(recorder, codec);
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_open(recorder, codec);
}
static void
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct sc_recorder *recorder = DOWNCAST(sink);
sc_recorder_close(recorder);
recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct recorder *recorder = DOWNCAST(sink);
recorder_close(recorder);
}
static bool
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
const AVPacket *packet) {
struct sc_recorder *recorder = DOWNCAST(sink);
return sc_recorder_push(recorder, packet);
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_push(recorder, packet);
}
bool
sc_recorder_init(struct sc_recorder *recorder,
const char *filename,
enum sc_record_format format,
struct sc_size declared_frame_size) {
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOG_OOM();
LOGE("Could not strdup filename");
return false;
}
@ -400,9 +376,9 @@ sc_recorder_init(struct sc_recorder *recorder,
recorder->declared_frame_size = declared_frame_size;
static const struct sc_packet_sink_ops ops = {
.open = sc_recorder_packet_sink_open,
.close = sc_recorder_packet_sink_close,
.push = sc_recorder_packet_sink_push,
.open = recorder_packet_sink_open,
.close = recorder_packet_sink_close,
.push = recorder_packet_sink_push,
};
recorder->packet_sink.ops = &ops;
@ -411,6 +387,6 @@ sc_recorder_init(struct sc_recorder *recorder,
}
void
sc_recorder_destroy(struct sc_recorder *recorder) {
recorder_destroy(struct recorder *recorder) {
free(recorder->filename);
}

View File

@ -1,5 +1,5 @@
#ifndef SC_RECORDER_H
#define SC_RECORDER_H
#ifndef RECORDER_H
#define RECORDER_H
#include "common.h"
@ -7,49 +7,46 @@
#include <libavformat/avformat.h>
#include "coords.h"
#include "options.h"
#include "scrcpy.h"
#include "trait/packet_sink.h"
#include "util/queue.h"
#include "util/thread.h"
struct sc_record_packet {
AVPacket *packet;
struct sc_record_packet *next;
struct record_packet {
AVPacket packet;
struct record_packet *next;
};
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
struct recorder_queue QUEUE(struct record_packet);
struct sc_recorder {
struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait
char *filename;
enum sc_record_format format;
AVFormatContext *ctx;
struct sc_size declared_frame_size;
struct size declared_frame_size;
bool header_written;
uint64_t pts_origin;
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
bool stopped; // set on recorder_close()
bool failed; // set on packet write failure
struct sc_recorder_queue queue;
struct recorder_queue queue;
// we can write a packet only once we received the next one so that we can
// set its duration (next_pts - current_pts)
// "previous" is only accessed from the recorder thread, so it does not
// need to be protected by the mutex
struct sc_record_packet *previous;
struct record_packet *previous;
};
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format,
struct sc_size declared_frame_size);
recorder_init(struct recorder *recorder, const char *filename,
enum sc_record_format format, struct size declared_frame_size);
void
sc_recorder_destroy(struct sc_recorder *recorder);
recorder_destroy(struct recorder *recorder);
#endif

View File

@ -15,83 +15,83 @@
#include "controller.h"
#include "decoder.h"
#include "demuxer.h"
#include "device.h"
#include "events.h"
#include "file_pusher.h"
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "file_handler.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#ifdef HAVE_USB
# include "usb/aoa_hid.h"
# include "usb/hid_keyboard.h"
# include "usb/hid_mouse.h"
# include "usb/usb.h"
#endif
#include "util/acksync.h"
#include "stream.h"
#include "tiny_xpm.h"
#include "util/log.h"
#include "util/net.h"
#include "util/rand.h"
#ifdef HAVE_V4L2
# include "v4l2_sink.h"
#endif
struct scrcpy {
struct sc_server server;
struct sc_screen screen;
struct sc_demuxer demuxer;
struct sc_decoder decoder;
struct sc_recorder recorder;
static struct server server;
static struct screen screen;
static struct fps_counter fps_counter;
static struct stream stream;
static struct decoder decoder;
static struct recorder recorder;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
static struct sc_v4l2_sink v4l2_sink;
#endif
struct sc_controller controller;
struct sc_file_pusher file_pusher;
#ifdef HAVE_USB
struct sc_usb usb;
struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif
union {
struct sc_keyboard_inject keyboard_inject;
#ifdef HAVE_USB
struct sc_hid_keyboard keyboard_hid;
#endif
};
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_USB
struct sc_hid_mouse mouse_hid;
#endif
};
};
static struct controller controller;
static struct file_handler file_handler;
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)
static struct input_manager input_manager = {
.controller = &controller,
.fps_counter = &fps_counter,
.screen = &screen,
.repeat = 0,
// initialized later
.prefer_text = false,
.sdl_shortcut_mods = {
.data = {0},
.count = 0,
},
};
#ifdef _WIN32
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) {
PUSH_EVENT(SDL_QUIT);
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
return TRUE;
}
return FALSE;
}
#endif // _WIN32
static void
sdl_set_hints(const char *render_driver) {
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
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)) {
LOGW("Could not set render driver");
@ -102,18 +102,11 @@ sdl_set_hints(const char *render_driver) {
LOGW("Could not enable linear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
// better not to generate them in the first place.
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
LOGW("Could not disable synthetic mouse events");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
@ -127,71 +120,88 @@ sdl_set_hints(const char *render_driver) {
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
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) {
LOGD("Screensaver disabled");
SDL_DisableScreenSaver();
} else {
LOGD("Screensaver enabled");
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
await_for_server(bool *connected) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
LOGD("User requested to quit");
*connected = false;
return true;
case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed");
return false;
case EVENT_SERVER_CONNECTED:
LOGD("Server connected");
*connected = true;
return true;
default:
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(SDL_Event *event, const struct scrcpy_options *options) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case SDL_DROPFILE: {
if (!options->control) {
break;
}
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action;
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, file);
goto end;
}
}
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(const struct scrcpy_options *options) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, options);
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;
}
@ -219,107 +229,40 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) {
return;
}
size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10);
char *local_fmt = malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOG_OOM();
LOGC("Could not allocate string");
return;
}
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
// strcpy is safe here, the destination is large enough
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
free(local_fmt);
}
static void
sc_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
(void) demuxer;
(void) userdata;
PUSH_EVENT(EVENT_STREAM_STOPPED);
}
static void
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
}
static uint32_t
scrcpy_generate_uid() {
struct sc_rand rand;
sc_rand_init(&rand);
// Only use 31 bits to avoid issues with signed values on the Java-side
return sc_rand_u32(&rand) & 0x7FFFFFFF;
}
enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError());
return SCRCPY_EXIT_FAILURE;
}
atexit(SDL_Quit);
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
bool
scrcpy(const struct scrcpy_options *options) {
bool ret = false;
bool server_started = false;
bool file_pusher_initialized = false;
bool fps_counter_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
bool demuxer_started = false;
#ifdef HAVE_USB
bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
#endif
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
bool screen_initialized = false;
struct sc_acksync *acksync = NULL;
uint32_t uid = scrcpy_generate_uid();
struct sc_server_params params = {
.uid = uid,
.req_serial = options->serial,
.select_usb = options->select_usb,
.select_tcpip = options->select_tcpip,
bool record = !!options->record_filename;
struct server_params params = {
.serial = options->serial,
.log_level = options->log_level,
.codec = options->codec,
.crop = options->crop,
.port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size,
.bit_rate = options->bit_rate,
.max_fps = options->max_fps,
@ -332,277 +275,105 @@ scrcpy(struct scrcpy_options *options) {
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync,
.downsize_on_error = options->downsize_on_error,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
.cleanup = options->cleanup,
.power_on = options->power_on,
};
static const struct sc_server_callbacks cbs = {
.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, &params, &cbs, NULL)) {
return SCRCPY_EXIT_FAILURE;
if (!server_init(&server, &params)) {
return false;
}
if (!sc_server_start(&s->server)) {
if (!server_start(&server)) {
goto end;
}
server_started = true;
if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
goto end;
}
if (!server_connect_to(&server)) {
goto end;
}
char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size;
// screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
goto end;
}
if (options->display) {
sdl_set_hints(options->render_driver);
}
// 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;
}
sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling
bool connected;
if (!await_for_server(&connected)) {
goto end;
}
if (!connected) {
// This is not an error, user requested to quit
ret = SCRCPY_EXIT_SUCCESS;
goto end;
}
// It is necessarily initialized here, since the device is connected
struct sc_server_info *info = &s->server.info;
const char *serial = s->server.serial;
assert(serial);
struct sc_file_pusher *fp = NULL;
if (options->display && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
if (!fps_counter_init(&fps_counter)) {
goto end;
}
fp = &s->file_pusher;
file_pusher_initialized = true;
fps_counter_initialized = true;
if (options->control) {
if (!file_handler_init(&file_handler, options->serial,
options->push_target)) {
goto end;
}
file_handler_initialized = true;
}
}
struct sc_decoder *dec = NULL;
struct decoder *dec = NULL;
bool needs_decoder = options->display;
#ifdef HAVE_V4L2
needs_decoder |= !!options->v4l2_device;
#endif
if (needs_decoder) {
sc_decoder_init(&s->decoder);
dec = &s->decoder;
decoder_init(&decoder);
dec = &decoder;
}
struct sc_recorder *rec = NULL;
if (options->record_filename) {
if (!sc_recorder_init(&s->recorder,
options->record_filename,
options->record_format,
info->frame_size)) {
struct recorder *rec = NULL;
if (record) {
if (!recorder_init(&recorder,
options->record_filename,
options->record_format,
frame_size)) {
goto end;
}
rec = &s->recorder;
rec = &recorder;
recorder_initialized = true;
}
av_log_set_callback(av_log_callback);
static const struct sc_demuxer_callbacks demuxer_cbs = {
.on_eos = sc_demuxer_on_eos,
};
sc_demuxer_init(&s->demuxer, s->server.video_socket, &demuxer_cbs, NULL);
stream_init(&stream, server.video_socket);
if (dec) {
sc_demuxer_add_sink(&s->demuxer, &dec->packet_sink);
stream_add_sink(&stream, &dec->packet_sink);
}
if (rec) {
sc_demuxer_add_sink(&s->demuxer, &rec->packet_sink);
stream_add_sink(&stream, &rec->packet_sink);
}
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
#ifdef HAVE_USB
bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
if (use_hid_keyboard || use_hid_mouse) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
}
ok = sc_usb_init(&s->usb);
if (!ok) {
LOGE("Failed to initialize USB");
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
assert(serial);
struct sc_usb_device usb_device;
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
if (!ok) {
sc_usb_destroy(&s->usb);
goto aoa_hid_end;
}
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device.serial, usb_device.vid, usb_device.pid,
usb_device.manufacturer, usb_device.product);
ok = sc_usb_connect(&s->usb, usb_device.device, NULL, NULL);
sc_usb_device_destroy(&usb_device);
if (!ok) {
LOGE("Failed to connect to USB device %s", serial);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync);
if (!ok) {
LOGE("Failed to enable HID over AOA");
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
if (use_hid_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor;
} else {
LOGE("Could not initialize HID keyboard");
}
}
if (use_hid_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor;
} else {
LOGE("Could not initialized HID mouse");
}
}
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
acksync = &s->acksync;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
hid_keyboard_initialized = false;
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
hid_mouse_initialized = false;
}
}
if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
}
}
#else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
#endif
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_processor;
}
// mouse_input_mode may have been reset if HID mode failed
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
mp = &s->mouse_inject.mouse_processor;
}
if (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end;
}
controller_initialized = true;
if (!sc_controller_start(&s->controller)) {
goto end;
}
controller_started = true;
controller = &s->controller;
if (options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->display) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
if (options->control) {
if (!controller_init(&controller, server.control_socket)) {
goto end;
}
controller_initialized = true;
struct sc_screen_params screen_params = {
.controller = controller,
.fp = fp,
.kp = kp,
.mp = mp,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = &options->shortcut_mods,
if (!controller_start(&controller)) {
goto end;
}
controller_started = true;
}
const char *window_title =
options->window_title ? options->window_title : device_name;
struct screen_params screen_params = {
.window_title = window_title,
.frame_size = info->frame_size,
.frame_size = frame_size,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
@ -612,124 +383,112 @@ aoa_hid_end:
.rotation = options->rotation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.buffering_time = options->display_buffer,
};
if (!sc_screen_init(&s->screen, &screen_params)) {
if (!screen_init(&screen, &fps_counter, &screen_params)) {
goto end;
}
screen_initialized = true;
sc_decoder_add_sink(&s->decoder, &s->screen.frame_sink);
decoder_add_sink(&decoder, &screen.frame_sink);
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
#ifdef HAVE_V4L2
if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
info->frame_size, options->v4l2_buffer)) {
if (!sc_v4l2_sink_init(&v4l2_sink, options->v4l2_device, frame_size)) {
goto end;
}
sc_decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
decoder_add_sink(&decoder, &v4l2_sink.frame_sink);
v4l2_sink_initialized = true;
}
#endif
// now we consumed the header values, the socket receives the video stream
// start the demuxer
if (!sc_demuxer_start(&s->demuxer)) {
// start the stream
if (!stream_start(&stream)) {
goto end;
}
demuxer_started = true;
stream_started = true;
ret = event_loop(s);
input_manager_init(&input_manager, options);
ret = event_loop(options);
LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may
// only be called once the demuxer thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
// only be called once the stream thread is joined (it may take time)
screen_hide_window(&screen);
end:
// The demuxer is not stopped explicitly, because it will stop by itself on
// The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream
#ifdef HAVE_USB
if (aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
}
sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);
}
if (acksync) {
sc_acksync_destroy(acksync);
}
#endif
if (controller_started) {
sc_controller_stop(&s->controller);
controller_stop(&controller);
}
if (file_pusher_initialized) {
sc_file_pusher_stop(&s->file_pusher);
if (file_handler_initialized) {
file_handler_stop(&file_handler);
}
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
if (fps_counter_initialized) {
fps_counter_interrupt(&fps_counter);
}
if (server_started) {
// shutdown the sockets and kill the server
sc_server_stop(&s->server);
server_stop(&server);
}
// now that the sockets are shutdown, the demuxer and controller are
// now that the sockets are shutdown, the stream and controller are
// interrupted, we can join them
if (demuxer_started) {
sc_demuxer_join(&s->demuxer);
if (stream_started) {
stream_join(&stream);
}
#ifdef HAVE_V4L2
if (v4l2_sink_initialized) {
sc_v4l2_sink_destroy(&s->v4l2_sink);
sc_v4l2_sink_destroy(&v4l2_sink);
}
#endif
#ifdef HAVE_USB
if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa);
sc_usb_join(&s->usb);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
}
#endif
// Destroy the screen only after the demuxer is guaranteed to be finished,
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
screen_destroy(&screen);
}
if (controller_started) {
sc_controller_join(&s->controller);
controller_join(&controller);
}
if (controller_initialized) {
sc_controller_destroy(&s->controller);
controller_destroy(&controller);
}
if (recorder_initialized) {
sc_recorder_destroy(&s->recorder);
recorder_destroy(&recorder);
}
if (file_pusher_initialized) {
sc_file_pusher_join(&s->file_pusher);
sc_file_pusher_destroy(&s->file_pusher);
if (file_handler_initialized) {
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
}
sc_server_destroy(&s->server);
if (fps_counter_initialized) {
fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter);
}
server_destroy(&server);
return ret;
}

View File

@ -4,20 +4,146 @@
#include "common.h"
#include <stdbool.h>
#include "options.h"
#include <stddef.h>
#include <stdint.h>
enum scrcpy_exit_code {
// Normal program termination
SCRCPY_EXIT_SUCCESS,
// No connection could be established
SCRCPY_EXIT_FAILURE,
// Device was disconnected while running
SCRCPY_EXIT_DISCONNECTED,
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options);
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,
};
#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;
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;
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, \
.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, \
.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(const struct scrcpy_options *options);
#endif

View File

@ -5,18 +5,19 @@
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.h"
#include "options.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/log.h"
#define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
struct sc_size rotated_size;
static inline struct size
get_rotated_size(struct size size, int rotation) {
struct size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
@ -27,34 +28,22 @@ get_rotated_size(struct sc_size size, int rotation) {
return rotated_size;
}
// get the window size in a struct sc_size
static struct sc_size
get_window_size(const struct sc_screen *screen) {
// get the window size in a struct size
static struct size
get_window_size(const struct screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
struct sc_size size;
struct size size;
size.width = width;
size.height = height;
return size;
}
static struct sc_point
get_window_position(const struct sc_screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
struct sc_point point;
point.x = x;
point.y = y;
return point;
}
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
set_window_size(struct screen *screen, struct size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -62,9 +51,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)
static bool
get_preferred_display_bounds(struct sc_size *bounds) {
get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect;
if (SDL_GetDisplayUsableBounds(0, &rect)) {
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif
if (GET_DISPLAY_BOUNDS(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false;
}
@ -75,7 +69,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
}
static bool
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
@ -89,21 +83,20 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// crops the black borders)
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct sc_size
get_optimal_size(struct sc_size current_size, struct sc_size content_size,
bool within_display_bounds) {
static struct size
get_optimal_size(struct size current_size, struct size content_size) {
if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0
return current_size;
}
struct sc_size window_size;
struct size window_size;
struct sc_size display_size;
if (!within_display_bounds ||
!get_preferred_display_bounds(&display_size)) {
// do not constraint the size
window_size = current_size;
struct size display_size;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
window_size.width = current_size.width;
window_size.height = current_size.height;
} else {
window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height);
@ -129,14 +122,21 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size,
return window_size;
}
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size window_size = get_window_size(screen);
return get_optimal_size(window_size, content_size);
}
// initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user
static inline struct sc_size
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
static inline struct size
get_initial_optimal_size(struct size content_size, uint16_t req_width,
uint16_t req_height) {
struct sc_size window_size;
struct size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size, true);
window_size = get_optimal_size(content_size, content_size);
} else {
if (req_width) {
window_size.width = req_width;
@ -156,62 +156,15 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size;
}
static inline bool
sc_screen_is_relative_mode(struct sc_screen *screen) {
// screen->im.mp may be NULL if --no-control
return screen->im.mp && screen->im.mp->relative_mode;
}
static void
sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(screen->window, &x, &y);
SDL_GetWindowSize(screen->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
}
}
#else
(void) screen;
#endif
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
}
}
static inline bool
sc_screen_get_mouse_capture(struct sc_screen *screen) {
(void) screen;
return SDL_GetRelativeMouseMode();
}
static inline void
sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
(void) screen;
bool new_value = !sc_screen_get_mouse_capture(screen);
sc_screen_set_mouse_capture(screen, new_value);
}
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
screen_update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct sc_size content_size = screen->content_size;
struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct sc_size drawable_size = {dw, dh};
struct size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect;
@ -241,9 +194,9 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
}
static inline SDL_Texture *
create_texture(struct sc_screen *screen) {
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size;
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
@ -267,45 +220,6 @@ create_texture(struct sc_screen *screen) {
return texture;
}
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
static void
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 = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
@ -318,20 +232,20 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data;
struct screen *screen = data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
sc_screen_render(screen, true);
screen_render(screen, true);
}
return 0;
}
#endif
static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
@ -342,8 +256,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
}
static void
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
@ -353,115 +267,93 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
}
static bool
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct sc_screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
assert(!screen->event_failed || previous_skipped);
bool need_new_event;
if (previous_skipped) {
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed
need_new_event = screen->event_failed;
} else {
need_new_event = true;
bool previous_frame_skipped;
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
if (!ok) {
return false;
}
if (need_new_event) {
if (previous_frame_skipped) {
fps_counter_add_skipped_frame(screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead
} else {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
int ret = SDL_PushEvent(&new_frame_event);
if (ret < 0) {
LOGW("Could not post new frame event: %s", SDL_GetError());
screen->event_failed = true;
} else {
screen->event_failed = false;
}
SDL_PushEvent(&new_frame_event);
}
return true;
}
bool
sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen_init(struct screen *screen, struct fps_counter *fps_counter,
const struct screen_params *params) {
screen->fps_counter = fps_counter;
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen->event_failed = false;
screen->mouse_capture_key_pressed = 0;
screen->req.x = params->window_x;
screen->req.y = params->window_y;
screen->req.width = params->window_width;
screen->req.height = params->window_height;
screen->req.fullscreen = params->fullscreen;
screen->req.start_fps_counter = params->start_fps_counter;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen);
bool ok = video_buffer_init(&screen->vb);
if (!ok) {
LOGE("Could not initialize video buffer");
return false;
}
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
goto error_destroy_video_buffer;
}
if (!sc_fps_counter_init(&screen->fps_counter)) {
goto error_stop_and_join_video_buffer;
}
screen->frame_size = params->frame_size;
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
struct sc_size content_size =
struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size = get_initial_optimal_size(content_size,
params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
}
if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
// The window will be positioned and sized on first video frame
screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y,
window_size.width, window_size.height,
window_flags);
if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
LOGC("Could not create window: %s", SDL_GetError());
return false;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window;
LOGC("Could not create renderer: %s", SDL_GetError());
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false;
}
SDL_RendererInfo renderer_info;
@ -497,10 +389,10 @@ sc_screen_init(struct sc_screen *screen,
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = scrcpy_icon_load();
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
scrcpy_icon_destroy(icon);
SDL_FreeSurface(icon);
} else {
LOGW("Could not load icon");
}
@ -509,38 +401,42 @@ sc_screen_init(struct sc_screen *screen,
params->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGE("Could not create texture: %s", SDL_GetError());
goto error_destroy_renderer;
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false;
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOG_OOM();
goto error_destroy_texture;
LOGC("Could not create screen frame");
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false;
}
struct sc_input_manager_params im_params = {
.controller = params->controller,
.fp = params->fp,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
// HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
sc_input_manager_init(&screen->im, &im_params);
screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen);
#endif
static const struct sc_frame_sink_ops ops = {
.open = sc_screen_frame_sink_open,
.close = sc_screen_frame_sink_close,
.push = sc_screen_frame_sink_push,
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
@ -550,68 +446,20 @@ sc_screen_init(struct sc_screen *screen,
#endif
return true;
error_destroy_texture:
SDL_DestroyTexture(screen->texture);
error_destroy_renderer:
SDL_DestroyRenderer(screen->renderer);
error_destroy_window:
SDL_DestroyWindow(screen->window);
error_destroy_fps_counter:
sc_fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
error_destroy_video_buffer:
sc_video_buffer_destroy(&screen->vb);
return false;
}
static void
sc_screen_show_initial_window(struct sc_screen *screen) {
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
struct sc_size window_size =
get_initial_optimal_size(screen->content_size, screen->req.width,
screen->req.height);
set_window_size(screen, window_size);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_switch_fullscreen(screen);
}
if (screen->req.start_fps_counter) {
sc_fps_counter_start(&screen->fps_counter);
}
screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window);
}
void
sc_screen_hide_window(struct sc_screen *screen) {
screen_hide_window(struct screen *screen) {
SDL_HideWindow(screen->window);
}
void
sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb);
sc_fps_counter_interrupt(&screen->fps_counter);
}
void
sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb);
sc_fps_counter_join(&screen->fps_counter);
}
void
sc_screen_destroy(struct sc_screen *screen) {
screen_destroy(struct screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
@ -619,26 +467,25 @@ sc_screen_destroy(struct sc_screen *screen) {
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
sc_fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb);
video_buffer_destroy(&screen->vb);
}
static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
resize_for_content(struct screen *screen, struct size old_content_size,
struct size new_content_size) {
struct size window_size = get_window_size(screen);
struct size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size, true);
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
}
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
set_content_size(struct screen *screen, struct size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@ -652,7 +499,7 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
}
static void
apply_pending_resize(struct sc_screen *screen) {
apply_pending_resize(struct screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
@ -663,13 +510,13 @@ apply_pending_resize(struct sc_screen *screen) {
}
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct sc_size new_content_size =
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
@ -677,12 +524,12 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
sc_screen_render(screen, true);
screen_render(screen, true);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
@ -690,17 +537,17 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGE("Could not create texture: %s", SDL_GetError());
LOGC("Could not create texture: %s", SDL_GetError());
return false;
}
}
@ -710,7 +557,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct sc_screen *screen, const AVFrame *frame) {
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
@ -724,36 +571,59 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
}
static bool
sc_screen_update_frame(struct sc_screen *screen) {
screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame);
video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
fps_counter_add_rendered_frame(screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height};
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
}
update_texture(screen, frame);
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_screen_set_mouse_capture(screen, true);
}
}
sc_screen_render(screen, false);
screen_render(screen, false);
return true;
}
void
sc_screen_switch_fullscreen(struct sc_screen *screen) {
screen_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 = &rect;
} 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;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@ -766,35 +636,24 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
sc_screen_render(screen, true);
screen_render(screen, true);
}
void
sc_screen_resize_to_fit(struct sc_screen *screen) {
screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen || screen->maximized) {
return;
}
struct sc_point point = get_window_position(screen);
struct sc_size window_size = get_window_size(screen);
struct sc_size optimal_size =
get_optimal_size(window_size, screen->content_size, false);
// Center the window related to the device screen
assert(optimal_size.width <= window_size.width);
assert(optimal_size.height <= window_size.height);
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
struct size optimal_size =
get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
screen_resize_to_pixel_perfect(struct screen *screen) {
if (screen->fullscreen) {
return;
}
@ -804,40 +663,37 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
screen->maximized = false;
}
struct sc_size content_size = screen->content_size;
struct size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
}
static inline bool
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
bool relative_mode = sc_screen_is_relative_mode(screen);
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return;
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return;
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
sc_screen_render(screen, true);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
sc_screen_render(screen, true);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
@ -853,79 +709,17 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
}
screen->maximized = false;
apply_pending_resize(screen);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (relative_mode) {
sc_screen_set_mouse_capture(screen, false);
}
break;
}
return;
case SDL_KEYDOWN:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
if (sc_screen_is_mouse_capture_key(key)) {
if (!screen->mouse_capture_key_pressed) {
screen->mouse_capture_key_pressed = key;
} else {
// Another mouse capture key has been pressed, cancel
// mouse (un)capture
screen->mouse_capture_key_pressed = 0;
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_KEYUP:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
if (sc_screen_is_mouse_capture_key(key)) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_toggle_mouse_capture(screen);
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return;
}
break;
case SDL_MOUSEBUTTONUP:
if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
sc_screen_set_mouse_capture(screen, true);
return;
}
break;
return true;
}
sc_input_manager_handle_event(&screen->im, event);
return false;
}
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
@ -937,7 +731,7 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct sc_point result;
struct point result;
switch (rotation) {
case 0:
result.x = x;
@ -960,15 +754,15 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
return result;
}
struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
sc_screen_hidpi_scale_coords(screen, &x, &y);
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
}
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@ -7,48 +7,32 @@
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
#include "video_buffer.h"
struct sc_screen {
struct screen {
struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct sc_input_manager im;
struct sc_video_buffer vb;
struct sc_fps_counter fps_counter;
// The initial requested window properties
struct {
int16_t x;
int16_t y;
uint16_t width;
uint16_t height;
bool fullscreen;
bool start_fps_counter;
} req;
struct video_buffer vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
struct sc_size frame_size;
struct sc_size content_size; // rotated frame_size
struct size frame_size;
struct size content_size; // rotated frame_size
bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or
// fullscreen (meaningful only when resize_pending is true)
struct sc_size windowed_content_size;
struct size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
@ -59,34 +43,18 @@ struct sc_screen {
bool maximized;
bool mipmaps;
bool event_failed; // in case SDL_PushEvent() returned an error
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.
SDL_Keycode mouse_capture_key_pressed;
AVFrame *frame;
};
struct sc_screen_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
struct screen_params {
const char *window_title;
struct sc_size frame_size;
struct size frame_size;
bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_width;
uint16_t window_height;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
@ -94,72 +62,68 @@ struct sc_screen_params {
bool mipmaps;
bool fullscreen;
bool start_fps_counter;
sc_tick buffering_time;
};
// initialize screen, create window, renderer and texture (window is hidden)
bool
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
// request to interrupt any inner thread
// must be called before screen_join()
void
sc_screen_interrupt(struct sc_screen *screen);
// join any inner thread
void
sc_screen_join(struct sc_screen *screen);
screen_init(struct screen *screen, struct fps_counter *fps_counter,
const struct screen_params *params);
// destroy window, renderer and texture (if any)
void
sc_screen_destroy(struct sc_screen *screen);
screen_destroy(struct screen *screen);
// hide the window
//
// It is used to hide the window immediately on closing without waiting for
// screen_destroy()
void
sc_screen_hide_window(struct sc_screen *screen);
screen_hide_window(struct screen *screen);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void
screen_render(struct screen *screen, bool update_content_rect);
// switch the fullscreen mode
void
sc_screen_switch_fullscreen(struct sc_screen *screen);
screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders)
void
sc_screen_resize_to_fit(struct sc_screen *screen);
screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
screen_resize_to_pixel_perfect(struct screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y);
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -7,31 +7,19 @@
#include <stdbool.h>
#include <stdint.h>
#include "adb/adb_tunnel.h"
#include "coords.h"
#include "options.h"
#include "util/intr.h"
#include "adb.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
#define SC_DEVICE_NAME_FIELD_LENGTH 64
struct sc_server_info {
char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
struct sc_size frame_size;
};
struct sc_server_params {
uint32_t uid;
const char *req_serial;
struct server_params {
enum sc_log_level log_level;
enum sc_codec codec;
const char *serial;
const char *crop;
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
@ -42,74 +30,58 @@ struct sc_server_params {
bool stay_awake;
bool force_adb_forward;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
bool select_usb;
bool select_tcpip;
bool cleanup;
bool power_on;
};
struct sc_server {
// The internal allocated strings are copies owned by the server
struct sc_server_params params;
struct server {
char *serial;
char *device_socket_name;
sc_thread thread;
struct sc_server_info info; // initialized once connected
process_t process;
sc_thread wait_server_thread;
atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond cond_stopped;
bool stopped;
sc_cond process_terminated_cond;
bool process_terminated;
struct sc_intr intr;
struct sc_adb_tunnel tunnel;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
sc_socket video_socket;
sc_socket control_socket;
// The internal allocated strings are copies owned by the server
struct server_params params;
const struct sc_server_callbacks *cbs;
void *cbs_userdata;
const struct server_callbacks *cbs;
void *userdata;
};
struct sc_server_callbacks {
/**
* Called when the server failed to connect
*
* If it is called, then on_connected() and on_disconnected() will never be
* called.
*/
void (*on_connection_failed)(struct sc_server *server, void *userdata);
/**
* Called on server connection
*/
void (*on_connected)(struct sc_server *server, void *userdata);
/**
* Called on server disconnection (after it has been connected)
*/
void (*on_disconnected)(struct sc_server *server, void *userdata);
struct server_callbacks {
void (*on_connection_failed)(struct server *server);
void (*on_connected)(struct server *server, const char *name,
struct size size, void *userdata);
void (*on_disconnected)(struct server *server, void *userdata);
};
// init the server with the given params
// init server fields
bool
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata);
server_init(struct server *server, const struct server_params *params);
// start the server asynchronously
// push, enable tunnel et start the server
bool
sc_server_start(struct sc_server *server);
server_start(struct server *server, const struct server_callbacks *cbs,
void *userdata);
// block until the communication with the server is established
bool
server_connect_to(struct server *server);
// disconnect and kill the server process
void
sc_server_stop(struct sc_server *server);
server_stop(struct server *server);
// close and release sockets
void
sc_server_destroy(struct sc_server *server);
server_destroy(struct server *server);
#endif

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

@ -0,0 +1,288 @@
#include "stream.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <unistd.h>
#include "decoder.h"
#include "events.h"
#include "recorder.h"
#include "util/buffer_util.h"
#include "util/log.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static bool
stream_recv_packet(struct stream *stream, AVPacket *packet) {
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r < HEADER_SIZE) {
return false;
}
uint64_t pts = buffer_read64be(header);
uint32_t len = buffer_read32be(&header[8]);
assert(pts == NO_PTS || (pts & 0x8000000000000000) == 0);
assert(len);
if (av_new_packet(packet, len)) {
LOGE("Could not allocate packet");
return false;
}
r = net_recv_all(stream->socket, packet->data, len);
if (r < 0 || ((uint32_t) r) < len) {
av_packet_unref(packet);
return false;
}
packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE;
return true;
}
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static bool
push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->push(sink, packet)) {
LOGE("Could not send config packet to sink %d", i);
return false;
}
}
return true;
}
static bool
stream_parse(struct stream *stream, AVPacket *packet) {
uint8_t *in_data = packet->data;
int in_len = packet->size;
uint8_t *out_data = NULL;
int out_len = 0;
int r = av_parser_parse2(stream->parser, stream->codec_ctx,
&out_data, &out_len, in_data, in_len,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
// PARSER_FLAG_COMPLETE_FRAMES is set
assert(r == in_len);
(void) r;
assert(out_len == in_len);
if (stream->parser->key_frame == 1) {
packet->flags |= AV_PKT_FLAG_KEY;
}
packet->dts = packet->pts;
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) {
LOGE("Could not process packet");
return false;
}
return true;
}
static bool
stream_push_packet(struct stream *stream, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immetiately (it contains no
// frame); instead, it must be concatenated with the future data packet.
if (stream->has_pending || is_config) {
size_t offset;
if (stream->has_pending) {
offset = stream->pending.size;
if (av_grow_packet(&stream->pending, packet->size)) {
LOGE("Could not grow packet");
return false;
}
} else {
offset = 0;
if (av_new_packet(&stream->pending, packet->size)) {
LOGE("Could not create packet");
return false;
}
stream->has_pending = true;
}
memcpy(stream->pending.data + offset, packet->data, packet->size);
if (!is_config) {
// prepare the concat packet to send to the decoder
stream->pending.pts = packet->pts;
stream->pending.dts = packet->dts;
stream->pending.flags = packet->flags;
packet = &stream->pending;
}
}
if (is_config) {
// config packet
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) {
return false;
}
} else {
// data packet
bool ok = stream_parse(stream, packet);
if (stream->has_pending) {
// the pending packet must be discarded (consumed or error)
stream->has_pending = false;
av_packet_unref(&stream->pending);
}
if (!ok) {
return false;
}
}
return true;
}
static void
stream_close_first_sinks(struct stream *stream, unsigned count) {
while (count) {
struct sc_packet_sink *sink = stream->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
stream_close_sinks(struct stream *stream) {
stream_close_first_sinks(stream, stream->sink_count);
}
static bool
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
stream_close_first_sinks(stream, i);
return false;
}
}
return true;
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
stream->codec_ctx = avcodec_alloc_context3(codec);
if (!stream->codec_ctx) {
LOGC("Could not allocate codec context");
goto end;
}
if (!stream_open_sinks(stream, codec)) {
LOGE("Could not open stream sinks");
goto finally_free_codec_ctx;
}
stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->parser) {
LOGE("Could not initialize parser");
goto finally_close_sinks;
}
// We must only pass complete frames to av_parser_parse2()!
// It's more complicated, but this allows to reduce the latency by 1 frame!
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
for (;;) {
AVPacket packet;
bool ok = stream_recv_packet(stream, &packet);
if (!ok) {
// end of stream
break;
}
ok = stream_push_packet(stream, &packet);
av_packet_unref(&packet);
if (!ok) {
// cannot process packet (error already logged)
break;
}
}
LOGD("End of frames");
if (stream->has_pending) {
av_packet_unref(&stream->pending);
}
av_parser_close(stream->parser);
finally_close_sinks:
stream_close_sinks(stream);
finally_free_codec_ctx:
avcodec_free_context(&stream->codec_ctx);
end:
notify_stopped();
return 0;
}
void
stream_init(struct stream *stream, socket_t socket) {
stream->socket = socket;
stream->has_pending = false;
stream->sink_count = 0;
}
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
assert(stream->sink_count < STREAM_MAX_SINKS);
assert(sink);
assert(sink->ops);
stream->sinks[stream->sink_count++] = sink;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
if (!ok) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_join(struct stream *stream) {
sc_thread_join(&stream->thread, NULL);
}

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

@ -0,0 +1,44 @@
#ifndef STREAM_H
#define STREAM_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h>
#include "trait/packet_sink.h"
#include "util/net.h"
#include "util/thread.h"
#define STREAM_MAX_SINKS 2
struct stream {
socket_t socket;
sc_thread thread;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
bool has_pending;
AVPacket pending;
};
void
stream_init(struct stream *stream, socket_t socket);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool
stream_start(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@ -1,81 +0,0 @@
#include "util/file.h"
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "util/log.h"
bool
sc_file_executable_exists(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
{
LOG_OOM();
continue;
}
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
sc_file_is_regular(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@ -1,204 +1,128 @@
#include "util/process.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "util/log.h"
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
int *pin, int *pout, int *perr) {
bool inherit_stdout = !pout && !(flags & SC_PROCESS_NO_STDOUT);
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
bool
search_executable(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
int in[2];
int out[2];
int err[2];
int internal[2]; // communication between parent and children
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
if (pipe(internal) == -1) {
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
return SC_PROCESS_ERROR_GENERIC;
}
if (pin) {
if (pipe(in) == -1) {
perror("pipe");
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
}
}
if (pout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
}
}
if (perr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pout) {
close(out[0]);
close(out[1]);
}
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
}
return PROCESS_ERROR_GENERIC;
}
enum process_result ret = PROCESS_SUCCESS;
*pid = fork();
if (*pid == -1) {
perror("fork");
// clean up
if (perr) {
close(err[0]);
close(err[1]);
}
if (pout) {
close(out[0]);
close(out[1]);
}
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
ret = PROCESS_ERROR_GENERIC;
goto end;
}
if (*pid == 0) {
if (pin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
close(in[1]);
} else {
int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDIN_FILENO);
} else {
LOGE("Could not open /dev/null for stdin");
}
if (*pid > 0) {
// parent close write side
close(fd[1]);
fd[1] = -1;
// wait for EOF or receive errno from child
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
if (pout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
} else if (!inherit_stdout) {
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDOUT_FILENO);
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
LOGE("Could not open /dev/null for stdout");
ret = PROCESS_ERROR_GENERIC;
}
}
if (perr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
}
close(err[0]);
} else if (!inherit_stderr) {
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDERR_FILENO);
} else {
LOGE("Could not open /dev/null for stderr");
}
}
close(internal[0]);
enum sc_process_result err;
// Somehow SDL masks many signals - undo them for other processes
// https://github.com/libsdl-org/SDL/blob/release-2.0.18/src/thread/pthread/SDL_systhread.c#L167
sigset_t mask;
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC;
} else {
perror("fcntl");
err = SC_PROCESS_ERROR_GENERIC;
ret = PROCESS_ERROR_GENERIC;
}
// send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) {
// send ret to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) {
perror("write");
}
close(internal[1]);
// close write side before exiting
close(fd[1]);
_exit(1);
}
// parent
assert(*pid > 0);
close(internal[1]);
enum sc_process_result res = SC_PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = SC_PROCESS_ERROR_GENERIC;
end:
if (fd[0] != -1) {
close(fd[0]);
}
close(internal[0]);
if (pin) {
close(in[0]);
*pin = in[1];
if (fd[1] != -1) {
close(fd[1]);
}
if (pout) {
*pout = out[0];
close(out[1]);
}
if (perr) {
*perr = err[0];
close(err[1]);
}
return res;
return ret;
}
bool
sc_process_terminate(pid_t pid) {
process_terminate(pid_t pid) {
if (pid <= 0) {
LOGE("Requested to kill %d, this is an error. Please report the bug.\n",
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
abort();
}
return kill(pid, SIGKILL) != -1;
}
sc_exit_code
sc_process_wait(pid_t pid, bool close) {
exit_code_t
process_wait(pid_t pid, bool close) {
int code;
int options = WEXITED;
if (!close) {
@ -209,7 +133,7 @@ sc_process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal
code = SC_EXIT_CODE_NONE;
code = NO_EXIT_CODE;
} else {
code = info.si_status;
}
@ -217,18 +141,37 @@ sc_process_wait(pid_t pid, bool close) {
}
void
sc_process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code
process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code
}
ssize_t
sc_pipe_read(int pipe, char *data, size_t len) {
return read(pipe, data, len);
}
void
sc_pipe_close(int pipe) {
if (close(pipe)) {
perror("close pipe");
char *
get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
is_regular_file(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

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