Compare commits

...

32 Commits

Author SHA1 Message Date
206abdb2c5 Add option to force legacy method for pasting
Some devices do not behave as expected when setting the device clipboard
programmatically.

Add an option --legacy-paste to change the behavior of Ctrl+v and MOD+v
so that they inject the computer clipboard text as a sequence of key
events (the same way as MOD+Shift+v).

Fixes #1750 <https://github.com/Genymobile/scrcpy/issues/1750>
Fixes #1771 <https://github.com/Genymobile/scrcpy/issues/1771>
2020-10-06 21:30:10 +02:00
83082406d3 Enable Java deprecation warnings details
Without the option, gradle reports a lint issue, but without any
details.
2020-10-05 21:11:50 +02:00
2edf192e3a Remove deprecation warning
As a workaround for some devices, we need to prepare the main looper.
The method is now deprecated, but we still want to call it.
2020-10-05 21:09:47 +02:00
d50ecf40b6 Fix options order 2020-10-01 15:08:18 +02:00
56d237f152 Fix "press Enter key" message
The message said "Press any key to continue...", whereas only
Enter/Return is accepted.

PR #1783 <https://github.com/Genymobile/scrcpy/pull/1783>
Fixes #1757 <https://github.com/Genymobile/scrcpy/issues/1757>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-10-01 14:52:24 +02:00
acc65f8c9d Remove unused field
Fixes #1775 <https://github.com/Genymobile/scrcpy/issues/1775>

Reported-by: lordnn
2020-09-20 01:11:22 +02:00
a65ebceac1 Add missing mutex unlock on error
Fixes #1770 <https://github.com/Genymobile/scrcpy/issues/1770>

Reported-by: lordnn
2020-09-20 01:11:13 +02:00
d662f73bdc Upgrade Android SDK to 30 2020-09-15 14:54:22 +02:00
1c44dc2259 Use portable shebang for all bash scripts
Refs #426 <https://github.com/Genymobile/scrcpy/pull/426>
Refs #1716 <https://github.com/Genymobile/scrcpy/pull/1716>
2020-09-15 13:54:00 +02:00
02a882a0a2 Use a more portable shebang for bash
This should increase the portability of bash scripts across various *nix
systems such as BSD-like distributions.

PR #1716 <https://github.com/Genymobile/scrcpy/pull/1716>

Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-09-15 13:52:50 +02:00
cf7bf3148c Use "/usr/bin/env bash" for build-wrapper.sh
PR #426 <https://github.com/Genymobile/scrcpy/pull/426>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-09-15 13:44:02 +02:00
ae758f99d6 Adapt call() on ContentProvider for Android 11
This commit in AOSP framework_base added a parameter "attributionTag" to
the call() method:
12ac3f406f%5E%21/#F17

As a consequence, the method did not exist, so scrcpy used the legacy
call() method (using the authority "unknown") as a fallback, which fails
for security reasons.

Fixes #1468 <https://github.com/Genymobile/scrcpy/issues/1468>
2020-09-15 13:31:10 +02:00
bd9f656933 Fix feature test macro
The expected feature test macro is _POSIX_C_SOURCE having a value
greater or equal to 200809L.

Fixes #1726 <https://github.com/Genymobile/scrcpy/issues/1726>
2020-08-31 14:02:51 +02:00
c243fd4c3f Fix more log format warning
The expression port + 1 is promoted to int, but printed as uint16_t.

This is the same mistake fixed for a different log by
7eb16ce364.

Refs #1726 <https://github.com/Genymobile/scrcpy/issues/1726>
2020-08-31 13:40:32 +02:00
0bf110dd5c Reset power mode only if screen is on
PR #1670 <https://github.com/Genymobile/scrcpy/pull/1670>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-21 12:34:59 +02:00
0c5e0a4f6d Make Device methods static when possible
The behavior of some methods do not depend on the user-provided options.
These methods can be static. This will allow to call them directly from
the cleanup process.
2020-08-21 12:34:50 +02:00
0be766e71a Add undetected device error message in FAQ 2020-08-20 19:14:45 +02:00
d02789ce21 List available shortcut keys on error
Fixes #1681 <https://github.com/Genymobile/scrcpy/issues/1681>

