Compare commits

..

26 Commits

Author SHA1 Message Date
5f2cf12acf Add intermediate frame in video buffer
There were only two frames simultaneously:
 - one used by the decoder;
 - one used by the renderer.

When the decoder finished decoding a frame, it swapped it with the
rendering frame.

Adding a third frame provides several benefits:
 - the decoder do not have to wait for the renderer to release the
   mutex;
 - it simplifies the video_buffer API;
 - it makes the rendering frame valid until the next call to
   video_buffer_take_rendering_frame(), which will be useful for
   swscaling on window resize.
2021-02-04 08:12:22 +01:00
2a3c6b64dd Assert non-recursive usage of mutexes 2021-02-04 08:12:22 +01:00
a9582b1d43 Add mutex assertions 2021-02-04 08:12:22 +01:00
fa3e84b700 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-04 08:12:22 +01:00
1521de9051 Expose thread id 2021-02-04 08:12:22 +01:00
eabaabdb78 Wrap SDL thread functions into scrcpy-specific API
The goal is to expose a consistent API for system tools, and paves the
way to make the "core" independant of SDL in the future.
2021-02-04 08:12:22 +01:00
8b48003074 Replace SDL_strdup() by strdup()
The functions SDL_malloc(), SDL_free() and SDL_strdup() were used only
because strdup() was not available everywhere.

Now that it is available, use the native version of these functions.
2021-02-04 08:12:22 +01:00
74bd25a0ed Provide strdup() compat
Make strdup() available on all platforms.
2021-02-04 08:12:22 +01:00
97b001e7c0 Fix undefined left shift
Small unsigned integers promote to signed int. As a consequence, if v is
a uint8_t, then (v << 24) yields an int, so the left shift is undefined
if the MSB is 1.

Cast to uint32_t to yield an unsigned value.

Reported by USAN (meson x -Db_sanitize=undefined):

    runtime error: left shift of 255 by 24 places cannot be represented
    in type 'int'
2021-01-24 15:31:21 +01:00
d8e9ad20b0 Improve file handler error message
Terminating the file handler current process may be either a "push" or
"install" command.
2021-01-24 14:24:24 +01:00
b566700bfd Kill process with SIGKILL signal
An "adb push" command is not terminated by SIGTERM.
2021-01-24 14:24:24 +01:00
7afd149f4b Fix file_handler process race condition
The current process could be waited both by run_file_handler() and
file_handler_stop().

To avoid the race condition, wait the process without closing, then
close with mutex locked.
2021-01-24 14:24:24 +01:00
6a50231698 Expose a single process_wait()
There were two versions: process_wait() and process_wait_noclose().

Expose a single version with a flag (it was already implemented that way
internally).
2021-01-24 14:24:18 +01:00
b8edcf52b0 Simplify process_wait()
The function process_wait() returned a bool (true if the process
terminated successfully) and provided the exit code via an output
parameter exit_code.

But the returned value was always equivalent to exit_code == 0, so just
return the exit code instead.
2021-01-22 18:29:21 +01:00
94eff0a4bb Fix size_t incorrectly assigned to int
The function control_msg_serialize() returns a size_t.
2021-01-17 19:44:23 +01:00
8dbb1676b7 Factorize meson compiler variable initialization 2021-01-17 19:44:23 +01:00
ab912c23e7 Define feature test macros in common.h
This enables necessary functions once for all.

As a consequence, define common.h before any other header.
2021-01-17 14:08:48 +01:00
59feb2a15c Group common includes into common.h
Include config.h and compat.h in common.h, and include common.h from all
source files.
2021-01-08 19:22:10 +01:00
6385b8c162 Move common structs to coords.h
The size, point and position structs were defined in common.h. Move them
to coords.h so that common.h could be used for generic code to be
included in all source files.
2021-01-08 19:22:10 +01:00
037be4af21 Fix compat missing include
The header libavformat/version.h was included, but not
libavcodec/version.h.

As a consequence, the LIBAVCODEC_VERSION_INT definition depended on the
caller includes.
2021-01-08 19:21:54 +01:00
1e215199dc Remove unused struct port_range
It had been replaced by struct sc_port_range in scrcpy.h.
2021-01-08 19:13:53 +01:00
d580ee30f1 Separate process wait and close
On Linux, waitpid() both waits for the process to terminate and reaps it
(closes its handle). On Windows, these actions are separated into
WaitForSingleObject() and CloseHandle().

Expose these actions separately, so that it is possible to send a signal
to a process while waiting for its termination without race condition.

This allows to wait for server termination normally, but kill the
process without race condition if it is not terminated after some delay.
2021-01-08 16:44:21 +01:00
821c175730 Rename process_simple_wait to process_wait
Adding "simple" in the function name brings no benefit.
2021-01-08 16:44:21 +01:00
cc6f5020d8 Move conditional src files in meson.build
Declare all the source files (including the platform-specific ones) at
the beginning.
2021-01-08 16:44:21 +01:00
4bd9da4c93 Split command into process and adb
The process API provides the system-specific implementation, the adb API
uses it to expose adb commands.
2021-01-08 16:44:21 +01:00
aa8b571389 Increase display id range
Some devices use big display id values.

Refs #2009 <https://github.com/Genymobile/scrcpy/issues/2009>
2021-01-04 08:16:32 +01:00
77 changed files with 1307 additions and 2059 deletions

120
BUILD.md
View File

@ -2,37 +2,11 @@
Here are the instructions to build _scrcpy_ (client and server). 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 [prebuilt server]: #prebuilt-server
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 \
libavcodec-dev libavformat-dev libavutil-dev libsdl2-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
```
## Branches ## Branches
@ -86,10 +60,11 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb
# client build dependencies # client build dependencies
sudo apt install gcc git pkg-config meson ninja-build \ sudo apt install gcc git pkg-config meson ninja-build \
libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies # 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 On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install
@ -131,13 +106,13 @@ sudo apt install mingw-w64 mingw-w64-tools
You also need the JDK to build the server: You also need the JDK to build the server:
```bash ```bash
sudo apt install openjdk-11-jdk sudo apt install openjdk-8-jdk
``` ```
Then generate the releases: Then generate the releases:
```bash ```bash
./release.sh make -f Makefile.CrossWindows
``` ```
It will generate win32 and win64 releases into `dist/`. It will generate win32 and win64 releases into `dist/`.
@ -214,27 +189,8 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps ## Common steps
**As a non-root user**, clone the project: If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_SDK_ROOT` to its directory. For example:
```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:
[Android SDK]: https://developer.android.com/studio/index.html [Android SDK]: https://developer.android.com/studio/index.html
@ -247,11 +203,20 @@ export ANDROID_SDK_ROOT=~/Library/Android/sdk
set ANDROID_SDK_ROOT=%LOCALAPPDATA%\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: Then, build:
```bash ```bash
meson x --buildtype release --strip -Db_lto=true meson x --buildtype release --strip -Db_lto=true
ninja -Cx # DO NOT RUN AS ROOT ninja -Cx
``` ```
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja _Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
@ -260,27 +225,9 @@ install` must be run as root)._
[ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a [ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a
#### Option 2: Use prebuilt server ### Run
- [`scrcpy-server-v1.17`][direct-scrcpy-server] To run without installing:
_(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
meson 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:
```bash ```bash
./run x [options] ./run x [options]
@ -303,3 +250,24 @@ This installs two files:
Just remove them to "uninstall" the application. Just remove them to "uninstall" the application.
You can then [run](README.md#run) _scrcpy_. 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
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).

13
FAQ.md
View File

@ -39,11 +39,8 @@ Check [stackoverflow][device-unauthorized].
> adb: error: failed to get feature set: no devices/emulators found > adb: error: failed to get feature set: no devices/emulators found
Check that you correctly enabled [adb debugging][enable-adb].
If your device is not detected, you may need some [drivers] (on Windows). If your device is not detected, you may need some [drivers] (on Windows).
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
@ -114,6 +111,16 @@ In developer options, enable:
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [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 ### Special characters do not work
Injecting text input is [limited to ASCII characters][text-input]. A trick Injecting text input is [limited to ASCII characters][text-input]. A trick

View File

@ -69,7 +69,10 @@ Anda juga bisa [membangun aplikasi secara manual][BUILD] (jangan khawatir, tidak
Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia : Untuk Windows, untuk kesederhanaan, arsip prebuilt dengan semua dependensi (termasuk `adb`) tersedia :
- [README](README.md#windows) - [`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]: Ini juga tersedia di [Chocolatey]:

View File

@ -1,725 +0,0 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.17)
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。
![screenshot](assets/screenshot-debian-600.jpg)
以下に焦点を当てています:
- **軽量** (ネイティブ、デバイス画面表示のみ)
- **パフォーマンス** (30~60fps)
- **クオリティ** (1920x1080以上)
- **低遅延** ([35~70ms][lowlatency])
- **短い起動時間** (初回画像を1秒以内に表示)
- **非侵入型** (デバイスに何もインストールされていない状態になる)
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
## 必要要件
AndroidデバイスはAPI21(Android 5.0)以上。
Androidデバイスで[adbデバッグが有効][enable-adb]であること。
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
一部のAndroidデバイスでは、キーボードとマウスを使用して制御する[追加オプション][control]を有効にする必要がある。
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## アプリの取得
<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
[自分でビルド][BUILD]も可能(心配しないでください、それほど難しくはありません。)
### Windows
Windowsでは簡単に、`adb`を含む)すべての依存関係を構築済みのアーカイブを利用可能です。
- [README](README.md#windows)
[Chocolatey]でも利用可能です:
[Chocolatey]: https://chocolatey.org/
```bash
choco install scrcpy
choco install adb # まだ入手していない場合
```
[Scoop]でも利用可能です:
```bash
scoop install scrcpy
scoop install adb # まだ入手していない場合
```
[Scoop]: https://scoop.sh
また、[アプリケーションをビルド][BUILD]することも可能です。
### macOS
アプリケーションは[Homebrew]で利用可能です。ただインストールするだけです。
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
`PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合:
```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
```
また、[アプリケーションをビルド][BUILD]することも可能です。
## 実行
Androidデバイスを接続し、実行:
```bash
scrcpy
```
次のコマンドでリストされるコマンドライン引数も受け付けます:
```bash
scrcpy --help
```
## 機能
### キャプチャ構成
#### サイズ削減
Androidデバイスを低解像度でミラーリングする場合、パフォーマンス向上に便利な場合があります。
幅と高さをある値(例1024)に制限するには:
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # 短縮版
```
一方のサイズはデバイスのアスペクト比が維持されるように計算されます。この方法では、1920x1080のデバイスでは1024x576にミラーリングされます。
#### ビットレート変更
ビットレートの初期値は8Mbpsです。ビットレートを変更するには(例:2Mbpsに変更):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # 短縮版
```
#### フレームレート制限
キャプチャするフレームレートを制限できます:
```bash
scrcpy --max-fps 15
```
この機能はAndroid 10からオフィシャルサポートとなっていますが、以前のバージョンでも動作する可能性があります。
#### トリミング
デバイスの画面は、画面の一部のみをミラーリングするようにトリミングできます。
これは、例えばOculus Goの片方の目をミラーリングする場合に便利です。:
```bash
scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440
```
もし`--max-size`も指定されている場合、トリミング後にサイズ変更が適用されます。
#### ビデオの向きをロックする
ミラーリングの向きをロックするには:
```bash
scrcpy --lock-video-orientation 0 # 自然な向き
scrcpy --lock-video-orientation 1 # 90°反時計回り
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90°時計回り
```
この設定は録画の向きに影響します。
[ウィンドウは独立して回転することもできます](#回転)。
#### エンコーダ
いくつかのデバイスでは一つ以上のエンコーダを持ちます。それらのいくつかは、問題やクラッシュを引き起こします。別のエンコーダを選択することが可能です:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
利用可能なエンコーダをリストするために、無効なエンコーダ名を渡すことができます。エラー表示で利用可能なエンコーダを提供します。
```bash
scrcpy --encoder _
```
### 録画
ミラーリング中に画面の録画をすることが可能です:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
録画中にミラーリングを無効にするには:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# Ctrl+Cで録画を中断する
```
"スキップされたフレーム"は(パフォーマンス上の理由で)リアルタイムで表示されなくても録画されます。
フレームはデバイス上で _タイムスタンプされる_ ため [パケット遅延のバリエーション] は録画されたファイルに影響を与えません。
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation
### 接続
#### ワイヤレス
_Scrcpy_ はデバイスとの通信に`adb`を使用します。そして`adb`はTCP/IPを介しデバイスに[接続]することができます:
1. あなたのコンピュータと同じWi-Fiに接続します。
2. あなたのIPアドレスを取得します。設定 → 端末情報 → ステータス情報、もしくは、このコマンドを実行します:
```bash
adb shell ip route | awk '{print $9}'
```
3. あなたのデバイスでTCP/IPを介したadbを有効にします: `adb tcpip 5555`
4. あなたのデバイスの接続を外します。
5. あなたのデバイスに接続します:
`adb connect DEVICE_IP:5555` _(`DEVICE_IP`は置き換える)_
6. 通常通り`scrcpy`を実行します。
この方法はビットレートと解像度を減らすのにおそらく有用です:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # 短縮版
```
[接続]: https://developer.android.com/studio/command-line/adb.html#wireless
#### マルチデバイス
もし`adb devices`でいくつかのデバイスがリストされる場合、 _シリアルナンバー_ を指定する必要があります:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # 短縮版
```
デバイスがTCP/IPを介して接続されている場合:
```bash
scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # 短縮版
```
複数のデバイスに対して、複数の _scrcpy_ インスタンスを開始することができます。
#### デバイス接続での自動起動
[AutoAdb]を使用可能です:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSHトンネル
リモートデバイスに接続するため、ローカル`adb`クライアントからリモート`adb`サーバーへ接続することが可能です(同じバージョンの _adb_ プロトコルを使用している場合):
```bash
adb kill-server # 5037ポートのローカルadbサーバーを終了する
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# オープンしたままにする
```
他の端末から:
```bash
scrcpy
```
リモートポート転送の有効化を回避するためには、代わりに転送接続を強制することができます(`-R`の代わりに`-L`を使用することに注意):
```bash
adb kill-server # 5037ポートのローカルadbサーバーを終了する
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# オープンしたままにする
```
他の端末から:
```bash
scrcpy --force-adb-forward
```
ワイヤレス接続と同様に、クオリティを下げると便利な場合があります:
```
scrcpy -b2M -m800 --max-fps 15
```
### ウィンドウ構成
#### タイトル
ウィンドウのタイトルはデバイスモデルが初期値です。これは変更できます:
```bash
scrcpy --window-title 'My device'
```
#### 位置とサイズ
ウィンドウの位置とサイズの初期値を指定できます:
```bash
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
```
#### ボーダーレス
ウィンドウの装飾を無効化するには:
```bash
scrcpy --window-borderless
```
#### 常に画面のトップ
scrcpyの画面を常にトップにするには:
```bash
scrcpy --always-on-top
```
#### フルスクリーン
アプリケーションを直接フルスクリーンで開始できます:
```bash
scrcpy --fullscreen
scrcpy -f # 短縮版
```
フルスクリーンは、次のコマンドで動的に切り替えることができます <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>はデバイスに縦向きと横向きの切り替えを要求する(現在実行中のアプリで要求している向きをサポートしていない場合、拒否することがある)
- [`--lock-video-orientation`](#ビデオの向きをロックする)は、ミラーリングする向きを変更する(デバイスからPCへ送信される向き)。録画に影響します。
- `--rotation` (もしくは<kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)は、ウィンドウのコンテンツのみを回転します。これは表示にのみに影響し、録画には影響しません。
### 他のミラーリングオプション
#### Read-only リードオンリー
制御を無効にするには(デバイスと対話する全てのもの:入力キー、マウスイベント、ファイルのドラッグ&ドロップ):
```bash
scrcpy --no-control
scrcpy -n
```
#### ディスプレイ
いくつか利用可能なディスプレイがある場合、ミラーリングするディスプレイを選択できます:
```bash
scrcpy --display 1
```
ディスプレイIDのリストは次の方法で取得できます:
```
adb shell dumpsys display # search "mDisplayId=" in the output
```
セカンダリディスプレイは、デバイスが少なくともAndroid 10の場合にコントロール可能です。(それ以外ではリードオンリーでミラーリングされます)
#### 起動状態にする
デバイス接続時、少し遅れてからデバイスのスリープを防ぐには:
```bash
scrcpy --stay-awake
scrcpy -w
```
scrcpyが閉じられた時、初期状態に復元されます。
#### 画面OFF
コマンドラインオプションを使用することで、ミラーリングの開始時にデバイスの画面をOFFにすることができます:
```bash
scrcpy --turn-screen-off
scrcpy -S
```
もしくは、<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
```
(デバイス上で指を使った) _物理的な_ タッチのみ表示されることに注意してください。
#### スクリーンセーバー無効
初期状態では、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>を介して)ペーストすることは、デバイスのクリップボードにコンテンツをコピーします。結果としてどのAndoridアプリケーションもそのコンテンツを読み取ることができます。機密性の高いコンテンツ(例えばパスワードなど)をこの方法でペーストすることは避けてください。
プログラムでデバイスのクリップボードを設定した場合、一部のデバイスは期待どおりに動作しません。`--legacy-paste`オプションは、コンピュータのクリップボードテキストをキーイベントのシーケンスとして挿入するため(<kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>と同じ方法)、<kbd>Ctrl</kbd>+<kbd>v</kbd>と<kbd>MOD</kbd>+<kbd>v</kbd>の動作の変更を提供します。
#### ピンチしてズームする
"ピンチしてズームする"をシミュレートするには: <kbd>Ctrl</kbd>+_クリック&移動_
より正確にするには、左クリックボタンを押している間、<kbd>Ctrl</kbd>を押したままにします。左クリックボタンを離すまで、全てのマウスの動きは、(アプリでサポートされている場合)画面の中心を基準として、コンテンツを拡大縮小および回転します。
具体的には、scrcpyは画面の中央を反転した位置にある"バーチャルフィンガー"から追加のタッチイベントを生成します。
#### テキストインジェクション環境設定
テキストをタイプした時に生成される2種類の[イベント][textevents]があります:
- _key events_ はキーを押したときと離したことを通知します。
- _text events_ はテキストが入力されたことを通知します。
初期状態で、文字はキーイベントで挿入されるため、キーボードはゲームで期待通りに動作します(通常はWASDキー)。
しかし、これは[問題を引き起こす][prefertext]かもしれません。もしこのような問題が発生した場合は、この方法で回避できます:
```bash
scrcpy --prefer-text
```
(しかしこの方法はゲームのキーボードの動作を壊します)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### キーの繰り返し
初期状態では、キーの押しっぱなしは繰り返しのキーイベントを生成します。これらのイベントが使われない場合でも、この方法は一部のゲームでパフォーマンスの問題を引き起す可能性があります。
繰り返しのキーイベントの転送を回避するためには:
```bash
scrcpy --no-key-repeat
```
#### 右クリックと真ん中クリック
初期状態では、右クリックはバックの動作(もしくはパワーオン)を起こし、真ん中クリックではホーム画面へ戻ります。このショートカットを無効にし、代わりにデバイスへクリックを転送するには:
```bash
scrcpy --forward-all-clicks
```
### ファイルのドロップ
#### APKのインストール
APKをインストールするには、(`.apk`で終わる)APKファイルを _scrcpy_ の画面にドラッグ&ドロップします。
見た目のフィードバックはありません。コンソールにログが出力されます。
#### デバイスにファイルを送る
デバイスの`/sdcard/`ディレクトリにファイルを送るには、(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>でショートカット変更します。初期状態では、(left)<kbd>Alt</kbd>または(left)<kbd>Super</kbd>です。
これは`--shortcut-mod`で変更することができます。可能なキーは`lctrl`、`rctrl`、`lalt`、 `ralt`、 `lsuper`そして`rsuper`です。例えば:
```bash
# RCtrlをショートカットとして使用します
scrcpy --shortcut-mod=rctrl
# ショートカットにLCtrl+LAltまたはLSuperのいずれかを使用します
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> \| _ダブルクリック¹_
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd>
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
| `POWER`をクリック | <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カウンタ有効/無効(標準入出力上) | <kbd>MOD</kbd>+<kbd>i</kbd>
| ピンチしてズームする | <kbd>Ctrl</kbd>+_クリック&移動_
_¹黒い境界線を削除するため、境界線上でダブルクリック_
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
_³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]を参照してください。
[BUILD]: 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/

View File

@ -68,7 +68,9 @@ Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link].
윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) : 윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) :
해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다. 해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다.
- [README](README.md#windows) - [`scrcpy-win`][direct-win]
[direct-win]: https://github.com/Genymobile/scrcpy/blob/master/README.md#windows
[어플을 직접 설치][BUILD] 할 수도 있습니다. [어플을 직접 설치][BUILD] 할 수도 있습니다.

View File

@ -38,18 +38,6 @@ control it using keyboard and mouse.
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a> <a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Summary
- Linux: `apt install scrcpy`
- Windows: [download][direct-win64]
- macOS: `brew install scrcpy`
Build from sources: [BUILD] ([simplified process][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04):
@ -79,8 +67,9 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
You could also [build the app manually][BUILD] ([simplified You could also [build the app manually][BUILD] (don't worry, it's not that
process][BUILD_simple]). hard).
### Windows ### Windows
@ -422,7 +411,7 @@ _(left)_ and <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_.
Note that _scrcpy_ manages 3 different rotations: Note that _scrcpy_ manages 3 different rotations:
- <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait - <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait
and landscape (the current running app may refuse, if it does not support the and landscape (the current running app may refuse, if it does support the
requested orientation). requested orientation).
- [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring - [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring
orientation (the orientation of the video sent from the device to the orientation (the orientation of the video sent from the device to the
@ -669,7 +658,7 @@ There is no visual feedback, a log is printed to the console.
The target directory can be changed on start: The target directory can be changed on start:
```bash ```bash
scrcpy --push-target=/sdcard/Download/ scrcpy --push-target /sdcard/foo/bar/
``` ```
@ -765,6 +754,8 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
See [BUILD]. See [BUILD].
[BUILD]: BUILD.md
## Common issues ## Common issues
@ -808,10 +799,9 @@ Read the [developers page].
This README is available in other languages: This README is available in other languages:
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [日本語 (Japanese, `jp`) - v1.17](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.17](README.pt-br.md) - [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.16](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
Only this README file is guaranteed to be up-to-date. Only this README file is guaranteed to be up-to-date.

View File

@ -1,16 +1,16 @@
_Apenas o [README](README.md) original é garantido estar atualizado._ _Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.17) # scrcpy (v1.12.1)
Esta aplicação fornece exibição e controle de dispositivos Android conectados via 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_. USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso root.
Funciona em _GNU/Linux_, _Windows_ e _macOS_. Funciona em _GNU/Linux_, _Windows_ e _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
Foco em: Foco em:
- **leveza** (nativo, mostra apenas a tela do dispositivo) - **leveza** (Nativo, mostra apenas a tela do dispositivo)
- **performance** (30~60fps) - **performance** (30~60fps)
- **qualidade** (1920×1080 ou acima) - **qualidade** (1920×1080 ou acima)
- **baixa latência** ([35~70ms][lowlatency]) - **baixa latência** ([35~70ms][lowlatency])
@ -22,41 +22,36 @@ Foco em:
## Requisitos ## Requisitos
O dispositivo Android requer pelo menos a API 21 (Android 5.0). O Dispositivo Android requer pelo menos a API 21 (Android 5.0).
Tenha certeza de ter [ativado a depuração adb][enable-adb] no(s) seu(s) dispositivo(s).
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 [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
Em alguns dispositivos, você também precisa ativar [uma opção adicional][control] para
controlá-lo usando teclado e mouse. 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 [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
## Obter o app ## Obtendo o app
<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 ### Linux
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04): No Debian (_em testes_ e _sid_ por enquanto):
``` ```
apt install scrcpy apt install scrcpy
``` ```
Um pacote [Snap] está disponível: [`scrcpy`][snap-link]. O pacote [Snap] está disponível: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy [snap-link]: https://snapstats.org/snaps/scrcpy
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager) [snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
Para Fedora, um pacote [COPR] está disponível: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link]. Para Arch Linux, um pacote [AUR] está disponível: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository [AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
@ -67,19 +62,21 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Você também pode [compilar o app manualmente][BUILD] (não se preocupe, não é tão
difícil).
Você também pode [compilar a aplicação manualmente][BUILD] (não se preocupe, não é tão difícil).
### Windows ### Windows
Para Windows, por simplicidade, um arquivo pré-compilado com todas as dependências Para Windows, para simplicidade, um arquivo pré-compilado com todas as dependências
(incluindo `adb`) está disponível: (incluindo `adb`) está disponível:
- [README](README.md#windows) - [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
Também está disponível em [Chocolatey]: [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/ [Chocolatey]: https://chocolatey.org/
@ -97,12 +94,12 @@ scoop install adb # se você ainda não o tem
[Scoop]: https://scoop.sh [Scoop]: https://scoop.sh
Você também pode [compilar o app manualmente][BUILD]. Você também pode [compilar a aplicação manualmente][BUILD].
### macOS ### macOS
A aplicação está disponível em [Homebrew]. Apenas instale-a: A aplicação está disponível em [Homebrew]. Apenas a instale:
[Homebrew]: https://brew.sh/ [Homebrew]: https://brew.sh/
@ -110,22 +107,18 @@ A aplicação está disponível em [Homebrew]. Apenas instale-a:
brew install scrcpy brew install scrcpy
``` ```
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem: Você precisa do `adb`, acessível através do seu `PATH`. Se você ainda não o tem:
```bash ```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools brew cask install android-platform-tools
``` ```
Você também pode [compilar o app manualmente][BUILD]. Você também pode [compilar a aplicação manualmente][BUILD].
## Executar ## Executar
Conecte um dispositivo Android e execute: Plugue um dispositivo Android e execute:
```bash ```bash
scrcpy scrcpy
@ -141,87 +134,52 @@ scrcpy --help
### Configuração de captura ### Configuração de captura
#### Reduzir tamanho #### Redução de tamanho
Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para Algumas vezes, é útil espelhar um dispositivo Android em uma resolução menor para
aumentar a performance. aumentar performance.
Para limitar ambos (largura e altura) para algum valor (ex: 1024): Para limitar ambos(largura e altura) para algum valor (ex: 1024):
```bash ```bash
scrcpy --max-size 1024 scrcpy --max-size 1024
scrcpy -m 1024 # versão curta scrcpy -m 1024 # versão reduzida
``` ```
A outra dimensão é calculada para que a proporção do dispositivo seja preservada. A outra dimensão é calculada para que a proporção do dispositivo seja preservada.
Dessa forma, um dispositivo de 1920x1080 será espelhado em 1024x576. Dessa forma, um dispositivo em 1920x1080 será espelhado em 1024x576.
#### Mudar bit-rate #### Mudanças no bit-rate
O bit-rate padrão é 8 Mbps. Para mudar o bit-rate do vídeo (ex: para 2 Mbps): O Padrão de bit-rate é 8 mbps. Para mudar o bitrate do vídeo (ex: para 2 Mbps):
```bash ```bash
scrcpy --bit-rate 2M scrcpy --bit-rate 2M
scrcpy -b 2M # versão curta scrcpy -b 2M # versão reduzida
``` ```
#### Limitar frame rate #### Limitar frame rates
O frame rate de captura pode ser limitado: Em dispositivos com Android >= 10, a captura de frame rate pode ser limitada:
```bash ```bash
scrcpy --max-fps 15 scrcpy --max-fps 15
``` ```
Isso é oficialmente suportado desde o Android 10, mas pode funcionar em versões anteriores.
#### Cortar #### Cortar
A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela. A tela do dispositivo pode ser cortada para espelhar apenas uma parte da tela.
Isso é útil por exemplo, para espelhar apenas um olho do Oculus Go: Isso é útil por exemplo, ao espelhar apenas um olho do Oculus Go:
```bash ```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0) scrcpy --crop 1224:1440:0:0 # 1224x1440 no deslocamento (0,0)
``` ```
Se `--max-size` também for especificado, o redimensionamento é aplicado após o corte. Se `--max-size` também for especificado, redimensionar é aplicado após os cortes.
#### Travar orientação do vídeo
Para travar a orientação do espelhamento:
```bash
scrcpy --lock-video-orientation 0 # orientação natural
scrcpy --lock-video-orientation 1 # 90° sentido anti-horário
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° sentido horário
```
Isso afeta a orientação de gravação.
A [janela também pode ser rotacionada](#rotação) independentemente.
#### Encoder
Alguns dispositivos têm mais de um encoder, e alguns deles podem causar problemas ou
travar. É possível selecionar um encoder diferente:
```bash
scrcpy --encoder OMX.qcom.video.encoder.avc
```
Para listar os encoders disponíveis, você pode passar um nome de encoder inválido, o
erro dará os encoders disponíveis:
```bash
scrcpy --encoder _
```
### Gravando ### Gravando
É possível gravar a tela enquanto ocorre o espelhamento: É possível gravar a tela enquanto ocorre o espelhamento:
@ -236,84 +194,65 @@ Para desativar o espelhamento durante a gravação:
```bash ```bash
scrcpy --no-display --record file.mp4 scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv scrcpy -Nr file.mkv
# interrompa a gravação com Ctrl+C # 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 exibidos em tempo real (por "Frames pulados" são gravados, mesmo que não sejam mostrado em tempo real (por motivos de performance).
motivos de performance). Frames têm seu _horário carimbado_ no dispositivo, então [variação de atraso nos Frames tem seu _horário_ _carimbado_ no dispositivo, então [Variação de atraso nos pacotes] não impacta na gravação do arquivo.
pacotes][packet delay variation] não impacta o arquivo gravado.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [Variação de atraso de pacote]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Conexão ### Conexão
#### Sem fio #### Wireless/Sem fio
_Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se][connect] a um _Scrcpy_ usa `adb` para se comunicar com o dispositivo, e `adb` pode [conectar-se] à um dispositivo via TCP/IP:
dispositivo via TCP/IP:
1. Conecte o dispositivo no mesmo Wi-Fi do seu computador.
2. Pegue o endereço IP do seu dispositivo, em Configurações → Sobre o telefone → Status, ou
executando este comando:
```bash
adb shell ip route | awk '{print $9}'
```
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`. 3. Ative o adb via TCP/IP no seu dispositivo: `adb tcpip 5555`.
4. Desconecte seu dispositivo. 4. Desplugue seu dispositivo.
5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua `DEVICE_IP`)_. 5. Conecte-se ao seu dispositivo: `adb connect DEVICE_IP:5555` _(substitua o `DEVICE_IP`)_.
6. Execute `scrcpy` como de costume. 6. Execute `scrcpy` como de costume.
Pode ser útil diminuir o bit-rate e a resolução: Pode ser útil diminuir o bit-rate e a resolução:
```bash ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # versão curta scrcpy -b2M -m800 # versão reduzida
``` ```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless [conectar-se]: https://developer.android.com/studio/command-line/adb.html#wireless
#### Múltiplos dispositivos #### N-dispositivos
Se vários dispositivos são listados em `adb devices`, você deve especificar o _serial_: Se alguns dispositivos estão listados em `adb devices`, você precisa especificar o _serial_:
```bash ```bash
scrcpy --serial 0123456789abcdef scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # versão curta scrcpy -s 0123456789abcdef # versão reduzida
``` ```
Se o dispositivo está conectado via TCP/IP: Se o dispositivo está conectado via TCP/IP:
```bash ```bash
scrcpy --serial 192.168.0.1:5555 scrcpy --serial 192.168.0.1:5555
scrcpy -s 192.168.0.1:5555 # versão curta scrcpy -s 192.168.0.1:5555 # versão reduzida
``` ```
Você pode iniciar várias instâncias do _scrcpy_ para vários dispositivos. Você pode iniciar algumas instâncias do _scrcpy_ para alguns dispositivos.
#### Iniciar automaticamente quando dispositivo é conectado #### Conexão via SSH
Você pode usar [AutoAdb]: 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 ```bash
autoadb scrcpy -s '{}' adb kill-server # encerra o servidor local na 5037
```
[AutoAdb]: https://github.com/rom1v/autoadb
#### Túnel SSH
Para conectar-se a um dispositivo remoto, é possível conectar um cliente `adb` local a
um servidor `adb` remoto (contanto que eles usem a mesma versão do protocolo
_adb_):
```bash
adb kill-server # encerra o servidor adb local em 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# mantenha isso aberto # mantém isso aberto
``` ```
De outro terminal: De outro terminal:
@ -322,33 +261,17 @@ De outro terminal:
scrcpy scrcpy
``` ```
Para evitar ativar o encaminhamento de porta remota, você pode forçar uma conexão Igual para conexões sem fio, pode ser útil reduzir a qualidade:
de encaminhamento (note o `-L` em vez de `-R`):
```bash
adb kill-server # encerra o servidor adb local em 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# mantenha isso aberto
```
De outro terminal:
```bash
scrcpy --force-adb-forward
```
Igual a conexões sem fio, pode ser útil reduzir a qualidade:
``` ```
scrcpy -b2M -m800 --max-fps 15 scrcpy -b2M -m800 --max-fps 15
``` ```
### Configuração de janela ### Configurações de Janela
#### Título #### Título
Por padrão, o título da janela é o modelo do dispositivo. Isso pode ser mudado: Por padrão, o título da janela é o modelo do dispositivo. Isto pode ser mudado:
```bash ```bash
scrcpy --window-title 'Meu dispositivo' scrcpy --window-title 'Meu dispositivo'
@ -364,15 +287,15 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### Sem bordas #### Sem bordas
Para desativar decorações de janela: Para desativar decorações da janela:
```bash ```bash
scrcpy --window-borderless scrcpy --window-borderless
``` ```
#### Sempre no topo #### Sempre visível
Para manter a janela do scrcpy sempre no topo: Para manter a janela do scrcpy sempre visível:
```bash ```bash
scrcpy --always-on-top scrcpy --always-on-top
@ -384,117 +307,41 @@ A aplicação pode ser iniciada diretamente em tela cheia:
```bash ```bash
scrcpy --fullscreen scrcpy --fullscreen
scrcpy -f # versão curta scrcpy -f # versão reduzida
``` ```
Tela cheia pode ser alternada dinamicamente com <kbd>MOD</kbd>+<kbd>f</kbd>. Tela cheia pode ser alternada dinamicamente com `Ctrl`+`f`.
#### Rotação
A janela pode ser rotacionada:
```bash
scrcpy --rotation 1
```
Valores possíveis são:
- `0`: sem rotação
- `1`: 90 graus sentido anti-horário
- `2`: 180 graus
- `3`: 90 graus sentido horário
A rotação também pode ser mudada dinamicamente com <kbd>MOD</kbd>+<kbd>←</kbd>
_(esquerda)_ e <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_.
Note que _scrcpy_ controla 3 rotações diferentes:
- <kbd>MOD</kbd>+<kbd>r</kbd> requisita ao dispositivo para mudar entre retrato
e paisagem (a aplicação em execução pode se recusar, se ela não suporta a
orientação requisitada).
- [`--lock-video-orientation`](#travar-orientação-do-vídeo) muda a orientação de
espelhamento (a orientação do vídeo enviado pelo dispositivo para o
computador). Isso afeta a gravação.
- `--rotation` (ou <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
rotaciona apenas o conteúdo da janela. Isso afeta apenas a exibição, não a
gravação.
### Outras opções de espelhamento ### Outras opções de espelhamento
#### Apenas leitura #### Apenas leitura
Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, Para desativar controles (tudo que possa interagir com o dispositivo: teclas de entrada, eventos de mouse, arrastar e soltar arquivos):
eventos de mouse, arrastar e soltar arquivos):
```bash ```bash
scrcpy --no-control scrcpy --no-control
scrcpy -n scrcpy -n
``` ```
#### Display #### Desligar a tela
Se vários displays estão disponíveis, é possível selecionar o display para É possível desligar a tela do dispositivo durante o início do espelhamento com uma opção de linha de comando:
espelhar:
```bash
scrcpy --display 1
```
A lista de IDs dos displays pode ser obtida por:
```
adb shell dumpsys display # busca "mDisplayId=" na saída
```
O display secundário pode apenas ser controlado se o dispositivo roda pelo menos Android
10 (caso contrário é espelhado como apenas leitura).
#### Permanecer ativo
Para evitar que o dispositivo seja suspenso após um delay quando o dispositivo é conectado:
```bash
scrcpy --stay-awake
scrcpy -w
```
O estado inicial é restaurado quando o scrcpy é fechado.
#### Desligar tela
É possível desligar a tela do dispositivo durante o início do espelhamento com uma
opção de linha de comando:
```bash ```bash
scrcpy --turn-screen-off scrcpy --turn-screen-off
scrcpy -S scrcpy -S
``` ```
Ou apertando <kbd>MOD</kbd>+<kbd>o</kbd> a qualquer momento. Ou apertando `Ctrl`+`o` durante qualquer momento.
Para ligar novamente, pressione <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>. Para ligar novamente, pressione `POWER` (ou `Ctrl`+`p`).
No Android, o botão de `POWER` sempre liga a tela. Por conveniência, se #### Frames expirados de renderização
`POWER` é enviado via scrcpy (via clique-direito ou <kbd>MOD</kbd>+<kbd>p</kbd>), ele
forçará a desligar a tela após um delay pequeno (numa base de melhor esforço).
O botão `POWER` físico ainda causará a tela ser ligada.
Também pode ser útil evitar que o dispositivo seja suspenso: Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado disponível e descarta o anterior.
```bash Para forçar a renderização de todos os frames ( com o custo de aumento de latência), use:
scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### Renderizar frames expirados
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado
disponível, e descarta o anterior.
Para forçar a renderização de todos os frames (com o custo de um possível aumento de
latência), use:
```bash ```bash
scrcpy --render-expired-frames scrcpy --render-expired-frames
@ -502,13 +349,11 @@ scrcpy --render-expired-frames
#### Mostrar toques #### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo Para apresentações, pode ser útil mostrar toques físicos(dispositivo físico).
físico).
Android fornece esta funcionalidade nas _Opções do desenvolvedor_. Android fornece esta funcionalidade nas _Opções do Desenvolvedor_.
_Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e restaurar o _Scrcpy_ fornece esta opção de ativar esta funcionalidade no início e desativar no encerramento:
valor inicial no encerramento:
```bash ```bash
scrcpy --show-touches scrcpy --show-touches
@ -518,137 +363,59 @@ scrcpy -t
Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo). Note que isto mostra apenas toques _físicos_ (com o dedo no dispositivo).
#### Desativar descanso de tela
Por padrão, scrcpy não evita que o descanso de tela rode no computador.
Para desativá-lo:
```bash
scrcpy --disable-screensaver
```
### Controle de entrada ### Controle de entrada
#### Rotacionar a tela do dispositivo #### Rotacionar a tela do dispositivo
Pressione <kbd>MOD</kbd>+<kbd>r</kbd> para mudar entre os modos retrato e Pressione `Ctrl`+`r` para mudar entre os modos Retrato e Paisagem.
paisagem.
Note que só será rotacionado se a aplicação em primeiro plano suportar a Note que só será rotacionado se a aplicação em primeiro plano tiver suporte para o modo requisitado.
orientação requisitada.
#### Copiar-colar #### Copiar-Colar
Sempre que a área de transferência do Android muda, é automaticamente sincronizada com a É possível sincronizar áreas de transferência entre computador e o dispositivo,
área de transferência do computador. para ambas direções:
Qualquer atalho com <kbd>Ctrl</kbd> é encaminhado para o dispositivo. Em particular: - `Ctrl`+`c` copia a área de transferência do dispositivo para a área de trasferência do computador;
- <kbd>Ctrl</kbd>+<kbd>c</kbd> tipicamente copia - `Ctrl`+`Shift`+`v` copia a área de transferência do computador para a área de transferência do dispositivo;
- <kbd>Ctrl</kbd>+<kbd>x</kbd> tipicamente recorta - `Ctrl`+`v` _cola_ a área de transferência do computador como uma sequência de eventos de texto (mas
- <kbd>Ctrl</kbd>+<kbd>v</kbd> tipicamente cola (após a sincronização de área de transferência quebra caracteres não-ASCII).
computador-para-dispositivo)
Isso tipicamente funciona como esperado. #### Preferências de injeção de texto
O comportamento de fato depende da aplicação ativa, no entanto. Por exemplo, Existe dois tipos de [eventos][textevents] gerados ao digitar um texto:
_Termux_ envia SIGINT com <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ - _eventos de teclas_, sinalizando que a tecla foi pressionada ou solta;
compõe uma nova mensagem.
Para copiar, recortar e colar em tais casos (mas apenas suportado no Android >= 7):
- <kbd>MOD</kbd>+<kbd>c</kbd> injeta `COPY`
- <kbd>MOD</kbd>+<kbd>x</kbd> injeta `CUT`
- <kbd>MOD</kbd>+<kbd>v</kbd> injeta `PASTE` (após a sincronização de área de transferência
computador-para-dispositivo)
Em adição, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite injetar o
texto da área de transferência do computador como uma sequência de eventos de tecla. Isso é útil quando o
componente não aceita colar texto (por exemplo no _Termux_), mas pode
quebrar conteúdo não-ASCII.
**ADVERTÊNCIA:** Colar a área de transferência do computador para o dispositivo (tanto via
<kbd>Ctrl</kbd>+<kbd>v</kbd> quanto <kbd>MOD</kbd>+<kbd>v</kbd>) copia o conteúdo
para a área de transferência do dispositivo. Como consequência, qualquer aplicação Android pode ler
o seu conteúdo. Você deve evitar colar conteúdo sensível (como senhas) dessa
forma.
Alguns dispositivos não se comportam como esperado quando a área de transferência é definida
programaticamente. Uma opção `--legacy-paste` é fornecida para mudar o comportamento
de <kbd>Ctrl</kbd>+<kbd>v</kbd> e <kbd>MOD</kbd>+<kbd>v</kbd> para que eles
também injetem o texto da área de transferência do computador como uma sequência de eventos de tecla (da mesma
forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
#### Pinçar para dar zoom
Para simular "pinçar para dar zoom": <kbd>Ctrl</kbd>+_clicar-e-mover_.
Mais precisamente, segure <kbd>Ctrl</kbd> enquanto pressiona o botão de clique-esquerdo. Até que
o botão de clique-esquerdo seja liberado, todos os movimentos do mouse ampliar e rotacionam o
conteúdo (se suportado pelo app) relativo ao centro da tela.
Concretamente, scrcpy gera eventos adicionais de toque de um "dedo virtual" em
uma posição invertida em relação ao centro da tela.
#### Preferência de injeção de texto
Existem dois tipos de [eventos][textevents] gerados ao digitar um texto:
- _eventos de tecla_, sinalizando que a tecla foi pressionada ou solta;
- _eventos de texto_, sinalizando que o texto foi inserido. - _eventos de texto_, sinalizando que o texto foi inserido.
Por padrão, letras são injetadas usando eventos de tecla, assim o teclado comporta-se Por padrão, letras são injetadas usando eventos de teclas, assim teclados comportam-se
como esperado em jogos (normalmente para teclas WASD). como esperado em jogos (normalmente para tecladas WASD)
Mas isso pode [causar problemas][prefertext]. Se você encontrar tal problema, você Mas isto pode [causar problemas][prefertext]. Se você encontrar tal problema,
pode evitá-lo com: pode evitá-lo usando:
```bash ```bash
scrcpy --prefer-text scrcpy --prefer-text
``` ```
(mas isso vai quebrar o comportamento do teclado em jogos) (mas isto vai quebrar o comportamento do teclado em jogos)
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Repetir tecla ### Transferência de arquivo
Por padrão, segurar uma tecla gera eventos de tecla repetidos. Isso pode causar
problemas de performance em alguns jogos, onde esses eventos são inúteis de qualquer forma.
Para evitar o encaminhamento eventos de tecla repetidos:
```bash
scrcpy --no-key-repeat
```
#### Clique-direito e clique-do-meio
Por padrão, clique-direito dispara BACK (ou POWER) e clique-do-meio dispara
HOME. Para desabilitar esses atalhos e encaminhar os cliques para o dispositivo:
```bash
scrcpy --forward-all-clicks
```
### Soltar arquivo
#### Instalar APK #### Instalar APK
Para instalar um APK, arraste e solte o arquivo APK (com extensão `.apk`) na janela Para instalar um APK, arraste e solte o arquivo APK(com extensão `.apk`) na janela _scrcpy_.
_scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
#### Enviar arquivo para dispositivo #### Enviar arquivo para o dispositivo
Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a Para enviar um arquivo para o diretório `/sdcard/` no dispositivo, arraste e solte um arquivo não APK para a janela do
janela do _scrcpy_. _scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
@ -661,73 +428,45 @@ scrcpy --push-target /sdcard/foo/bar/
### Encaminhamento de áudio ### Encaminhamento de áudio
Áudio não é encaminhado pelo _scrcpy_. Use [sndcpy]. Áudio não é encaminhando pelo _scrcpy_. Use [USBaudio] (Apenas linux).
Também veja [issue #14]. Também veja [issue #14].
[sndcpy]: https://github.com/rom1v/sndcpy [USBaudio]: https://github.com/rom1v/usbaudio
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 [issue #14]: https://github.com/Genymobile/scrcpy/issues/14
## Atalhos ## Atalhos
Na lista a seguir, <kbd>MOD</kbd> é o modificador de atalho. Por padrão, é | Ação | Atalho | Atalho (macOS)
<kbd>Alt</kbd> (esquerdo) ou <kbd>Super</kbd> (esquerdo). | ------------------------------------------------------------- |:------------------------------- |:-----------------------------
| Alternar para modo de tela cheia | `Ctrl`+`f` | `Cmd`+`f`
Ele pode ser mudado usando `--shortcut-mod`. Possíveis teclas são `lctrl`, `rctrl`, | Redimensionar janela para pixel-perfect(Escala 1:1) | `Ctrl`+`g` | `Cmd`+`g`
`lalt`, `ralt`, `lsuper` e `rsuper`. Por exemplo: | 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_
```bash | Clicar em `BACK` | `Ctrl`+`b` \| _Clique-direito²_ | `Cmd`+`b` \| _Clique-direito²_
# usar RCtrl para atalhos | Clicar em `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
scrcpy --shortcut-mod=rctrl | Clicar em `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Clicar em `VOLUME_UP` | `Ctrl`+`↑` _(cima)_ | `Cmd`+`↑` _(cima)_
# usar tanto LCtrl+LAlt quanto LSuper para atalhos | Clicar em `VOLUME_DOWN` | `Ctrl`+`↓` _(baixo)_ | `Cmd`+`↓` _(baixo)_
scrcpy --shortcut-mod=lctrl+lalt,lsuper | Clicar em `POWER` | `Ctrl`+`p` | `Cmd`+`p`
``` | Ligar | _Clique-direito²_ | _Clique-direito²_
| Desligar a tela do dispositivo | `Ctrl`+`o` | `Cmd`+`o`
_<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>._ | Rotacionar tela do dispositivo | `Ctrl`+`r` | `Cmd`+`r`
| Expandir painel de notificação | `Ctrl`+`n` | `Cmd`+`n`
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) | 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`
| Ação | Atalho | 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`
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd> | Ativar/desativar contador de FPS(Frames por segundo) | `Ctrl`+`i` | `Cmd`+`i`
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
| Redimensionar janela para 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo¹_
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Clicar em `MENU` (desbloquear tela | <kbd>MOD</kbd>+<kbd>m</kbd>
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Ligar | _Clique-direito²_
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd>
| Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_clicar-e-mover_
_¹Clique-duplo em bordas pretas para removê-las._ _¹Clique-duplo em bordas pretas para removê-las._
_²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._ _²Botão direito liga a tela se ela estiver desligada, clique BACK para o contrário._
_³Apenas em Android >= 7._
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
tratados pela aplicação ativa.
## Caminhos personalizados ## Caminhos personalizados
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente Para usar um binário específico _adb_, configure seu caminho na variável de ambiente `ADB`:
`ADB`:
ADB=/caminho/para/adb scrcpy ADB=/caminho/para/adb scrcpy
@ -739,7 +478,7 @@ Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
## Por quê _scrcpy_? ## Por quê _scrcpy_?
Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet]. Um colega me desafiou a encontrar um nome impronunciável como [gnirehtet].
[`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een. [`strcpy`] copia uma **str**ing; `scrcpy` copia uma **scr**een.
@ -756,12 +495,12 @@ Veja [BUILD].
## Problemas comuns ## Problemas comuns
Veja o [FAQ](FAQ.md). Veja [FAQ](FAQ.md).
## Desenvolvedores ## Desenvolvedores
Leia a [página dos desenvolvedores][developers page]. Leia a [developers page].
[developers page]: DEVELOP.md [developers page]: DEVELOP.md

View File

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

View File

@ -80,7 +80,10 @@ apt install scrcpy
為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包: 為了保持簡單Windows 用戶可以下載一個包含所有必需軟體 (包含 `adb`) 的壓縮包:
- [README](README.md#windows) - [`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] 上也可以下載:

View File

@ -1,7 +1,8 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb.c',
'src/cli.c', 'src/cli.c',
'src/command.c', 'src/compat.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
@ -21,9 +22,23 @@ src = [
'src/tiny_xpm.c', 'src/tiny_xpm.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/net.c', 'src/util/net.c',
'src/util/str_util.c' 'src/util/process.c',
'src/util/str_util.c',
'src/util/thread.c',
] ]
if host_machine.system() == 'windows'
src += [ 'src/sys/win/process.c' ]
else
src += [ 'src/sys/unix/process.c' ]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')
# native build # native build
@ -37,8 +52,6 @@ if not get_option('crossbuild_windows')
else else
# cross-compile mingw32 build (from Linux to Windows) # cross-compile mingw32 build (from Linux to Windows)
cc = meson.get_compiler('c')
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' 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_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
@ -73,17 +86,19 @@ else
endif endif
cc = meson.get_compiler('c')
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
src += [ 'src/sys/win/command.c' ]
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
else
src += [ 'src/sys/unix/command.c' ]
endif endif
conf = configuration_data() conf = configuration_data()
foreach f : check_functions
if cc.has_function(f)
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# expose the build type # expose the build type
conf.set('NDEBUG', get_option('buildtype') != 'debug') conf.set('NDEBUG', get_option('buildtype') != 'debug')

View File

@ -213,25 +213,25 @@ Set a custom window title.
.BI "\-\-window\-x " value .BI "\-\-window\-x " value
Set the initial window horizontal position. Set the initial window horizontal position.
Default is "auto". Default is "auto".\n
.TP .TP
.BI "\-\-window\-y " value .BI "\-\-window\-y " value
Set the initial window vertical position. Set the initial window vertical position.
Default is "auto". Default is "auto".\n
.TP .TP
.BI "\-\-window\-width " value .BI "\-\-window\-width " value
Set the initial window width. Set the initial window width.
Default is 0 (automatic). Default is 0 (automatic).\n
.TP .TP
.BI "\-\-window\-height " value .BI "\-\-window\-height " value
Set the initial window height. Set the initial window height.
Default is 0 (automatic). Default is 0 (automatic).\n
.SH SHORTCUTS .SH SHORTCUTS

View File

@ -1,12 +1,10 @@
#include "command.h" #include "adb.h"
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "common.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -70,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"}, {"pacman", "pacman -S android-tools"},
}; };
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (cmd_search(pkg_managers[i].binary)) { if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return; return;
} }
@ -118,7 +116,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd, &process); enum process_result r = process_execute(cmd, &process);
if (r != PROCESS_SUCCESS) { if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd); show_adb_err_msg(r, cmd);
return PROCESS_NONE; return PROCESS_NONE;
@ -175,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
} }
remote = strquote(remote); remote = strquote(remote);
if (!remote) { if (!remote) {
SDL_free((void *) local); free((void *) local);
return PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
@ -184,8 +182,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
SDL_free((void *) remote); free((void *) remote);
SDL_free((void *) local); free((void *) local);
#endif #endif
return proc; return proc;
@ -206,26 +204,8 @@ adb_install(const char *serial, const char *local) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
SDL_free((void *) local); free((void *) local);
#endif #endif
return proc; return proc;
} }
bool
process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

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

@ -6,7 +6,6 @@
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -478,14 +477,14 @@ parse_port_range(const char *s, struct sc_port_range *port_range) {
} }
static bool static bool
parse_display_id(const char *s, uint16_t *display_id) { parse_display_id(const char *s, uint32_t *display_id) {
long value; long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id"); bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id");
if (!ok) { if (!ok) {
return false; return false;
} }
*display_id = (uint16_t) value; *display_id = (uint32_t) value;
return true; return true;
} }

View File

@ -1,9 +1,10 @@
#ifndef SCRCPY_CLI_H #ifndef SCRCPY_CLI_H
#define SCRCPY_CLI_H #define SCRCPY_CLI_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "config.h"
#include "scrcpy.h" #include "scrcpy.h"
struct scrcpy_cli_args { struct scrcpy_cli_args {

View File

@ -1,35 +1,11 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include <stdint.h>
#include "config.h" #include "config.h"
#include "compat.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
struct size {
uint16_t width;
uint16_t height;
};
struct point {
int32_t x;
int32_t y;
};
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 size screen_size;
struct point point;
};
struct port_range {
uint16_t first;
uint16_t last;
};
#endif #endif

14
app/src/compat.c Normal file
View File

@ -0,0 +1,14 @@
#include "compat.h"
#include "config.h"
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
return dup;
}
#endif

View File

@ -1,6 +1,14 @@
#ifndef COMPAT_H #ifndef COMPAT_H
#define COMPAT_H #define COMPAT_H
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavcodec/version.h>
#include <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.h>
@ -48,4 +56,8 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif #endif
#ifdef HAVE_STRDUP
char *strdup(const char *s);
#endif
#endif #endif

View File

@ -1,9 +1,9 @@
#include "control_msg.h" #include "control_msg.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -94,10 +94,10 @@ void
control_msg_destroy(struct control_msg *msg) { control_msg_destroy(struct control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
SDL_free(msg->inject_text.text); free(msg->inject_text.text);
break; break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
SDL_free(msg->set_clipboard.text); free(msg->set_clipboard.text);
break; break;
default: default:
// do nothing // do nothing

View File

@ -1,14 +1,15 @@
#ifndef CONTROLMSG_H #ifndef CONTROLMSG_H
#define CONTROLMSG_H #define CONTROLMSG_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
#include "common.h" #include "coords.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
@ -49,7 +50,7 @@ struct control_msg {
enum android_metastate metastate; enum android_metastate metastate;
} inject_keycode; } inject_keycode;
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by free()
} inject_text; } inject_text;
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
@ -64,7 +65,7 @@ struct control_msg {
int32_t vscroll; int32_t vscroll;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by free()
bool paste; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {

View File

@ -2,26 +2,27 @@
#include <assert.h> #include <assert.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, socket_t control_socket) { controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
if (!receiver_init(&controller->receiver, control_socket)) { bool ok = receiver_init(&controller->receiver, control_socket);
if (!ok) {
return false; return false;
} }
if (!(controller->mutex = SDL_CreateMutex())) { ok = sc_mutex_init(&controller->mutex);
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
return false; return false;
} }
if (!(controller->msg_cond = SDL_CreateCond())) { ok = sc_cond_init(&controller->msg_cond);
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
SDL_DestroyMutex(controller->mutex); sc_mutex_destroy(&controller->mutex);
return false; return false;
} }
@ -33,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void void
controller_destroy(struct controller *controller) { controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->msg_cond); sc_cond_destroy(&controller->msg_cond);
SDL_DestroyMutex(controller->mutex); sc_mutex_destroy(&controller->mutex);
struct control_msg msg; struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
@ -47,13 +48,13 @@ controller_destroy(struct controller *controller) {
bool bool
controller_push_msg(struct controller *controller, controller_push_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
mutex_lock(controller->mutex); sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue); bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg); bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) { if (was_empty) {
cond_signal(controller->msg_cond); sc_cond_signal(&controller->msg_cond);
} }
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
return res; return res;
} }
@ -61,12 +62,12 @@ static bool
process_msg(struct controller *controller, process_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
int length = control_msg_serialize(msg, serialized_msg); size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
int w = net_send_all(controller->control_socket, serialized_msg, length); int w = net_send_all(controller->control_socket, serialized_msg, length);
return w == length; return (size_t) w == length;
} }
static int static int
@ -74,20 +75,20 @@ run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
mutex_lock(controller->mutex); sc_mutex_lock(&controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) { while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
cond_wait(controller->msg_cond, controller->mutex); sc_cond_wait(&controller->msg_cond, &controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
// stop immediately, do not process further msgs // stop immediately, do not process further msgs
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
break; break;
} }
struct control_msg msg; struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg); control_msg_destroy(&msg);
@ -103,16 +104,16 @@ bool
controller_start(struct controller *controller) { controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", bool ok = sc_thread_create(&controller->thread, run_controller,
controller); "controller", controller);
if (!controller->thread) { if (!ok) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return false;
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
controller_stop(controller); controller_stop(controller);
SDL_WaitThread(controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
return false; return false;
} }
@ -121,14 +122,14 @@ controller_start(struct controller *controller) {
void void
controller_stop(struct controller *controller) { controller_stop(struct controller *controller) {
mutex_lock(controller->mutex); sc_mutex_lock(&controller->mutex);
controller->stopped = true; controller->stopped = true;
cond_signal(controller->msg_cond); sc_cond_signal(&controller->msg_cond);
mutex_unlock(controller->mutex); sc_mutex_unlock(&controller->mutex);
} }
void void
controller_join(struct controller *controller) { controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL); sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@ -1,23 +1,23 @@
#ifndef CONTROLLER_H #ifndef CONTROLLER_H
#define CONTROLLER_H #define CONTROLLER_H
#include <stdbool.h> #include "common.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <stdbool.h>
#include "config.h"
#include "control_msg.h" #include "control_msg.h"
#include "receiver.h" #include "receiver.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct controller { struct controller {
socket_t control_socket; socket_t control_socket;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *msg_cond; sc_cond msg_cond;
bool stopped; bool stopped;
struct control_msg_queue queue; struct control_msg_queue queue;
struct receiver receiver; struct receiver receiver;

24
app/src/coords.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef SC_COORDS
#define SC_COORDS
#include <stdint.h>
struct size {
uint16_t width;
uint16_t height;
};
struct point {
int32_t x;
int32_t y;
};
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 size screen_size;
struct point point;
};
#endif

View File

@ -3,12 +3,8 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "compat.h"
#include "events.h" #include "events.h"
#include "recorder.h" #include "recorder.h"
#include "video_buffer.h" #include "video_buffer.h"

View File

@ -1,11 +1,11 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h"
struct video_buffer; struct video_buffer;
struct decoder { struct decoder {

View File

@ -1,6 +1,5 @@
#include "device.h" #include "device.h"
#include "config.h"
#include "util/log.h" #include "util/log.h"
bool bool

View File

@ -1,10 +1,11 @@
#ifndef DEVICE_H #ifndef DEVICE_H
#define DEVICE_H #define DEVICE_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "config.h" #include "coords.h"
#include "common.h"
#include "util/net.h" #include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64 #define DEVICE_NAME_FIELD_LENGTH 64

View File

@ -1,8 +1,8 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
@ -21,7 +21,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
if (clipboard_len > len - 5) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = SDL_malloc(clipboard_len + 1); char *text = malloc(clipboard_len + 1);
if (!text) { if (!text) {
LOGW("Could not allocate text for clipboard"); LOGW("Could not allocate text for clipboard");
return -1; return -1;
@ -43,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
void void
device_msg_destroy(struct device_msg *msg) { device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
SDL_free(msg->clipboard.text); free(msg->clipboard.text);
} }
} }

View File

@ -1,12 +1,12 @@
#ifndef DEVICEMSG_H #ifndef DEVICEMSG_H
#define DEVICEMSG_H #define DEVICEMSG_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
// type: 1 byte; length: 4 bytes // type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
@ -19,7 +19,7 @@ struct device_msg {
enum device_msg_type type; enum device_msg_type type;
union { union {
struct { struct {
char *text; // owned, to be freed by SDL_free() char *text; // owned, to be freed by free()
} clipboard; } clipboard;
}; };
}; };

View File

@ -1,7 +1,5 @@
#include "event_converter.h" #include "event_converter.h"
#include "config.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true #define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false #define FAIL default: return false

View File

@ -1,10 +1,11 @@
#ifndef CONVERT_H #ifndef CONVERT_H
#define CONVERT_H #define CONVERT_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "config.h"
#include "control_msg.h" #include "control_msg.h"
bool bool

View File

@ -3,16 +3,14 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "config.h" #include "adb.h"
#include "command.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/" #define DEFAULT_PUSH_TARGET "/sdcard/"
static void static void
file_handler_request_destroy(struct file_handler_request *req) { file_handler_request_destroy(struct file_handler_request *req) {
SDL_free(req->file); free(req->file);
} }
bool bool
@ -21,21 +19,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
cbuf_init(&file_handler->queue); cbuf_init(&file_handler->queue);
if (!(file_handler->mutex = SDL_CreateMutex())) { bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false; return false;
} }
if (!(file_handler->event_cond = SDL_CreateCond())) { ok = sc_cond_init(&file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
if (serial) { if (serial) {
file_handler->serial = SDL_strdup(serial); file_handler->serial = strdup(serial);
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Could not strdup serial"); LOGW("Could not strdup serial");
SDL_DestroyCond(file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
} else { } else {
@ -55,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
SDL_free(file_handler->serial); free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) { while (cbuf_take(&file_handler->queue, &req)) {
@ -93,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
.file = file, .file = file,
}; };
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue); bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req); bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
cond_signal(file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
} }
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
return res; return res;
} }
@ -108,14 +108,14 @@ run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex); sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
// stop immediately, do not process further events // stop immediately, do not process further events
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
break; break;
} }
struct file_handler_request req; struct file_handler_request req;
@ -133,16 +133,16 @@ run_file_handler(void *data) {
file_handler->push_target); file_handler->push_target);
} }
file_handler->current_process = process; file_handler->current_process = process;
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install")) { if (process_check_success(process, "adb install", false)) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
if (process_check_success(process, "adb push")) { if (process_check_success(process, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file, LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target); file_handler->push_target);
} else { } else {
@ -151,6 +151,14 @@ run_file_handler(void *data) {
} }
} }
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); file_handler_request_destroy(&req);
} }
return 0; return 0;
@ -160,9 +168,9 @@ bool
file_handler_start(struct file_handler *file_handler) { file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
file_handler); "file_handler", file_handler);
if (!file_handler->thread) { if (!ok) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return false;
} }
@ -172,20 +180,18 @@ file_handler_start(struct file_handler *file_handler) {
void void
file_handler_stop(struct file_handler *file_handler) { file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
cond_signal(file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) { if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate install process"); LOGW("Could not terminate push/install process");
} }
cmd_simple_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
} }
mutex_unlock(file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
} }
void void
file_handler_join(struct file_handler *file_handler) { file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL); sc_thread_join(&file_handler->thread, NULL);
} }

View File

@ -1,13 +1,13 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HANDLER_H #define FILE_HANDLER_H
#include <stdbool.h> #include "common.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include <stdbool.h>
#include "command.h"
#include "adb.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@ -24,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler { struct file_handler {
char *serial; char *serial;
const char *push_target; const char *push_target;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *event_cond; sc_cond event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
process_t current_process; process_t current_process;
@ -49,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
void void
file_handler_join(struct file_handler *file_handler); file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it // take ownership of file, and will free() it
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,

View File

@ -3,26 +3,24 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000 #define FPS_COUNTER_INTERVAL_MS 1000
bool bool
fps_counter_init(struct fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
counter->mutex = SDL_CreateMutex(); bool ok = sc_mutex_init(&counter->mutex);
if (!counter->mutex) { if (!ok) {
return false; return false;
} }
counter->state_cond = SDL_CreateCond(); ok = sc_cond_init(&counter->state_cond);
if (!counter->state_cond) { if (!ok) {
SDL_DestroyMutex(counter->mutex); sc_mutex_destroy(&counter->mutex);
return false; return false;
} }
counter->thread = NULL; counter->thread_started = false;
atomic_init(&counter->started, 0); atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started // no need to initialize the other fields, they are unused until started
@ -31,8 +29,8 @@ fps_counter_init(struct fps_counter *counter) {
void void
fps_counter_destroy(struct fps_counter *counter) { fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond); sc_cond_destroy(&counter->state_cond);
SDL_DestroyMutex(counter->mutex); sc_mutex_destroy(&counter->mutex);
} }
static inline bool static inline bool
@ -78,10 +76,10 @@ static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct fps_counter *counter = data; struct fps_counter *counter = data;
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !is_started(counter)) {
cond_wait(counter->state_cond, counter->mutex); sc_cond_wait(&counter->state_cond, &counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
@ -91,32 +89,35 @@ run_fps_counter(void *data) {
uint32_t remaining = counter->next_timestamp - now; uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining); sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
} }
} }
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
return 0; return 0;
} }
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS; counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
set_started(counter, true); set_started(counter, true);
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock // counter->thread_started and counter->thread are always accessed from the
if (!counter->thread) { // same thread, no need to lock
counter->thread = if (!counter->thread_started) {
SDL_CreateThread(run_fps_counter, "fps counter", counter); bool ok = sc_thread_create(&counter->thread, run_fps_counter,
if (!counter->thread) { "fps counter", counter);
if (!ok) {
LOGE("Could not start FPS counter thread"); LOGE("Could not start FPS counter thread");
return false; return false;
} }
counter->thread_started = true;
} }
return true; return true;
@ -125,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) {
void void
fps_counter_stop(struct fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
} }
bool bool
@ -135,21 +136,21 @@ fps_counter_is_started(struct fps_counter *counter) {
void void
fps_counter_interrupt(struct fps_counter *counter) { fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) { if (!counter->thread_started) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
counter->interrupted = true; counter->interrupted = true;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
// wake up blocking wait // wake up blocking wait
cond_signal(counter->state_cond); sc_cond_signal(&counter->state_cond);
} }
void void
fps_counter_join(struct fps_counter *counter) { fps_counter_join(struct fps_counter *counter) {
if (counter->thread) { if (counter->thread_started) {
SDL_WaitThread(counter->thread, NULL); sc_thread_join(&counter->thread, NULL);
} }
} }
@ -159,11 +160,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
} }
void void
@ -172,9 +173,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
return; return;
} }
mutex_lock(counter->mutex); sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
mutex_unlock(counter->mutex); sc_mutex_unlock(&counter->mutex);
} }

View File

@ -1,18 +1,20 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include "common.h"
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include "util/thread.h"
struct fps_counter { struct fps_counter {
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *state_cond; sc_cond state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex // atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily // if the FPS counter is disabled, we don't want to lock unnecessarily

View File

@ -3,9 +3,7 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_keycode.h>
#include "config.h"
#include "event_converter.h" #include "event_converter.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
@ -191,13 +189,20 @@ set_device_clipboard(struct controller *controller, bool paste) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text; msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
SDL_free(text); free(text_dup);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
} }
} }
@ -243,11 +248,18 @@ clipboard_paste(struct controller *controller) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text; msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
SDL_free(text); free(text_dup);
LOGW("Could not request 'paste clipboard'"); LOGW("Could not request 'paste clipboard'");
} }
} }
@ -292,13 +304,13 @@ input_manager_process_text_input(struct input_manager *im,
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = SDL_strdup(event->text); msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) { if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!controller_push_msg(im->controller, &msg)) { if (!controller_push_msg(im->controller, &msg)) {
SDL_free(msg.inject_text.text); free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
} }

View File

@ -1,12 +1,12 @@
#ifndef INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define INPUTMANAGER_H #define INPUTMANAGER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "scrcpy.h" #include "scrcpy.h"

View File

@ -1,5 +1,7 @@
#include "scrcpy.h" #include "scrcpy.h"
#include "common.h"
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
@ -7,9 +9,7 @@
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "cli.h" #include "cli.h"
#include "compat.h"
#include "util/log.h" #include "util/log.h"
static void static void

View File

@ -1,11 +1,11 @@
#ifndef SC_OPENGL_H #ifndef SC_OPENGL_H
#define SC_OPENGL_H #define SC_OPENGL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_opengl.h> #include <SDL2/SDL_opengl.h>
#include "config.h"
struct sc_opengl { struct sc_opengl {
const char *version; const char *version;
bool is_opengles; bool is_opengles;

View File

@ -3,14 +3,13 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_clipboard.h> #include <SDL2/SDL_clipboard.h>
#include "config.h"
#include "device_msg.h" #include "device_msg.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, socket_t control_socket) { receiver_init(struct receiver *receiver, socket_t control_socket) {
if (!(receiver->mutex = SDL_CreateMutex())) { bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
@ -19,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
void void
receiver_destroy(struct receiver *receiver) { receiver_destroy(struct receiver *receiver) {
SDL_DestroyMutex(receiver->mutex); sc_mutex_destroy(&receiver->mutex);
} }
static void static void
@ -102,8 +101,9 @@ bool
receiver_start(struct receiver *receiver) { receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread"); LOGD("Starting receiver thread");
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver); bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
if (!receiver->thread) { receiver);
if (!ok) {
LOGC("Could not start receiver thread"); LOGC("Could not start receiver thread");
return false; return false;
} }
@ -113,5 +113,5 @@ receiver_start(struct receiver *receiver) {
void void
receiver_join(struct receiver *receiver) { receiver_join(struct receiver *receiver) {
SDL_WaitThread(receiver->thread, NULL); sc_thread_join(&receiver->thread, NULL);
} }

View File

@ -1,19 +1,19 @@
#ifndef RECEIVER_H #ifndef RECEIVER_H
#define RECEIVER_H #define RECEIVER_H
#include <stdbool.h> #include "common.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <stdbool.h>
#include "config.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
// receive events from the device // receive events from the device
// managed by the controller // managed by the controller
struct receiver { struct receiver {
socket_t control_socket; socket_t control_socket;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
}; };
bool bool

View File

@ -3,9 +3,6 @@
#include <assert.h> #include <assert.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include "config.h"
#include "compat.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -29,7 +26,7 @@ find_muxer(const char *name) {
static struct record_packet * static struct record_packet *
record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct record_packet *rec = SDL_malloc(sizeof(*rec)); struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) { if (!rec) {
return NULL; return NULL;
} }
@ -39,7 +36,7 @@ record_packet_new(const AVPacket *packet) {
av_init_packet(&rec->packet); av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) { if (av_packet_ref(&rec->packet, packet)) {
SDL_free(rec); free(rec);
return NULL; return NULL;
} }
return rec; return rec;
@ -48,7 +45,7 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet); av_packet_unref(&rec->packet);
SDL_free(rec); free(rec);
} }
static void static void
@ -65,24 +62,24 @@ recorder_init(struct recorder *recorder,
const char *filename, const char *filename,
enum sc_record_format format, enum sc_record_format format,
struct size declared_frame_size) { struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Could not strdup filename"); LOGE("Could not strdup filename");
return false; return false;
} }
recorder->mutex = SDL_CreateMutex(); bool ok = sc_mutex_init(&recorder->mutex);
if (!recorder->mutex) { if (!ok) {
LOGC("Could not create mutex"); LOGC("Could not create mutex");
SDL_free(recorder->filename); free(recorder->filename);
return false; return false;
} }
recorder->queue_cond = SDL_CreateCond(); ok = sc_cond_init(&recorder->queue_cond);
if (!recorder->queue_cond) { if (!ok) {
LOGC("Could not create cond"); LOGC("Could not create cond");
SDL_DestroyMutex(recorder->mutex); sc_mutex_destroy(&recorder->mutex);
SDL_free(recorder->filename); free(recorder->filename);
return false; return false;
} }
@ -99,9 +96,9 @@ recorder_init(struct recorder *recorder,
void void
recorder_destroy(struct recorder *recorder) { recorder_destroy(struct recorder *recorder) {
SDL_DestroyCond(recorder->queue_cond); sc_cond_destroy(&recorder->queue_cond);
SDL_DestroyMutex(recorder->mutex); sc_mutex_destroy(&recorder->mutex);
SDL_free(recorder->filename); free(recorder->filename);
} }
static const char * static const char *
@ -260,17 +257,17 @@ run_recorder(void *data) {
struct recorder *recorder = data; struct recorder *recorder = data;
for (;;) { for (;;) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) { while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) { if (recorder->stopped && queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
// assign an arbitrary duration to the last packet // assign an arbitrary duration to the last packet
@ -290,7 +287,7 @@ run_recorder(void *data) {
struct record_packet *rec; struct record_packet *rec;
queue_take(&recorder->queue, next, &rec); queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock // recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous; struct record_packet *previous = recorder->previous;
@ -313,11 +310,11 @@ run_recorder(void *data) {
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->failed = true; recorder->failed = true;
// discard pending packets // discard pending packets
recorder_queue_clear(&recorder->queue); recorder_queue_clear(&recorder->queue);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
break; break;
} }
@ -332,8 +329,9 @@ bool
recorder_start(struct recorder *recorder) { recorder_start(struct recorder *recorder) {
LOGD("Starting recorder thread"); LOGD("Starting recorder thread");
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder); bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
if (!recorder->thread) { recorder);
if (!ok) {
LOGC("Could not start recorder thread"); LOGC("Could not start recorder thread");
return false; return false;
} }
@ -343,38 +341,38 @@ recorder_start(struct recorder *recorder) {
void void
recorder_stop(struct recorder *recorder) { recorder_stop(struct recorder *recorder) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
cond_signal(recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
} }
void void
recorder_join(struct recorder *recorder) { recorder_join(struct recorder *recorder) {
SDL_WaitThread(recorder->thread, NULL); sc_thread_join(&recorder->thread, NULL);
} }
bool bool
recorder_push(struct recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
mutex_lock(recorder->mutex); sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
if (recorder->failed) { if (recorder->failed) {
// reject any new packet (this will stop the stream) // reject any new packet (this will stop the stream)
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
struct record_packet *rec = record_packet_new(packet); struct record_packet *rec = record_packet_new(packet);
if (!rec) { if (!rec) {
LOGC("Could not allocate record packet"); LOGC("Could not allocate record packet");
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return false; return false;
} }
queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
cond_signal(recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
} }

View File

@ -1,15 +1,15 @@
#ifndef RECORDER_H #ifndef RECORDER_H
#define RECORDER_H #define RECORDER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include "coords.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h"
struct record_packet { struct record_packet {
AVPacket packet; AVPacket packet;
@ -25,9 +25,9 @@ struct recorder {
struct size declared_frame_size; struct size declared_frame_size;
bool header_written; bool header_written;
SDL_Thread *thread; sc_thread thread;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *queue_cond; sc_cond queue_cond;
bool stopped; // set on recorder_stop() by the stream reader bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;

View File

@ -13,10 +13,6 @@
# include <windows.h> # include <windows.h>
#endif #endif
#include "config.h"
#include "command.h"
#include "common.h"
#include "compat.h"
#include "controller.h" #include "controller.h"
#include "decoder.h" #include "decoder.h"
#include "device.h" #include "device.h"
@ -30,7 +26,6 @@
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
@ -230,13 +225,20 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
if (!options->control) { if (!options->control) {
break; break;
} }
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action; file_handler_action_t action;
if (is_apk(event->drop.file)) { if (is_apk(file)) {
action = ACTION_INSTALL_APK; action = ACTION_INSTALL_APK;
} else { } else {
action = ACTION_PUSH_FILE; action = ACTION_PUSH_FILE;
} }
file_handler_request(&file_handler, action, event->drop.file); file_handler_request(&file_handler, action, file);
break; break;
} }
} }
@ -290,7 +292,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) { if (priority == 0) {
return; return;
} }
char *local_fmt = SDL_malloc(strlen(fmt) + 10); char *local_fmt = malloc(strlen(fmt) + 10);
if (!local_fmt) { if (!local_fmt) {
LOGC("Could not allocate string"); LOGC("Could not allocate string");
return; return;
@ -299,7 +301,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
strcpy(local_fmt, "[FFmpeg] "); strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt); strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt); free(local_fmt);
} }
bool bool

View File

@ -1,12 +1,12 @@
#ifndef SCRCPY_H #ifndef SCRCPY_H
#define SCRCPY_H #define SCRCPY_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
enum sc_log_level { enum sc_log_level {
SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO, SC_LOG_LEVEL_INFO,
@ -65,7 +65,7 @@ struct scrcpy_options {
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint16_t display_id; uint32_t display_id;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;

View File

@ -4,14 +4,10 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "scrcpy.h" #include "scrcpy.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
@ -457,15 +453,13 @@ update_texture(struct screen *screen, const AVFrame *frame) {
bool bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) { screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_lock(vb->mutex); const AVFrame *frame = video_buffer_take_rendering_frame(vb);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex); sc_mutex_unlock(&vb->mutex);
return false; return false;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false); screen_render(screen, false);
return true; return true;

View File

@ -1,12 +1,13 @@
#ifndef SCREEN_H #ifndef SCREEN_H
#define SCREEN_H #define SCREEN_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h" #include "coords.h"
#include "common.h"
#include "opengl.h" #include "opengl.h"
struct video_buffer; struct video_buffer;

View File

@ -5,13 +5,10 @@
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "config.h" #include "adb.h"
#include "command.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -34,7 +31,7 @@ get_server_path(void) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *server_path = utf8_from_wide_char(server_path_env); char *server_path = utf8_from_wide_char(server_path_env);
#else #else
char *server_path = SDL_strdup(server_path_env); char *server_path = strdup(server_path_env);
#endif #endif
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
@ -46,7 +43,7 @@ get_server_path(void) {
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH); LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH); char *server_path = strdup(DEFAULT_SERVER_PATH);
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
return NULL; return NULL;
@ -68,11 +65,11 @@ get_server_path(void) {
// sizeof(SERVER_FILENAME) gives statically the size including the null byte // sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = SDL_malloc(len); char *server_path = malloc(len);
if (!server_path) { if (!server_path) {
LOGE("Could not alloc server path string, " LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
SDL_free(executable_path); free(executable_path);
return SERVER_FILENAME; return SERVER_FILENAME;
} }
@ -81,7 +78,7 @@ get_server_path(void) {
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME // the final null byte has been copied with SERVER_FILENAME
SDL_free(executable_path); free(executable_path);
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
return server_path; return server_path;
@ -96,36 +93,36 @@ push_server(const char *serial) {
} }
if (!is_regular_file(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
SDL_free(server_path); free(server_path);
return false; return false;
} }
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
SDL_free(server_path); free(server_path);
return process_check_success(process, "adb push"); return process_check_success(process, "adb push", true);
} }
static bool static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) { enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse"); return process_check_success(process, "adb reverse", true);
} }
static bool static bool
disable_tunnel_reverse(const char *serial) { disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove"); return process_check_success(process, "adb reverse --remove", true);
} }
static bool static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) { enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward"); return process_check_success(process, "adb forward", true);
} }
static bool static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) { disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove"); return process_check_success(process, "adb forward --remove", true);
} }
static bool static bool
@ -258,12 +255,12 @@ execute_server(struct server *server, const struct server_params *params) {
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
char lock_video_orientation_string[5]; char lock_video_orientation_string[5];
char display_id_string[6]; char display_id_string[11];
sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu16, params->display_id); sprintf(display_id_string, "%"PRIu32, params->display_id);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
@ -358,18 +355,17 @@ bool
server_init(struct server *server) { server_init(struct server *server) {
server->serial = NULL; server->serial = NULL;
server->process = PROCESS_NONE; server->process = PROCESS_NONE;
server->wait_server_thread = NULL;
atomic_flag_clear_explicit(&server->server_socket_closed, atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed); memory_order_relaxed);
server->mutex = SDL_CreateMutex(); bool ok = sc_mutex_init(&server->mutex);
if (!server->mutex) { if (!ok) {
return false; return false;
} }
server->process_terminated_cond = SDL_CreateCond(); ok = sc_cond_init(&server->process_terminated_cond);
if (!server->process_terminated_cond) { if (!ok) {
SDL_DestroyMutex(server->mutex); sc_mutex_destroy(&server->mutex);
return false; return false;
} }
@ -392,12 +388,12 @@ server_init(struct server *server) {
static int static int
run_wait_server(void *data) { run_wait_server(void *data) {
struct server *server = data; struct server *server = data;
cmd_simple_wait(server->process, NULL); // ignore exit code process_wait(server->process, false); // ignore exit code
mutex_lock(server->mutex); sc_mutex_lock(&server->mutex);
server->process_terminated = true; server->process_terminated = true;
cond_signal(server->process_terminated_cond); sc_cond_signal(&server->process_terminated_cond);
mutex_unlock(server->mutex); sc_mutex_unlock(&server->mutex);
// no need for synchronization, server_socket is initialized before this // no need for synchronization, server_socket is initialized before this
// thread was created // thread was created
@ -417,7 +413,7 @@ server_start(struct server *server, const char *serial,
server->port_range = params->port_range; server->port_range = params->port_range;
if (serial) { if (serial) {
server->serial = SDL_strdup(serial); server->serial = strdup(serial);
if (!server->serial) { if (!server->serial) {
return false; return false;
} }
@ -444,11 +440,11 @@ server_start(struct server *server, const char *serial,
// things simple and multiplatform, just spawn a new thread waiting for the // things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if // server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call. // necessary to wake up any accept() blocking call.
server->wait_server_thread = bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
SDL_CreateThread(run_wait_server, "wait-server", server); "wait-server", server);
if (!server->wait_server_thread) { if (!ok) {
cmd_terminate(server->process); process_terminate(server->process);
cmd_simple_wait(server->process, NULL); // ignore exit code process_wait(server->process, true); // ignore exit code
goto error2; goto error2;
} }
@ -467,7 +463,7 @@ error2:
} }
disable_tunnel(server); disable_tunnel(server);
error1: error1:
SDL_free(server->serial); free(server->serial);
return false; return false;
} }
@ -536,33 +532,33 @@ server_stop(struct server *server) {
} }
// Give some delay for the server to terminate properly // Give some delay for the server to terminate properly
mutex_lock(server->mutex); sc_mutex_lock(&server->mutex);
int r = 0; bool signaled = false;
if (!server->process_terminated) { if (!server->process_terminated) {
#define WATCHDOG_DELAY_MS 1000 #define WATCHDOG_DELAY_MS 1000
r = cond_wait_timeout(server->process_terminated_cond, signaled = sc_cond_timedwait(&server->process_terminated_cond,
server->mutex, &server->mutex,
WATCHDOG_DELAY_MS); WATCHDOG_DELAY_MS);
} }
mutex_unlock(server->mutex); sc_mutex_unlock(&server->mutex);
// After this delay, kill the server if it's not dead already. // After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the // On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep. // blocking calls while the device is asleep.
if (r == SDL_MUTEX_TIMEDOUT) { if (!signaled) {
// FIXME There is a race condition here: there is a small chance that // The process is terminated, but not reaped (closed) yet, so its PID
// the process is already terminated, and the PID assigned to a new // is still valid.
// process.
LOGW("Killing the server..."); LOGW("Killing the server...");
cmd_terminate(server->process); process_terminate(server->process);
} }
SDL_WaitThread(server->wait_server_thread, NULL); sc_thread_join(&server->wait_server_thread, NULL);
process_close(server->process);
} }
void void
server_destroy(struct server *server) { server_destroy(struct server *server) {
SDL_free(server->serial); free(server->serial);
SDL_DestroyCond(server->process_terminated_cond); sc_cond_destroy(&server->process_terminated_cond);
SDL_DestroyMutex(server->mutex); sc_mutex_destroy(&server->mutex);
} }

View File

@ -1,26 +1,26 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include "common.h"
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include "adb.h"
#include "command.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct server { struct server {
char *serial; char *serial;
process_t process; process_t process;
SDL_Thread *wait_server_thread; sc_thread wait_server_thread;
atomic_flag server_socket_closed; atomic_flag server_socket_closed;
SDL_mutex *mutex; sc_mutex mutex;
SDL_cond *process_terminated_cond; sc_cond process_terminated_cond;
bool process_terminated; bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
@ -43,7 +43,7 @@ struct server_params {
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
bool control; bool control;
uint16_t display_id; uint32_t display_id;
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;

View File

@ -4,12 +4,8 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "compat.h"
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
#include "recorder.h" #include "recorder.h"
@ -281,8 +277,8 @@ bool
stream_start(struct stream *stream) { stream_start(struct stream *stream) {
LOGD("Starting stream thread"); LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream); bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
if (!stream->thread) { if (!ok) {
LOGC("Could not start stream thread"); LOGC("Could not start stream thread");
return false; return false;
} }
@ -298,5 +294,5 @@ stream_stop(struct stream *stream) {
void void
stream_join(struct stream *stream) { stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL); sc_thread_join(&stream->thread, NULL);
} }

View File

@ -1,20 +1,21 @@
#ifndef STREAM_H #ifndef STREAM_H
#define STREAM_H #define STREAM_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct video_buffer; struct video_buffer;
struct stream { struct stream {
socket_t socket; socket_t socket;
SDL_Thread *thread; sc_thread thread;
struct decoder *decoder; struct decoder *decoder;
struct recorder *recorder; struct recorder *recorder;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;

View File

@ -1,17 +1,4 @@
// for portability (kill, readlink, strdup, strtok_r) #include "util/process.h"
#define _POSIX_C_SOURCE 200809L
#define _BSD_SOURCE
// modern glibc will complain without this
#define _DEFAULT_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4()
#endif
#include "command.h"
#include "config.h"
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
@ -27,7 +14,7 @@
#include "util/log.h" #include "util/log.h"
bool bool
cmd_search(const char *file) { search_executable(const char *file) {
char *path = getenv("PATH"); char *path = getenv("PATH");
if (!path) if (!path)
return false; return false;
@ -63,7 +50,7 @@ cmd_search(const char *file) {
} }
enum process_result enum process_result
cmd_execute(const char *const argv[], pid_t *pid) { process_execute(const char *const argv[], pid_t *pid) {
int fd[2]; int fd[2];
if (pipe(fd) == -1) { if (pipe(fd) == -1) {
@ -125,29 +112,37 @@ end:
} }
bool bool
cmd_terminate(pid_t pid) { process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
abort(); abort();
} }
return kill(pid, SIGTERM) != -1; return kill(pid, SIGKILL) != -1;
} }
bool exit_code_t
cmd_simple_wait(pid_t pid, int *exit_code) { process_wait(pid_t pid, bool close) {
int status;
int code; int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) { int options = WEXITED;
if (!close) {
options |= WNOWAIT;
}
siginfo_t info;
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 // could not wait, or exited unexpectedly, probably by a signal
code = -1; code = NO_EXIT_CODE;
} else { } else {
code = WEXITSTATUS(status); code = info.si_status;
} }
if (exit_code) { return code;
*exit_code = code; }
}
return !code; void
process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code
} }
char * char *
@ -161,7 +156,7 @@ get_executable_path(void) {
return NULL; return NULL;
} }
buf[len] = '\0'; buf[len] = '\0';
return SDL_strdup(buf); return strdup(buf);
#else #else
// in practice, we only need this feature for portable builds, only used on // in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform // Windows, so we don't care implementing it for every platform

View File

@ -1,8 +1,8 @@
#include "command.h" #include "util/process.h"
#include <assert.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "config.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -21,7 +21,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
} }
enum process_result enum process_result
cmd_execute(const char *const argv[], HANDLE *handle) { process_execute(const char *const argv[], HANDLE *handle) {
STARTUPINFOW si; STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
@ -41,7 +41,7 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi)) { &pi)) {
SDL_free(wide); free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY; return PROCESS_ERROR_MISSING_BINARY;
@ -49,29 +49,35 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
SDL_free(wide); free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
bool bool
cmd_terminate(HANDLE handle) { process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1);
} }
bool exit_code_t
cmd_simple_wait(HANDLE handle, DWORD *exit_code) { process_wait(HANDLE handle, bool close) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = -1; // max value, it's unsigned code = NO_EXIT_CODE; // max value, it's unsigned
} }
if (exit_code) { if (close) {
*exit_code = code; CloseHandle(handle);
} }
CloseHandle(handle); return code;
return !code; }
void
process_close(HANDLE handle) {
bool closed = CloseHandle(handle);
assert(closed);
(void) closed;
} }
char * char *
@ -99,7 +105,7 @@ is_regular_file(const char *path) {
struct _stat path_stat; struct _stat path_stat;
int r = _wstat(wide_path, &path_stat); int r = _wstat(wide_path, &path_stat);
SDL_free(wide_path); free(wide_path);
if (r) { if (r) {
perror("stat"); perror("stat");

View File

@ -6,7 +6,6 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "config.h"
#include "util/log.h" #include "util/log.h"
struct index { struct index {

View File

@ -1,9 +1,9 @@
#ifndef TINYXPM_H #ifndef TINYXPM_H
#define TINYXPM_H #define TINYXPM_H
#include <SDL2/SDL.h> #include "common.h"
#include "config.h" #include <SDL2/SDL.h>
SDL_Surface * SDL_Surface *
read_xpm(char *xpm[]); read_xpm(char *xpm[]);

View File

@ -1,11 +1,11 @@
#ifndef BUFFER_UTIL_H #ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H #define BUFFER_UTIL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
static inline void static inline void
buffer_write16be(uint8_t *buf, uint16_t value) { buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) {
static inline uint32_t static inline uint32_t
buffer_read32be(const uint8_t *buf) { buffer_read32be(const uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline uint64_t static inline uint64_t

View File

@ -2,11 +2,11 @@
#ifndef CBUF_H #ifndef CBUF_H
#define CBUF_H #define CBUF_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
// To define a circular buffer type of 20 ints: // To define a circular buffer type of 20 ints:
// struct cbuf_int CBUF(int, 20); // struct cbuf_int CBUF(int, 20);
// //

View File

@ -1,74 +0,0 @@
#ifndef LOCK_H
#define LOCK_H
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "config.h"
#include "log.h"
static inline void
mutex_lock(SDL_mutex *mutex) {
int r = SDL_LockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
mutex_unlock(SDL_mutex *mutex) {
int r = SDL_UnlockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not unlock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
int r = SDL_CondWait(cond, mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
#endif
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
int r = SDL_CondSignal(cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
#endif

View File

@ -3,7 +3,6 @@
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "config.h"
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__

View File

@ -1,6 +1,8 @@
#ifndef NET_H #ifndef NET_H
#define NET_H #define NET_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
@ -17,8 +19,6 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
#include "config.h"
bool bool
net_init(void); net_init(void);

21
app/src/util/process.c Normal file
View File

@ -0,0 +1,21 @@
#include "process.h"
#include "log.h"
bool
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

View File

@ -1,8 +1,9 @@
#ifndef COMMAND_H #ifndef SC_PROCESS_H
#define COMMAND_H #define SC_PROCESS_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h>
#ifdef _WIN32 #ifdef _WIN32
@ -31,58 +32,45 @@
#endif #endif
#include "config.h"
enum process_result { enum process_result {
PROCESS_SUCCESS, PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC, PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
#ifndef __WINDOWS__ // execute the command and write the result to the output parameter "process"
bool
cmd_search(const char *file);
#endif
enum process_result enum process_result
cmd_execute(const char *const argv[], process_t *process); process_execute(const char *const argv[], process_t *process);
// kill the process
bool bool
cmd_terminate(process_t pid); process_terminate(process_t pid);
bool // wait and close the process (like waitpid())
cmd_simple_wait(process_t pid, exit_code_t *exit_code); // the "close" flag indicates if the process must be "closed" (reaped)
// (passing false is equivalent to enable WNOWAIT in waitid())
exit_code_t
process_wait(process_t pid, bool close);
process_t // close the process
adb_execute(const char *serial, const char *const adb_cmd[], size_t len); //
// Semantically, process_wait(close) = process_wait(noclose) + process_close
process_t void
adb_forward(const char *serial, uint16_t local_port, process_close(process_t pid);
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);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
bool bool
process_check_success(process_t proc, const char *name); process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary) // return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by SDL_free // may be NULL on error; to be freed by free()
char * char *
get_executable_path(void); get_executable_path(void);

View File

@ -2,12 +2,12 @@
#ifndef QUEUE_H #ifndef QUEUE_H
#define QUEUE_H #define QUEUE_H
#include "common.h"
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "config.h"
// To define a queue type of "struct foo": // To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo); // struct queue_foo QUEUE(struct foo);
#define QUEUE(TYPE) { \ #define QUEUE(TYPE) { \

View File

@ -10,10 +10,6 @@
# include <tchar.h> # include <tchar.h>
#endif #endif
#include <SDL2/SDL_stdinc.h>
#include "config.h"
size_t size_t
xstrncpy(char *dest, const char *src, size_t n) { xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
@ -51,7 +47,7 @@ truncated:
char * char *
strquote(const char *src) { strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = SDL_malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
return NULL; return NULL;
} }
@ -169,7 +165,7 @@ utf8_to_wide_char(const char *utf8) {
return NULL; return NULL;
} }
wchar_t *wide = SDL_malloc(len * sizeof(wchar_t)); wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) { if (!wide) {
return NULL; return NULL;
} }
@ -185,7 +181,7 @@ utf8_from_wide_char(const wchar_t *ws) {
return NULL; return NULL;
} }
char *utf8 = SDL_malloc(len); char *utf8 = malloc(len);
if (!utf8) { if (!utf8) {
return NULL; return NULL;
} }

View File

@ -1,11 +1,11 @@
#ifndef STRUTIL_H #ifndef STRUTIL_H
#define STRUTIL_H #define STRUTIL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "config.h"
// like strncpy, except: // like strncpy, except:
// - it copies at most n-1 chars // - it copies at most n-1 chars
// - the dest string is nul-terminated // - the dest string is nul-terminated

159
app/src/util/thread.c Normal file
View File

@ -0,0 +1,159 @@
#include "thread.h"
#include <assert.h>
#include <SDL2/SDL_thread.h>
#include "log.h"
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
return false;
}
thread->thread = sdl_thread;
return true;
}
void
sc_thread_join(sc_thread *thread, int *status) {
SDL_WaitThread(thread->thread, status);
}
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
return false;
}
mutex->mutex = sdl_mutex;
#ifndef NDEBUG
mutex->locker = 0;
#endif
return true;
}
void
sc_mutex_destroy(sc_mutex *mutex) {
SDL_DestroyMutex(mutex->mutex);
}
void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_ThreadID();
}
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
return mutex->locker == sc_thread_get_id();
}
#endif
bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
return false;
}
cond->cond = sdl_cond;
return true;
}
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

81
app/src/util/thread.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef SC_THREAD_H
#define SC_THREAD_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned int sc_thread_id;
typedef struct sc_thread {
SDL_Thread *thread;
} sc_thread;
typedef struct sc_mutex {
SDL_mutex *mutex;
#ifndef NDEBUG
sc_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);
void
sc_thread_join(sc_thread *thread, int *status);
bool
sc_mutex_init(sc_mutex *mutex);
void
sc_mutex_destroy(sc_mutex *mutex);
void
sc_mutex_lock(sc_mutex *mutex);
void
sc_mutex_unlock(sc_mutex *mutex);
sc_thread_id
sc_thread_get_id(void);
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex);
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
#else
# define sc_mutex_assert(mutex)
#endif
bool
sc_cond_init(sc_cond *cond);
void
sc_cond_destroy(sc_cond *cond);
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void
sc_cond_signal(sc_cond *cond);
void
sc_cond_broadcast(sc_cond *cond);
#endif

View File

@ -1,12 +1,9 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
@ -14,22 +11,31 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) { bool render_expired_frames) {
vb->fps_counter = fps_counter; vb->fps_counter = fps_counter;
if (!(vb->decoding_frame = av_frame_alloc())) { vb->decoding_frame = av_frame_alloc();
if (!vb->decoding_frame) {
goto error_0; goto error_0;
} }
if (!(vb->rendering_frame = av_frame_alloc())) { vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_1; goto error_1;
} }
if (!(vb->mutex = SDL_CreateMutex())) { vb->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
goto error_2; goto error_2;
} }
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
goto error_3;
}
vb->render_expired_frames = render_expired_frames; vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) { if (render_expired_frames) {
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) { ok = sc_cond_init(&vb->pending_frame_consumed_cond);
SDL_DestroyMutex(vb->mutex); if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2; goto error_2;
} }
// interrupted is not used if expired frames are not rendered // interrupted is not used if expired frames are not rendered
@ -39,12 +45,14 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
// there is initially no rendering frame, so consider it has already been // there is initially no rendering frame, so consider it has already been
// consumed // consumed
vb->rendering_frame_consumed = true; vb->pending_frame_consumed = true;
return true; return true;
error_2: error_3:
av_frame_free(&vb->rendering_frame); av_frame_free(&vb->rendering_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1: error_1:
av_frame_free(&vb->decoding_frame); av_frame_free(&vb->decoding_frame);
error_0: error_0:
@ -54,60 +62,78 @@ error_0:
void void
video_buffer_destroy(struct video_buffer *vb) { video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) { if (vb->render_expired_frames) {
SDL_DestroyCond(vb->rendering_frame_consumed_cond); sc_cond_destroy(&vb->pending_frame_consumed_cond);
} }
SDL_DestroyMutex(vb->mutex); sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->rendering_frame); av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->decoding_frame); av_frame_free(&vb->decoding_frame);
} }
static void static void
video_buffer_swap_frames(struct video_buffer *vb) { video_buffer_swap_decoding_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->decoding_frame; AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame; vb->decoding_frame = vb->pending_frame;
vb->rendering_frame = tmp; vb->pending_frame = tmp;
}
static void
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->rendering_frame;
vb->rendering_frame = vb->pending_frame;
vb->pending_frame = tmp;
} }
void void
video_buffer_offer_decoded_frame(struct video_buffer *vb, video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) { bool *previous_frame_skipped) {
mutex_lock(vb->mutex); sc_mutex_lock(&vb->mutex);
if (vb->render_expired_frames) { if (vb->render_expired_frames) {
// wait for the current (expired) frame to be consumed // wait for the current (expired) frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) { while (!vb->pending_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex); sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
} }
} else if (!vb->rendering_frame_consumed) { } else if (!vb->pending_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter); fps_counter_add_skipped_frame(vb->fps_counter);
} }
video_buffer_swap_frames(vb); video_buffer_swap_decoding_frame(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed; *previous_frame_skipped = !vb->pending_frame_consumed;
vb->rendering_frame_consumed = false; vb->pending_frame_consumed = false;
mutex_unlock(vb->mutex); sc_mutex_unlock(&vb->mutex);
} }
const AVFrame * const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) { video_buffer_take_rendering_frame(struct video_buffer *vb) {
assert(!vb->rendering_frame_consumed); sc_mutex_lock(&vb->mutex);
vb->rendering_frame_consumed = true; assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter); fps_counter_add_rendered_frame(vb->fps_counter);
video_buffer_swap_rendering_frame(vb);
if (vb->render_expired_frames) { if (vb->render_expired_frames) {
// unblock video_buffer_offer_decoded_frame() // unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond); sc_cond_signal(&vb->pending_frame_consumed_cond);
} }
sc_mutex_unlock(&vb->mutex);
// rendering_frame is only written from this thread, no need to lock
return vb->rendering_frame; return vb->rendering_frame;
} }
void void
video_buffer_interrupt(struct video_buffer *vb) { video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) { if (vb->render_expired_frames) {
mutex_lock(vb->mutex); sc_mutex_lock(&vb->mutex);
vb->interrupted = true; vb->interrupted = true;
mutex_unlock(vb->mutex); sc_mutex_unlock(&vb->mutex);
// wake up blocking wait // wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond); sc_cond_signal(&vb->pending_frame_consumed_cond);
} }
} }

View File

@ -1,23 +1,45 @@
#ifndef VIDEO_BUFFER_H #ifndef VIDEO_BUFFER_H
#define VIDEO_BUFFER_H #define VIDEO_BUFFER_H
#include <stdbool.h> #include "common.h"
#include <SDL2/SDL_mutex.h>
#include <stdbool.h>
#include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "util/thread.h"
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
/**
* There are 3 frames in memory:
* - one frame is held by the decoder (decoding_frame)
* - one frame is held by the renderer (rendering_frame)
* - one frame is shared between the decoder and the renderer (pending_frame)
*
* The decoder decodes a packet into the decoding_frame (it may takes time).
*
* Once the frame is decoded, it calls video_buffer_offer_decoded_frame(),
* which swaps the decoding and pending frames.
*
* When the renderer is notified that a new frame is available, it calls
* video_buffer_take_rendering_frame() to retrieve it, which swaps the pending
* and rendering frames. The frame is valid until the next call, without
* blocking the decoder.
*/
struct video_buffer { struct video_buffer {
AVFrame *decoding_frame; AVFrame *decoding_frame;
AVFrame *pending_frame;
AVFrame *rendering_frame; AVFrame *rendering_frame;
SDL_mutex *mutex;
sc_mutex mutex;
bool render_expired_frames; bool render_expired_frames;
bool interrupted; bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed; sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
struct fps_counter *fps_counter; struct fps_counter *fps_counter;
}; };
@ -29,18 +51,15 @@ void
video_buffer_destroy(struct video_buffer *vb); video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering // set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped // the output flag is set to report whether the previous frame has been skipped
void void
video_buffer_offer_decoded_frame(struct video_buffer *vb, video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped); bool *previous_frame_skipped);
// mark the rendering frame as consumed and return it // mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!! // the frame is valid until the next call to this function
// the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
const AVFrame * const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb); video_buffer_take_rendering_frame(struct video_buffer *vb);
// wake up and avoid any blocking call // wake up and avoid any blocking call
void void

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>

View File

@ -1,8 +1,9 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "cli.h" #include "cli.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
static void test_flag_version(void) { static void test_flag_version(void) {

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -15,7 +17,7 @@ static void test_serialize_inject_keycode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 14); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -37,7 +39,7 @@ static void test_serialize_inject_text(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 18); assert(size == 18);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -57,7 +59,7 @@ static void test_serialize_inject_text_long(void) {
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
@ -93,7 +95,7 @@ static void test_serialize_inject_touch_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 28); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -128,7 +130,7 @@ static void test_serialize_inject_scroll_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 21); assert(size == 21);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -147,7 +149,7 @@ static void test_serialize_back_or_screen_on(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -162,7 +164,7 @@ static void test_serialize_expand_notification_panel(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -177,7 +179,7 @@ static void test_serialize_collapse_notification_panel(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -192,7 +194,7 @@ static void test_serialize_get_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -211,7 +213,7 @@ static void test_serialize_set_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 19); assert(size == 19);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -232,7 +234,7 @@ static void test_serialize_set_screen_power_mode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -248,7 +250,7 @@ static void test_serialize_rotate_device(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>

View File

@ -1,3 +1,5 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include "util/queue.h" #include "util/queue.h"

View File

@ -1,8 +1,9 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <SDL2/SDL.h>
#include "util/str_util.h" #include "util/str_util.h"
@ -136,7 +137,7 @@ static void test_strquote(void) {
// add '"' at the beginning and the end // add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out)); assert(!strcmp("\"abcde\"", out));
SDL_free(out); free(out);
} }
static void test_utf8_truncate(void) { static void test_utf8_truncate(void) {

View File

@ -1,21 +0,0 @@
#!/usr/bin/env bash
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17
PREBUILT_SERVER_SHA256=11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
echo "[scrcpy] Verifying prebuilt server..."
echo "$PREBUILT_SERVER_SHA256 scrcpy-server" | sha256sum --check
echo "[scrcpy] Building client..."
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=scrcpy-server
cd "$BUILDDIR"
ninja
echo "[scrcpy] Installing (sudo)..."
sudo ninja install