Suggested-by: Moritz Schulz <moritzleni@gmail.com>
2020-08-16 13:52:01 +02:00
6cc22e1c5b Reference --shortcut-mod from shortcuts list
Fixes #1681 <https://github.com/Genymobile/scrcpy/issues/1681>

Suggested-by: Moritz Schulz <moritzleni@gmail.com>
2020-08-16 13:44:42 +02:00
479d10dc22 Update links to v1.16 in README and BUILD 2020-08-10 20:34:51 +02:00
d7779d08e8 Bump version to 1.16 2020-08-10 20:09:28 +02:00
df4ba1b8b0 Merge branch 'master' into dev 2020-08-10 20:08:33 +02:00
198346d148 Add pinch-to-zoom simulation
If Ctrl is hold when the left-click button is pressed, enable
pinch-to-zoom to scale and rotate relative to the center of the screen.

Fixes #24 <https://github.com/Genymobile/scrcpy/issues/24>
2020-08-10 20:08:24 +02:00
8081e9cc11 Add reference of the translations in README
Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-10 19:58:11 +02:00
b87a0df99a Add Traditional Chinese translation for README
Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-10 19:58:00 +02:00
95f1ea0d80 Fix clipboard paste condition
To avoid possible copy-paste loops between the computer and the device,
the device clipboard is not set if it already contains the expected
content.

But the condition was wrong: it was not set also if it was empty.

Refs 1223a72eb8
Fixes #1658 <https://github.com/Genymobile/scrcpy/issues/1658>
2020-08-10 14:37:32 +02:00
38940ffe89 Revert "Inject WAKEUP instead of POWER"
WAKEUP does not work on some devices.

Fixes #1655 <https://github.com/Genymobile/scrcpy/issues/1655>

This reverts commit 322f1512ea.
2020-08-09 17:10:27 +02:00
a59a15777d Fix missing change of Ctrl key in README
PR #1652 <https://github.com/Genymobile/scrcpy/pull/1652>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-09 11:14:38 +02:00
a2cb63e344 Add packaging status
PR #1650 <https://github.com/Genymobile/scrcpy/pull/1650>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-08-08 13:18:23 +02:00
f03a3edde6 Update links to v1.15.1 in README and BUILD 2020-08-07 12:13:27 +02:00
633a51e9c4 Bump version to 1.15.1 2020-08-07 12:01:34 +02:00
976761956f Fix uninitialized repeat count in key events
A new "repeat" field has been added by
3c1ed5d86c, but it was not initialized in
every code path.

As a consequence, keycodes generated by shortcuts were sent with an
undetermined value, breaking some shortcuts (especially HOME) randomly.

Fixes #1643 <https://github.com/Genymobile/scrcpy/issues/1643>
2020-08-07 11:32:11 +02:00
29 changed files with 954 additions and 88 deletions

View File

@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.15`][direct-scrcpy-server]
_(SHA-256: e160c46784f30566eae621cad88bfb34e124f945cb8274b8dfc615b613b86dd8)_
- [`scrcpy-server-v1.16`][direct-scrcpy-server]
_(SHA-256: 94a79e05b4498d0460ab7bd9d12cbf05156e3a47bf0c5d1420cee1d4493b3832)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-server-v1.15
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-server-v1.16
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

2
FAQ.md
View File

@ -37,6 +37,8 @@ Check [stackoverflow][device-unauthorized].
### Device not detected
> adb: error: failed to get feature set: no devices/emulators found
If your device is not detected, you may need some [drivers] (on Windows).
[drivers]: https://developer.android.com/studio/run/oem-usb.html

View File

@ -1,4 +1,6 @@
# scrcpy (v1.15)
# scrcpy (v1.16)
[Read in another language](#translations)
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@ -34,6 +36,7 @@ control it using keyboard and mouse.
## Get the 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
@ -74,10 +77,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.15.zip`][direct-win64]
_(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_
- [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
It is also available in [Chocolatey]:
@ -448,7 +451,7 @@ Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
On Android, the `POWER` button always turns the screen on. For convenience, if
`POWER` is sent via scrcpy (via right-click or <kbd>Ctrl</kbd>+<kbd>p</kbd>), it
`POWER` is sent via scrcpy (via right-click or <kbd>MOD</kbd>+<kbd>p</kbd>), it
will force to turn the screen off after a small delay (on a best effort basis).
The physical `POWER` button will still cause the screen to be turned on.
@ -545,6 +548,23 @@ into the device clipboard. As a consequence, any Android application could read
its content. You should avoid to paste sensitive content (like passwords) that
way.
Some devices do not behave as expected when setting the device clipboard
programmatically. An option `--legacy-paste` is provided to change the behavior
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> to also inject
the computer clipboard text as a sequence of key events.
#### Pinch-to-zoom
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
More precisely, hold <kbd>Ctrl</kbd> while pressing the left-click button. Until
the left-click button is released, all mouse movements scale and rotate the
content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen.
#### Text injection preference
There are two kinds of [events][textevents] generated when typing text:
@ -658,6 +678,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
@ -733,3 +754,13 @@ Read the [developers page].
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
## Translations
This README is available in other languages:
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [português brasileiro (Brazilian Portuguese, `pt-BR`) - v1.12.1](README.pt-br.md)
Only this README file is guaranteed to be up-to-date.

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

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

View File

@ -68,6 +68,12 @@ Start in fullscreen.
.B \-h, \-\-help
Print this help.
.TP
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP
.BI "\-\-lock\-video\-orientation " value
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
@ -92,14 +98,14 @@ Disable device control (mirror the device in read\-only).
.B \-N, \-\-no\-display
Do not display device (only when screen recording is enabled).
.TP
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP
.B \-\-no\-key\-repeat
Do not forward repeated key events when a key is held down.
.TP
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.
@ -222,7 +228,7 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \-\-shortcut-mod.
Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
.TP
.B MOD+f
@ -316,6 +322,10 @@ Inject computer clipboard text as a sequence of key events
.B MOD+i
Enable/disable FPS counter (print frames/second in logs)
.TP
.B Ctrl+click-and-move
Pinch-to-zoom from the center of the screen
.TP
.B Drag & drop APK file
Install APK from computer

View File

@ -63,6 +63,12 @@ scrcpy_print_usage(const char *arg0) {
" -h, --help\n"
" Print this help.\n"
"\n"
" --legacy-paste\n"
" Inject computer clipboard text as a sequence of key events\n"
" on Ctrl+v (like MOD+Shift+v).\n"
" This is a workaround for some devices not behaving as\n"
" expected when setting the device clipboard programmatically.\n"
"\n"
" --lock-video-orientation value\n"
" Lock video orientation to value.\n"
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
@ -87,14 +93,14 @@ scrcpy_print_usage(const char *arg0) {
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" --no-mipmaps\n"
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
" mipmaps are automatically generated to improve downscaling\n"
" quality. This option disables the generation of mipmaps.\n"
"\n"
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n"
" Default is %d:%d.\n"
@ -203,7 +209,7 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" In the following list, MOD is the shortcut modifier. By default,\n"
" it's (left) Alt or (left) Super, but it can be configured by\n"
" --shortcut-mod.\n"
" --shortcut-mod (see above).\n"
"\n"
" MOD+f\n"
" Switch fullscreen mode\n"
@ -279,6 +285,9 @@ scrcpy_print_usage(const char *arg0) {
" MOD+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Ctrl+click-and-move\n"
" Pinch-to-zoom from the center of the screen\n"
"\n"
" Drag & drop APK file\n"
" Install APK from computer\n"
"\n",
@ -529,7 +538,9 @@ parse_shortcut_mods_item(const char *item, size_t len) {
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER;
} else {
LOGW("Unknown modifier key: %.*s", (int) key_len, item);
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
(int) key_len, item);
return 0;
}
#undef STREQ
@ -646,6 +657,7 @@ guess_record_format(const char *filename) {
#define OPT_DISABLE_SCREENSAVER 1020
#define OPT_SHORTCUT_MOD 1021
#define OPT_NO_KEY_REPEAT 1022
#define OPT_LEGACY_PASTE 1023
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -661,14 +673,15 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
OPT_FORCE_ADB_FORWARD},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
{"lock-video-orientation", required_argument, NULL,
OPT_LOCK_VIDEO_ORIENTATION},
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
{"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
{"port", required_argument, NULL, 'p'},
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
@ -851,6 +864,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false;
}
break;
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
break;
default:
// getopt prints the error message on stderr
return false;

View File

@ -17,6 +17,7 @@
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
#define POINTER_ID_MOUSE UINT64_C(-1);
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE,

View File

@ -60,6 +60,7 @@ input_manager_init(struct input_manager *im,
im->control = options->control;
im->forward_key_repeat = options->forward_key_repeat;
im->prefer_text = options->prefer_text;
im->legacy_paste = options->legacy_paste;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
assert(shortcut_mods->count);
@ -70,6 +71,8 @@ input_manager_init(struct input_manager *im,
im->sdl_shortcut_mods.data[i] = sdl_mod;
}
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
}
static void
@ -80,6 +83,7 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.keycode = keycode;
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
@ -298,6 +302,36 @@ input_manager_process_text_input(struct input_manager *im,
}
}
static bool
simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action,
struct point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point;
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0;
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'");
return false;
}
return true;
}
static struct point
inverse_point(struct point point, struct size size) {
point.x = size.width - point.x;
point.y = size.height - point.y;
return point;
}
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) {
@ -407,7 +441,7 @@ input_manager_process_key(struct input_manager *im,
return;
case SDLK_v:
if (control && !repeat && down) {
if (shift) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
} else {
@ -471,6 +505,11 @@ input_manager_process_key(struct input_manager *im,
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
return;
}
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
set_device_clipboard(controller, false);
@ -511,10 +550,18 @@ input_manager_process_mouse_motion(struct input_manager *im,
return;
}
struct control_msg msg;
if (convert_mouse_motion(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
if (!convert_mouse_motion(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
if (im->vfinger_down) {
struct point mouse = msg.inject_touch_event.position.point;
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@ -586,7 +633,9 @@ input_manager_process_mouse_button(struct input_manager *im,
// simulated from touch events, so it's a duplicate
return;
}
if (event->type == SDL_MOUSEBUTTONDOWN) {
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (down) {
if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller);
return;
@ -617,10 +666,36 @@ input_manager_process_mouse_button(struct input_manager *im,
}
struct control_msg msg;
if (convert_mouse_button(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
if (!convert_mouse_button(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
return;
}
// Pinch-to-zoom simulation.
//
// If Ctrl is hold when the left-click button is pressed, then
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
// button is released, an additional "virtual finger" event is generated,
// having a position inverted through the center of the screen.
//
// In other words, the center of the rotation/scaling is the center of the
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
struct point mouse = msg.inject_touch_event.position.point;
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
if (!simulate_virtual_finger(im, action, vfinger)) {
return;
}
im->vfinger_down = down;
}
}

View File

@ -25,11 +25,14 @@ struct input_manager {
bool control;
bool forward_key_repeat;
bool prefer_text;
bool legacy_paste;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
bool vfinger_down;
};
void

View File

@ -100,7 +100,7 @@ main(int argc, char *argv[]) {
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
fprintf(stderr, "Press Enter to continue...\n");
getchar();
}
#endif

View File

@ -361,12 +361,14 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
if (recorder->failed) {
// reject any new packet (this will stop the stream)
mutex_unlock(recorder->mutex);
return false;
}
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOGC("Could not allocate record packet");
mutex_unlock(recorder->mutex);
return false;
}

View File

@ -79,6 +79,7 @@ struct scrcpy_options {
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool legacy_paste;
};
#define SCRCPY_OPTIONS_DEFAULT { \
@ -123,6 +124,7 @@ struct scrcpy_options {
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.legacy_paste = false, \
}
bool

View File

@ -201,7 +201,7 @@ enable_tunnel_forward_any_port(struct server *server,
if (port < port_range.last) {
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
port, port + 1);
port, (uint16_t) (port + 1));
port++;
continue;
}

View File

@ -14,7 +14,6 @@ struct video_buffer;
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;

View File

@ -1,6 +1,6 @@
// for portability
#define _POSIX_SOURCE // for kill()
#define _BSD_SOURCE // for readlink()
// for portability (kill, readlink, strdup, strtok_r)
#define _POSIX_C_SOURCE 200809L
#define _BSD_SOURCE
// modern glibc will complain without this
#define _DEFAULT_SOURCE

View File

@ -19,6 +19,9 @@ allprojects {
google()
jcenter()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
}
task clean(type: Delete) {

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '1.15',
version: '1.16',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -e
url="$1"
sum="$2"

2
run
View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Run scrcpy generated in the specified BUILDDIR.
#
# This provides the same feature as "ninja run", except that it is possible to

View File

@ -1,2 +1,2 @@
#!/bin/bash
#!/usr/bin/env bash
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"

View File

@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 29
versionCode 17
versionName "1.15"
targetSdkVersion 30
versionCode 19
versionName "1.16"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
# This script generates the scrcpy binary "manually" (without gradle).
#
@ -12,10 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.15
SCRCPY_VERSION_NAME=1.16
PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
PLATFORM=${ANDROID_PLATFORM:-30}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes"

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Wrapper script to invoke gradle from meson
set -e

View File

@ -78,7 +78,9 @@ public final class CleanUp {
if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
if (Device.isScreenOn()) {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}
}
}

View File

@ -54,11 +54,11 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!device.isScreenOn()) {
device.injectKeycode(KeyEvent.KEYCODE_WAKEUP);
if (!Device.isScreenOn()) {
device.injectKeycode(KeyEvent.KEYCODE_POWER);
// dirty hack
// After the keycode is injected, the device is powered on asynchronously.
// After POWER is injected, the device is powered on asynchronously.
// To turn the device screen off while mirroring, the client will send a message that
// would be handled before the device is actually powered on, so its effect would
// be "canceled" once the device is turned back on.
@ -105,13 +105,13 @@ public class Controller {
}
break;
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel();
Device.expandNotificationPanel();
break;
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
Device.collapsePanels();
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
String clipboardText = Device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
@ -130,7 +130,7 @@ public class Controller {
}
break;
case ControlMessage.TYPE_ROTATE_DEVICE:
device.rotateDevice();
Device.rotateDevice();
break;
default:
// do nothing
@ -248,8 +248,8 @@ public class Controller {
}
private boolean pressBackOrTurnScreenOn() {
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP;
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_WAKEUP) {
int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
schedulePowerModeOff();
}
return device.injectKeycode(keycode);

View File

@ -25,6 +25,8 @@ public final class Device {
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
public interface RotationListener {
void onRotationChanged(int rotation);
}
@ -33,8 +35,6 @@ public final class Device {
void onClipboardTextChanged(String text);
}
private final ServiceManager serviceManager = new ServiceManager();
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private ClipboardListener clipboardListener;
@ -54,9 +54,9 @@ public final class Device {
public Device(Options options) {
displayId = options.getDisplayId();
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId);
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
int[] displayIds = serviceManager.getDisplayManager().getDisplayIds();
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
throw new InvalidDisplayIdException(displayId, displayIds);
}
@ -65,7 +65,7 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
layerStack = displayInfo.getLayerStack();
serviceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) {
synchronized (Device.this) {
@ -81,7 +81,7 @@ public final class Device {
if (options.getControl()) {
// If control is enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override
@ -166,7 +166,7 @@ public final class Device {
return false;
}
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
}
public boolean injectEvent(InputEvent event) {
@ -184,8 +184,8 @@ public final class Device {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
public boolean isScreenOn() {
return serviceManager.getPowerManager().isScreenOn();
public static boolean isScreenOn() {
return SERVICE_MANAGER.getPowerManager().isScreenOn();
}
public synchronized void setRotationListener(RotationListener rotationListener) {
@ -196,16 +196,16 @@ public final class Device {
this.clipboardListener = clipboardListener;
}
public void expandNotificationPanel() {
serviceManager.getStatusBarManager().expandNotificationsPanel();
public static void expandNotificationPanel() {
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
}
public void collapsePanels() {
serviceManager.getStatusBarManager().collapsePanels();
public static void collapsePanels() {
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
}
public String getClipboardText() {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
public static String getClipboardText() {
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
if (clipboardManager == null) {
return null;
}
@ -217,13 +217,13 @@ public final class Device {
}
public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
if (clipboardManager == null) {
return false;
}
String currentClipboard = getClipboardText();
if (currentClipboard == null || currentClipboard.equals(text)) {
if (currentClipboard != null && currentClipboard.equals(text)) {
// The clipboard already contains the requested text.
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
@ -252,8 +252,8 @@ public final class Device {
/**
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/
public void rotateDevice() {
WindowManager wm = serviceManager.getWindowManager();
public static void rotateDevice() {
WindowManager wm = SERVICE_MANAGER.getWindowManager();
boolean accelerometerRotation = !wm.isRotationFrozen();
@ -270,7 +270,7 @@ public final class Device {
}
}
public ContentProvider createSettingsProvider() {
return serviceManager.getActivityManager().createSettingsProvider();
public static ContentProvider createSettingsProvider() {
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
}
}

View File

@ -26,7 +26,7 @@ public final class Server {
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
if (options.getShowTouches() || options.getStayAwake()) {
try (ContentProvider settings = device.createSettingsProvider()) {
try (ContentProvider settings = Device.createSettingsProvider()) {
if (options.getShowTouches()) {
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up

View File

@ -16,6 +16,7 @@ public final class Workarounds {
// not instantiable
}
@SuppressWarnings("deprecation")
public static void prepareMainLooper() {
// Some devices internally create a Handler when creating an input Surface, causing an exception:
// "Can't create handler inside thread that has not called Looper.prepare()"

View File

@ -35,7 +35,7 @@ public class ContentProvider implements Closeable {
private final IBinder token;
private Method callMethod;
private boolean callMethodLegacy;
private int callMethodVersion;
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
this.manager = manager;
@ -46,12 +46,20 @@ public class ContentProvider implements Closeable {
private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) {
try {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
callMethod = provider.getClass()
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 0;
} catch (NoSuchMethodException e) {
// old version
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodLegacy = true;
// old versions
try {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 1;
} catch (NoSuchMethodException e2) {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodVersion = 2;
}
}
}
return callMethod;
@ -61,10 +69,16 @@ public class ContentProvider implements Closeable {
try {
Method method = getCallMethod();
Object[] args;
if (!callMethodLegacy) {
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
} else {
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
switch (callMethodVersion) {
case 0:
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
break;
case 1:
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
break;
default:
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
break;
}
return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {