Compare commits

...

19 Commits

Author SHA1 Message Date
5cbdd567bd Fix process execution on Windows 7
According to this bug report on Firefox:
<https://bugzilla.mozilla.org/show_bug.cgi?id=1460995>

> CreateProcess fails with ERROR_NO_SYSTEM_RESOURCES on Windows 7. It
> looks like the reason why is because PROC_THREAD_ATTRIBUTE_HANDLE_LIST
> doesn't like console handles.

To avoid the problem, do not pass console handles to
PROC_THREAD_ATTRIBUTE_HANDLE_LIST.

Refs #2783 <https://github.com/Genymobile/scrcpy/pull/2783>
Refs f801d8b312
Fixes #2838 <https://github.com/Genymobile/scrcpy/issues/2838>
2021-11-30 12:22:12 +01:00
86c91e183d Log CreateProcessW() error code on Windows
Refs #2838 <https://github.com/Genymobile/scrcpy/issues/2838>
2021-11-30 09:41:47 +01:00
cb8713eb1f Update links to v1.21 2021-11-29 22:24:06 +01:00
003e738106 Bump version to 1.21 2021-11-29 22:15:28 +01:00
30c79f2d25 Merge branch 'master' into dev 2021-11-29 22:08:43 +01:00
b25b674c45 Clarify TCP/IP mode in README 2021-11-29 22:03:58 +01:00
e2b3968c66 Always synchronize clipboard on explicit COPY/CUT
If --no-clipboard-autosync is enabled, the automatic clipboard
synchronization performed whenever the device clipboard changes is
disabled.

But on explicit COPY and CUT scrcpy shortcuts (MOD+c and MOD+x), the
clipboard should still be synchronized, so that it remains possible to
copy-paste from the device to the computer.

This is consistent with the behavior of MOD+v, which pastes the computer
clipboard to the device.

Refs #2228 <https://github.com/Genymobile/scrcpy/issues/2228>
Refs #2817 <https://github.com/Genymobile/scrcpy/pull/2817>
PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 22:00:55 +01:00
bfcb9d06c3 Expose sync mode for injecting events
Expose the inject input event mode so that it is possible to wait for
the events to be "finished". This will be necessary to read the
clipboard content only after the COPY or CUT key event is handled.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 22:00:21 +01:00
dc19ae334d Move acknowledgment handling
Handle all actions related to SET_CLIPBOARD from the dedicated method.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 21:58:30 +01:00
cbe73b0bc3 Fix set_clipboard message log
If paste is disabled on set_clipboard, then the PASTE key is not
injected, but COPY is unrelated.

PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
2021-11-29 21:55:37 +01:00
bf97a46b0c Upgrade gradle build tools to 7.0.3 2021-11-29 21:55:12 +01:00
bd56d81f72 Add --raw-key-events
This option allows to inject all input keys as key events, and ignore
text events.

Fixes #2816 <https://github.com/Genymobile/scrcpy/issues/2816>
PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:41 +01:00
5e918ac0c3 Use enum for key injection mode
There was only two key injection modes:
 - the default one
 - the mode with --prefer-text enabled

To prepare the addition of another mode (--raw-key-events), use an enum
instead of a bool.

PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:32 +01:00
0c0f62e4ab Use static maps to convert input events
This improves readability (hopefully).

PR #2831 <https://github.com/Genymobile/scrcpy/pull/2831>
2021-11-29 21:15:07 +01:00
c96505200a Fix code style in keyboard_inject 2021-11-29 21:01:57 +01:00
82a053015d Improve HID keyboard documentation
Explain how to configure the keyboard layout.
2021-11-29 21:01:15 +01:00
01ab503c22 Remove obsolete precision in README
Scrcpy is available in Debian stable packages, and several Ubuntu
versions.
2021-11-29 00:36:09 +01:00
57fb08e443 Update Simplified Chinese README to 1.20
PR #2786 <https://github.com/Genymobile/scrcpy/pull/2786>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-17 19:14:32 +01:00
02ae0db6cd Fix wrong package to install for Ubuntu/Debian
Without this package, meson fails:

    Run-time dependency libusb-1.0 found: NO (tried pkgconfig and cmake)
    app/meson.build:88:8: ERROR: Dependency "libusb-1.0" not found, tried pkgconfig and cmake

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-15 22:42:08 +01:00
30 changed files with 709 additions and 275 deletions

View File

@ -15,7 +15,7 @@ First, you need to install the required packages:
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \ gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0 libusb-dev libusb-1.0-0 libusb-1.0-0-dev
``` ```
Then clone the repo and execute the installation script Then clone the repo and execute the installation script
@ -94,7 +94,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies # client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \ sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-dev libusb-1.0-0-dev
# server build dependencies # server build dependencies
sudo apt install openjdk-11-jdk sudo apt install openjdk-11-jdk
@ -270,10 +270,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.20`][direct-scrcpy-server] - [`scrcpy-server-v1.21`][direct-scrcpy-server]
_(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_ _(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@ -1,4 +1,4 @@
# scrcpy (v1.20) # scrcpy (v1.21)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@ -65,7 +65,7 @@ Build from sources: [BUILD] ([simplified process][BUILD_simple])
### Linux ### Linux
On Debian (_testing_ and _sid_ for now) and Ubuntu (20.04): On Debian and Ubuntu:
``` ```
apt install scrcpy apt install scrcpy
@ -101,10 +101,10 @@ process][BUILD_simple]).
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available: (including `adb`) is available:
- [`scrcpy-win64-v1.20.zip`][direct-win64] - [`scrcpy-win64-v1.21.zip`][direct-win64]
_(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_ _(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -359,7 +359,8 @@ scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
#### TCP/IP (wireless) #### TCP/IP (wireless)
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a _Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP. device over TCP/IP. The device must be connected on the same network as the
computer.
##### Automatic ##### Automatic
@ -374,8 +375,8 @@ scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555 scrcpy --tcpip=192.168.1.1:5555
``` ```
If the device TCP/IP mode is disabled (or if you don't know the IP address), If adb TCP/IP mode is disabled on the device (or if you don't know the IP
connect the device over USB, then run: address), connect the device over USB, then run:
```bash ```bash
scrcpy --tcpip # without arguments scrcpy --tcpip # without arguments
@ -803,6 +804,15 @@ of the host key mapping. Therefore, if your keyboard layout does not match, it
must be configured on the Android device, in Settings → System → Languages and must be configured on the Android device, in Settings → System → Languages and
input → [Physical keyboard]. input → [Physical keyboard].
This settings page can be started directly:
```bash
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
```
However, the option is only available when the HID keyboard is enabled (or when
a physical keyboard is connected).
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915 [Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
@ -824,7 +834,13 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games) (but this will break keyboard behavior in games)
This option has no effect on HID keyboard (all key events are sent as On the contrary, you could force to always inject raw key events:
```bash
scrcpy --raw-key-events
```
These options have no effect on HID keyboard (all key events are sent as
scancodes in this mode). scancodes in this mode).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
@ -1033,7 +1049,7 @@ This README is available in other languages:
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md) - [Turkish (Turkish, `tr`) - v1.18](README.tr.md)

View File

@ -2,27 +2,41 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._
只有原版的[README](README.md)会保持最新。 只有原版的[README](README.md)会保持最新。
本文根据[ed130e05]进行翻译。 Current version is based on [65b023a]
[ed130e05]: https://github.com/Genymobile/scrcpy/blob/ed130e05d55615d6014d93f15cfcb92ad62b01d8/README.md 本文根据[65b023a]进行翻译。
# scrcpy (v1.17) [65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md
# scrcpy (v1.20)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_ 本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
专注于: 本应用专注于:
- **轻量** (原生,仅显示设备屏幕) - **轻量** 原生,仅显示设备屏幕
- **性能** (30~60fps) - **性能** 30~120fps,取决于设备
- **质量** (分辨率可达 1920×1080 或更高) - **质量** 分辨率可达 1920×1080 或更高
- **低延迟** ([35~70ms][lowlatency]) - **低延迟** [35~70ms][lowlatency]
- **快速启动** (最快 1 秒内即可显示第一帧) - **快速启动** 最快 1 秒内即可显示第一帧
- **无侵入性** (不会在设备上遗留任何程序) - **无侵入性** 不会在设备上遗留任何程序
- **用户利益** 无需帐号,无广告,无需联网
- **自由** 自由和开源软件
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
功能:
- [屏幕录制](#屏幕录制)
- 镜像时[关闭设备屏幕](#关闭设备屏幕)
- 双向[复制粘贴](#复制粘贴)
- [可配置显示质量](#采集设置)
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
- 更多 ……
## 系统要求 ## 系统要求
@ -41,6 +55,17 @@ _Only the original [README](README.md) is guaranteed to be up-to-date._
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a> <a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### 概要
- Linux: `apt install scrcpy`
- Windows: [下载][direct-win64]
- macOS: `brew install scrcpy`
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上: 在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上:
@ -70,13 +95,12 @@ apt install scrcpy
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行构建][BUILD] (不必担心,这并不困难)。 您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。
### Windows ### Windows
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。 在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
- [README](README.md#windows) - [README](README.md#windows)
@ -114,13 +138,17 @@ brew install scrcpy
你还需要在 `PATH` 内有 `adb`。如果还没有: 你还需要在 `PATH` 内有 `adb`。如果还没有:
```bash ```bash
# Homebrew >= 2.6.0 brew install android-platform-tools
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
``` ```
或者通过 [MacPorts],该方法同时设置好 adb
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
您也可以[自行构建][BUILD]。 您也可以[自行构建][BUILD]。
@ -140,7 +168,7 @@ scrcpy --help
## 功能介绍 ## 功能介绍
### 捕获设置 ### 采集设置
#### 降低分辨率 #### 降低分辨率
@ -158,7 +186,7 @@ scrcpy -m 1024 # 简写
#### 修改码率 #### 修改码率
默认码率是 8Mbps。改变视频码率 (例如改为 2Mbps) 默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps)
```bash ```bash
scrcpy --bit-rate 2M scrcpy --bit-rate 2M
@ -167,7 +195,7 @@ scrcpy -b 2M # 简写
#### 限制帧率 #### 限制帧率
要限制捕获的帧率: 要限制采集的帧率:
```bash ```bash
scrcpy --max-fps 15 scrcpy --max-fps 15
@ -194,10 +222,11 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
要锁定镜像画面的方向: 要锁定镜像画面的方向:
```bash ```bash
scrcpy --lock-video-orientation 0 # 自然方向 scrcpy --lock-video-orientation # 初始(目前)方向
scrcpy --lock-video-orientation 1 # 逆时针旋转 90° scrcpy --lock-video-orientation=0 # 自然方向
scrcpy --lock-video-orientation 2 # 18 scrcpy --lock-video-orientation=1 # 逆时针旋转 9
scrcpy --lock-video-orientation 3 # 顺时针旋转 9 scrcpy --lock-video-orientation=2 # 18
scrcpy --lock-video-orientation=3 # 顺时针旋转 90°
``` ```
只影响录制的方向。 只影响录制的方向。
@ -219,7 +248,9 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
scrcpy --encoder _ scrcpy --encoder _
``` ```
### 屏幕录制 ### 采集
#### 屏幕录制
可以在镜像的同时录制视频: 可以在镜像的同时录制视频:
@ -241,6 +272,75 @@ scrcpy -Nr file.mkv
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
在 Linux 上,可以将视频流发送至 v4l2 回环 (loopback) 设备,因此可以使用任何 v4l2 工具像摄像头一样打开安卓设备。
需安装 `v4l2loopback` 模块:
```bash
sudo apt install v4l2loopback-dkms
```
创建一个 v4l2 设备:
```bash
sudo modprobe v4l2loopback
```
这样会在 `/dev/videoN` 创建一个新的视频设备,其中 `N` 是整数。 ([更多选项](https://github.com/umlaeute/v4l2loopback#options) 可以用来创建多个设备或者特定 ID 的设备)。
列出已启用的设备:
```bash
# 需要 v4l-utils 包
v4l2-ctl --list-devices
# 简单但或许足够
ls /dev/video*
```
使用一个 v4l2 漏开启 scrcpy
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # 禁用窗口镜像
scrcpy --v4l2-sink=/dev/videoN -N # 简写
```
(将 `N` 替换为设备 ID使用 `ls /dev/video*` 命令查看)
启用之后,可以使用 v4l2 工具打开视频流:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC 可能存在一些缓冲延迟
```
例如,可以在 [OBS] 中采集视频。
[OBS]: https://obsproject.com/
#### 缓冲
可以加入缓冲,会增加延迟,但可以减少抖动 (见 [#2464])。
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
对于显示缓冲:
```bash
scrcpy --display-buffer=50 # 为显示增加 50 毫秒的缓冲
```
对于 V4L2 漏:
```bash
scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
```
### 连接 ### 连接
#### 无线 #### 无线
@ -249,16 +349,17 @@ _Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接
1. 将设备和电脑连接至同一 Wi-Fi。 1. 将设备和电脑连接至同一 Wi-Fi。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令: 2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
```bash ```bash
adb shell ip route | awk '{print $9}' adb shell ip route | awk '{print $9}'
``` ```
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。 3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
4. 断开设备的 USB 连接。 4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_. 5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_
6. 正常运行 `scrcpy`。 6. 正常运行 `scrcpy`。
可能需要降低码率和分辨率: 可能降低码率和分辨率会更好一些
```bash ```bash
scrcpy --bit-rate 2M --max-size 800 scrcpy --bit-rate 2M --max-size 800
@ -327,7 +428,7 @@ scrcpy --force-adb-forward
``` ```
类似无线网络连接,可能需要降低画面质量: 类似地,对于无线连接,可能需要降低画面质量:
``` ```
scrcpy -b2M -m800 --max-fps 15 scrcpy -b2M -m800 --max-fps 15
@ -353,7 +454,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### 无边框 #### 无边框
关闭边框: 禁用窗口边框:
```bash ```bash
scrcpy --window-borderless scrcpy --window-borderless
@ -369,7 +470,7 @@ scrcpy --always-on-top
#### 全屏 #### 全屏
您可以通过如下命令直接全屏启动scrcpy 您可以通过如下命令直接全屏启动 scrcpy
```bash ```bash
scrcpy --fullscreen scrcpy --fullscreen
@ -394,7 +495,7 @@ scrcpy --rotation 1
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。 也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
需要注意的是, _scrcpy_ 有三个不同的方向: 需要注意的是, _scrcpy_ 有三类旋转方向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。 - <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。 - [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。 - `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
@ -404,7 +505,7 @@ scrcpy --rotation 1
#### 只读 #### 只读
禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放) 禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放)
```bash ```bash
scrcpy --no-control scrcpy --no-control
@ -430,14 +531,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
#### 保持常亮 #### 保持常亮
阻止设备在连接时休眠: 阻止设备在连接时一段时间后休眠:
```bash ```bash
scrcpy --stay-awake scrcpy --stay-awake
scrcpy -w scrcpy -w
``` ```
程序关闭时会恢复设备原来的设置。 scrcpy 关闭时会恢复设备原来的设置。
#### 关闭设备屏幕 #### 关闭设备屏幕
@ -451,7 +552,7 @@ scrcpy -S
或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。 或者在任何时候按 <kbd>MOD</kbd>+<kbd>o</kbd>。
要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>. 要重新打开屏幕,按下 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。 在Android上`电源` 按钮始终能把屏幕打开。为了方便,对于在 _scrcpy_ 中发出的 `电源` 事件 (通过鼠标右键或 <kbd>MOD</kbd>+<kbd>p</kbd>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
@ -462,20 +563,17 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### 退出时息屏
#### 渲染过期帧 scrcpy 退出时关闭设备屏幕:
默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。
强制渲染所有帧 (可能导致延迟变高)
```bash ```bash
scrcpy --render-expired-frames scrcpy --power-off-on-close
``` ```
#### 显示触摸 #### 显示触摸
在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点) 在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。
Android 在 _开发者选项_ 中提供了这项功能。 Android 在 _开发者选项_ 中提供了这项功能。
@ -538,10 +636,32 @@ scrcpy --disable-screensaver
更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。 更准确的说,在按住鼠标左键时按住 <kbd>Ctrl</kbd>。直到松开鼠标左键,所有鼠标移动将以屏幕中心为原点,缩放或旋转内容 (如果应用支持)。
实际上_scrcpy_ 会在屏幕中心对称的位置上生成由“虚拟手指”发出的额外触摸事件。 实际上_scrcpy_ 会在关于屏幕中心对称的位置上“虚拟手指”发出触摸事件。
#### 物理键盘模拟 (HID)
#### 文字注入偏好 默认情况下scrcpy 使用安卓按键或文本注入这在任何情况都可以使用但仅限于ASCII字符。
在 Linux 上scrcpy 可以模拟为 Android 上的物理 USB 键盘,以提供更好地输入体验 (使用 [USB HID over AOAv2][hid-aoav2]):禁用虚拟键盘,并适用于任何字符和输入法。
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
不过,这种方法仅支持 USB 连接以及 Linux平台。
启用 HID 模式:
```bash
scrcpy --hid-keyboard
scrcpy -K # 简写
```
如果失败了 (如设备未通过 USB 连接),则自动回退至默认模式 (终端中会输出日志)。这即允许通过 USB 和 TCP/IP 连接时使用相同的命令行参数。
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### 文本注入偏好
打字的时候,系统会产生两种[事件][textevents] 打字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下或松开。 - _按键事件_ ,代表一个按键被按下或松开。
@ -557,13 +677,15 @@ scrcpy --prefer-text
(这会导致键盘在游戏中工作不正常) (这会导致键盘在游戏中工作不正常)
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 按键重复 #### 按键重复
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。 默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。
避免转发重复按键事件: 避免转发重复按键事件:
@ -571,10 +693,11 @@ scrcpy --prefer-text
scrcpy --no-key-repeat scrcpy --no-key-repeat
``` ```
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
#### 右键和中键 #### 右键和中键
默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备: 默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
```bash ```bash
scrcpy --forward-all-clicks scrcpy --forward-all-clicks
@ -587,27 +710,27 @@ scrcpy --forward-all-clicks
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。 将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。 不会有视觉反馈,终端会输出一条日志。
#### 将文件推送至设备 #### 将文件推送至设备
要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。 要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
该操作没有可见的响应,只会在控制台输出日志。 不会有视觉反馈,终端会输出一条日志。
在启动时可以修改目标目录: 在启动时可以修改目标目录:
```bash ```bash
scrcpy --push-target /sdcard/foo/bar/ scrcpy --push-target=/sdcard/Movies/
``` ```
### 音频转发 ### 音频转发
_Scrcpy_ 不支持音频。请使用 [sndcpy]. _Scrcpy_ 不支持音频。请使用 [sndcpy]
外请阅读 [issue #14]。 [issue #14]。
[sndcpy]: https://github.com/rom1v/sndcpy [sndcpy]: https://github.com/rom1v/sndcpy
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14 [issue #14]: https://github.com/Genymobile/scrcpy/issues/14
@ -632,36 +755,46 @@ _<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> \| _第4键³_
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> | | 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ | | 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ | | 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> | | 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
| 打开屏幕 | _鼠标右键²_ | | 打开屏幕 | _鼠标右键²_
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> | | 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | | 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> | | 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> | | 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | | 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
| 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd> | | 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd> | | 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd>
| 同步剪贴板并粘贴³ | <kbd>MOD</kbd>+<kbd>v</kbd> | | 剪切到剪贴板⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | | 同步剪贴板并粘贴⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示 (在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | | 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_ | | 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
| 拖放 APK 文件 | 从电脑安装 APK 文件
| 拖放非 APK 文件 | [将文件推送至设备](#push-file-to-device)
_¹双击黑边可以去除黑边_ _¹双击黑边可以去除黑边。_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_ _²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_
需要安卓版本 Android >= 7。_ 鼠标的第4键和第5键。_
_⁴需要安卓版本 Android >= 7。_
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
1. 按下 <kbd>MOD</kbd> 不放。
2. 双击 <kbd>n</kbd>。
3. 松开 <kbd>MOD</kbd>。
所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。 所有的 <kbd>Ctrl</kbd>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
@ -670,18 +803,20 @@ _³需要安卓版本 Android >= 7。_
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB` 要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`
ADB=/path/to/adb scrcpy ```bash
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 要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。
## 为什么叫 _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
@ -689,14 +824,12 @@ _³需要安卓版本 Android >= 7。_
## 如何构建? ## 如何构建?
请查看[BUILD]。 请查看 [BUILD]。
[BUILD]: BUILD.md
## 常见问题 ## 常见问题
请查看[FAQ](FAQ.md)。 请查看 [FAQ](FAQ.md)。
## 开发者 ## 开发者

View File

@ -28,6 +28,7 @@ src = [
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/acksync.c', 'src/util/acksync.c',
'src/util/file.c', 'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c', 'src/util/intr.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',

View File

@ -90,6 +90,12 @@ This provides a better experience for IME users, and allows to generate non-ASCI
It may only work over USB, and is currently only supported on Linux. It may only work over USB, and is currently only supported on Linux.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
.TP .TP
.B \-\-legacy\-paste .B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -159,6 +165,10 @@ Set the target directory for pushing files to the device by drag & drop. It is p
Default is "/sdcard/Download/". Default is "/sdcard/Download/".
.TP
.B \-\-raw\-key\-events
Inject key events for all input keys, and ignore text events.
.TP .TP
.BI "\-r, \-\-record " file .BI "\-r, \-\-record " file
Record screen to Record screen to

View File

@ -51,6 +51,7 @@
#define OPT_TUNNEL_PORT 1031 #define OPT_TUNNEL_PORT 1031
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032 #define OPT_NO_CLIPBOARD_AUTOSYNC 1032
#define OPT_TCPIP 1033 #define OPT_TCPIP 1033
#define OPT_RAW_KEY_EVENTS 1034
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@ -165,7 +166,14 @@ static const struct sc_option options[] = {
"generate non-ASCII characters, contrary to the default " "generate non-ASCII characters, contrary to the default "
"injection method.\n" "injection method.\n"
"It may only work over USB, and is currently only supported " "It may only work over USB, and is currently only supported "
"on Linux.", "on Linux.\n"
"The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started "
"directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).",
}, },
{ {
.shortopt = 'h', .shortopt = 'h',
@ -275,6 +283,11 @@ static const struct sc_option options[] = {
"drag & drop. It is passed as is to \"adb push\".\n" "drag & drop. It is passed as is to \"adb push\".\n"
"Default is \"/sdcard/Download/\".", "Default is \"/sdcard/Download/\".",
}, },
{
.longopt_id = OPT_RAW_KEY_EVENTS,
.longopt = "raw-key-events",
.text = "Inject key events for all input keys, and ignore text events."
},
{ {
.shortopt = 'r', .shortopt = 'r',
.longopt = "record", .longopt = "record",
@ -1343,7 +1356,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->push_target = optarg; opts->push_target = optarg;
break; break;
case OPT_PREFER_TEXT: case OPT_PREFER_TEXT:
opts->prefer_text = true; if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
LOGE("--prefer-text is incompatible with --raw-key-events");
return false;
}
opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT;
break;
case OPT_RAW_KEY_EVENTS:
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
LOGE("--prefer-text is incompatible with --raw-key-events");
return false;
}
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
break; break;
case OPT_ROTATION: case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) { if (!parse_rotation(optarg, &opts->rotation)) {

View File

@ -55,6 +55,12 @@ static const char *const screen_power_mode_labels[] = {
"suspend", "suspend",
}; };
static const char *const copy_key_labels[] = {
"none",
"copy",
"cut",
};
static void static void
write_position(uint8_t *buf, const struct sc_position *position) { write_position(uint8_t *buf, const struct sc_position *position) {
buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
@ -117,6 +123,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action; buf[1] = msg->inject_keycode.action;
return 2; return 2;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy_key;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: { case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buffer_write64be(&buf[1], msg->set_clipboard.sequence); buffer_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste; buf[9] = !!msg->set_clipboard.paste;
@ -131,7 +140,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data // no additional data
return 1; return 1;
@ -194,10 +202,14 @@ control_msg_log(const struct control_msg *msg) {
LOG_CMSG("back-or-screen-on %s", LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action)); KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break; break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s",
copy_key_labels[msg->get_clipboard.copy_key]);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"", LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence, msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "copy", msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text); msg->set_clipboard.text);
break; break;
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
@ -213,9 +225,6 @@ control_msg_log(const struct control_msg *msg) {
case CONTROL_MSG_TYPE_COLLAPSE_PANELS: case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels"); LOG_CMSG("collapse panels");
break; break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device"); LOG_CMSG("rotate device");
break; break;

View File

@ -41,6 +41,12 @@ enum screen_power_mode {
SCREEN_POWER_MODE_NORMAL = 2, SCREEN_POWER_MODE_NORMAL = 2,
}; };
enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_NONE,
GET_CLIPBOARD_COPY_KEY_COPY,
GET_CLIPBOARD_COPY_KEY_CUT,
};
struct control_msg { struct control_msg {
enum control_msg_type type; enum control_msg_type type;
union { union {
@ -69,6 +75,9 @@ struct control_msg {
enum android_keyevent_action action; // action for the BACK key enum android_keyevent_action action; // action for the BACK key
// screen may only be turned on on ACTION_DOWN // screen may only be turned on on ACTION_DOWN
} back_or_screen_on; } back_or_screen_on;
struct {
enum get_clipboard_copy_key copy_key;
} get_clipboard;
struct { struct {
uint64_t sequence; uint64_t sequence;
char *text; // owned, to be freed by free() char *text; // owned, to be freed by free()

View File

@ -148,16 +148,6 @@ action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU"); send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
} }
static inline void
action_copy(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
}
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN // If the screen is off, it is turned on only on ACTION_DOWN
static void static void
@ -211,6 +201,21 @@ collapse_panels(struct controller *controller) {
} }
} }
static bool
get_device_clipboard(struct controller *controller,
enum get_clipboard_copy_key copy_key) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
return true;
}
static bool static bool
set_device_clipboard(struct controller *controller, bool paste, set_device_clipboard(struct controller *controller, bool paste,
uint64_t sequence) { uint64_t sequence) {
@ -450,13 +455,15 @@ input_manager_process_key(struct input_manager *im,
} }
return; return;
case SDLK_c: case SDLK_c:
if (control && !shift && !repeat) { if (control && !shift && !repeat && down) {
action_copy(controller, action); get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_COPY);
} }
return; return;
case SDLK_x: case SDLK_x:
if (control && !shift && !repeat) { if (control && !shift && !repeat && down) {
action_cut(controller, action); get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_CUT);
} }
return; return;
case SDLK_v: case SDLK_v:

View File

@ -6,65 +6,168 @@
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */ /** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) \ #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
container_of(KP, struct sc_keyboard_inject, key_processor)
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
static bool static bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) { convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) { static const struct sc_intmap_entry actions[] = {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN); {SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN},
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP); {SDL_KEYUP, AKEY_EVENT_ACTION_UP},
FAIL; };
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
} }
return false;
} }
static bool static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text) { enum sc_key_inject_mode key_inject_mode) {
switch (from) { // Navigation keys and ENTER.
MAP(SDLK_RETURN, AKEYCODE_ENTER); // Used in all modes.
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER); static const struct sc_intmap_entry special_keys[] = {
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); {SDLK_RETURN, AKEYCODE_ENTER},
MAP(SDLK_BACKSPACE, AKEYCODE_DEL); {SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
MAP(SDLK_TAB, AKEYCODE_TAB); {SDLK_ESCAPE, AKEYCODE_ESCAPE},
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); {SDLK_BACKSPACE, AKEYCODE_DEL},
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); {SDLK_TAB, AKEYCODE_TAB},
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME); {SDLK_PAGEUP, AKEYCODE_PAGE_UP},
MAP(SDLK_END, AKEYCODE_MOVE_END); {SDLK_DELETE, AKEYCODE_FORWARD_DEL},
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); {SDLK_HOME, AKEYCODE_MOVE_HOME},
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); {SDLK_END, AKEYCODE_MOVE_END},
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT); {SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN},
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN); {SDLK_RIGHT, AKEYCODE_DPAD_RIGHT},
MAP(SDLK_UP, AKEYCODE_DPAD_UP); {SDLK_LEFT, AKEYCODE_DPAD_LEFT},
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT); {SDLK_DOWN, AKEYCODE_DPAD_DOWN},
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT); {SDLK_UP, AKEYCODE_DPAD_UP},
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT); {SDLK_LCTRL, AKEYCODE_CTRL_LEFT},
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT); {SDLK_RCTRL, AKEYCODE_CTRL_RIGHT},
{SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT},
{SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT},
};
// Numpad navigation keys.
// Used in all modes, when NumLock and Shift are disabled.
static const struct sc_intmap_entry kp_nav_keys[] = {
{SDLK_KP_0, AKEYCODE_INSERT},
{SDLK_KP_1, AKEYCODE_MOVE_END},
{SDLK_KP_2, AKEYCODE_DPAD_DOWN},
{SDLK_KP_3, AKEYCODE_PAGE_DOWN},
{SDLK_KP_4, AKEYCODE_DPAD_LEFT},
{SDLK_KP_6, AKEYCODE_DPAD_RIGHT},
{SDLK_KP_7, AKEYCODE_MOVE_HOME},
{SDLK_KP_8, AKEYCODE_DPAD_UP},
{SDLK_KP_9, AKEYCODE_PAGE_UP},
{SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL},
};
// Letters and space.
// Used in non-text mode.
static const struct sc_intmap_entry alphaspace_keys[] = {
{SDLK_a, AKEYCODE_A},
{SDLK_b, AKEYCODE_B},
{SDLK_c, AKEYCODE_C},
{SDLK_d, AKEYCODE_D},
{SDLK_e, AKEYCODE_E},
{SDLK_f, AKEYCODE_F},
{SDLK_g, AKEYCODE_G},
{SDLK_h, AKEYCODE_H},
{SDLK_i, AKEYCODE_I},
{SDLK_j, AKEYCODE_J},
{SDLK_k, AKEYCODE_K},
{SDLK_l, AKEYCODE_L},
{SDLK_m, AKEYCODE_M},
{SDLK_n, AKEYCODE_N},
{SDLK_o, AKEYCODE_O},
{SDLK_p, AKEYCODE_P},
{SDLK_q, AKEYCODE_Q},
{SDLK_r, AKEYCODE_R},
{SDLK_s, AKEYCODE_S},
{SDLK_t, AKEYCODE_T},
{SDLK_u, AKEYCODE_U},
{SDLK_v, AKEYCODE_V},
{SDLK_w, AKEYCODE_W},
{SDLK_x, AKEYCODE_X},
{SDLK_y, AKEYCODE_Y},
{SDLK_z, AKEYCODE_Z},
{SDLK_SPACE, AKEYCODE_SPACE},
};
// Numbers and punctuation keys.
// Used in raw mode only.
static const struct sc_intmap_entry numbers_punct_keys[] = {
{SDLK_HASH, AKEYCODE_POUND},
{SDLK_PERCENT, AKEYCODE_PERIOD},
{SDLK_QUOTE, AKEYCODE_APOSTROPHE},
{SDLK_ASTERISK, AKEYCODE_STAR},
{SDLK_PLUS, AKEYCODE_PLUS},
{SDLK_COMMA, AKEYCODE_COMMA},
{SDLK_MINUS, AKEYCODE_MINUS},
{SDLK_PERIOD, AKEYCODE_PERIOD},
{SDLK_SLASH, AKEYCODE_SLASH},
{SDLK_0, AKEYCODE_0},
{SDLK_1, AKEYCODE_1},
{SDLK_2, AKEYCODE_2},
{SDLK_3, AKEYCODE_3},
{SDLK_4, AKEYCODE_4},
{SDLK_5, AKEYCODE_5},
{SDLK_6, AKEYCODE_6},
{SDLK_7, AKEYCODE_7},
{SDLK_8, AKEYCODE_8},
{SDLK_9, AKEYCODE_9},
{SDLK_SEMICOLON, AKEYCODE_SEMICOLON},
{SDLK_EQUALS, AKEYCODE_EQUALS},
{SDLK_AT, AKEYCODE_AT},
{SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
{SDLK_BACKSLASH, AKEYCODE_BACKSLASH},
{SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
{SDLK_BACKQUOTE, AKEYCODE_GRAVE},
{SDLK_KP_1, AKEYCODE_NUMPAD_1},
{SDLK_KP_2, AKEYCODE_NUMPAD_2},
{SDLK_KP_3, AKEYCODE_NUMPAD_3},
{SDLK_KP_4, AKEYCODE_NUMPAD_4},
{SDLK_KP_5, AKEYCODE_NUMPAD_5},
{SDLK_KP_6, AKEYCODE_NUMPAD_6},
{SDLK_KP_7, AKEYCODE_NUMPAD_7},
{SDLK_KP_8, AKEYCODE_NUMPAD_8},
{SDLK_KP_9, AKEYCODE_NUMPAD_9},
{SDLK_KP_0, AKEYCODE_NUMPAD_0},
{SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
{SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
{SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
{SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD},
{SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
{SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
{SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
{SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
};
const struct sc_intmap_entry *entry =
SC_INTMAP_FIND_ENTRY(special_keys, from);
if (entry) {
*to = entry->value;
return true;
} }
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
// Handle Numpad events when Num Lock is disabled // Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead // If SHIFT is pressed, a text event will be sent instead
switch(from) { entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
MAP(SDLK_KP_0, AKEYCODE_INSERT); if (entry) {
MAP(SDLK_KP_1, AKEYCODE_MOVE_END); *to = entry->value;
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN); return true;
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
} }
} }
if (prefer_text && !(mod & KMOD_CTRL)) { if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) {
// do not forward alpha and space key events (unless Ctrl is pressed) // do not forward alpha and space key events (unless Ctrl is pressed)
return false; return false;
} }
@ -72,37 +175,23 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false; return false;
} }
// if ALT and META are not pressed, also handle letters and space // if ALT and META are not pressed, also handle letters and space
switch (from) { entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
MAP(SDLK_a, AKEYCODE_A); if (entry) {
MAP(SDLK_b, AKEYCODE_B); *to = entry->value;
MAP(SDLK_c, AKEYCODE_C); return true;
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
} }
if (key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
entry = SC_INTMAP_FIND_ENTRY(numbers_punct_keys, from);
if (entry) {
*to = entry->value;
return true;
}
}
return false;
} }
static enum android_metastate static enum android_metastate
@ -167,7 +256,7 @@ convert_meta_state(SDL_Keymod mod) {
static bool static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) { enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE; to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) { if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@ -176,7 +265,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
uint16_t mod = from->keysym.mod; uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod, if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
prefer_text)) { key_inject_mode)) {
return false; return false;
} }
@ -207,7 +296,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
} }
struct control_msg msg; struct control_msg msg;
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) { if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!controller_push_msg(ki->controller, &msg)) { if (!controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }
@ -219,11 +308,16 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp); struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (!ki->prefer_text) { if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
// Never inject text events
return;
}
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0'); assert(event->text[1] == '\0');
// letters and space are handled as raw key event // Letters and space are handled as raw key events
return; return;
} }
} }
@ -246,7 +340,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller, struct controller *controller,
const struct scrcpy_options *options) { const struct scrcpy_options *options) {
ki->controller = controller; ki->controller = controller;
ki->prefer_text = options->prefer_text; ki->key_inject_mode = options->key_inject_mode;
ki->forward_key_repeat = options->forward_key_repeat; ki->forward_key_repeat = options->forward_key_repeat;
ki->repeat = 0; ki->repeat = 0;

View File

@ -18,7 +18,7 @@ struct sc_keyboard_inject {
// number of repetitions. This variable keeps track of the count. // number of repetitions. This variable keeps track of the count.
unsigned repeat; unsigned repeat;
bool prefer_text; enum sc_key_inject_mode key_inject_mode;
bool forward_key_repeat; bool forward_key_repeat;
}; };

View File

@ -6,6 +6,7 @@
#include "android/input.h" #include "android/input.h"
#include "control_msg.h" #include "control_msg.h"
#include "controller.h" #include "controller.h"
#include "util/intmap.h"
#include "util/log.h" #include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */ /** Downcast mouse processor to sc_mouse_inject */
@ -32,25 +33,37 @@ convert_mouse_buttons(uint32_t state) {
return buttons; return buttons;
} }
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
static bool static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) { convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) { static const struct sc_intmap_entry actions[] = {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN); {SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN},
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP); {SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP},
FAIL; };
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
} }
return false;
} }
static bool static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) { convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) { static const struct sc_intmap_entry actions[] = {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE); {SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE},
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN); {SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN},
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP); {SDL_FINGERUP, AMOTION_EVENT_ACTION_UP},
FAIL; };
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
} }
return false;
} }
static bool static bool

View File

@ -43,7 +43,7 @@ const struct scrcpy_options scrcpy_options_default = {
.control = true, .control = true,
.display = true, .display = true,
.turn_screen_off = false, .turn_screen_off = false,
.prefer_text = false, .key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false, .window_borderless = false,
.mipmaps = true, .mipmaps = true,
.stay_awake = false, .stay_awake = false,

View File

@ -38,6 +38,20 @@ enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_HID, SC_KEYBOARD_INPUT_MODE_HID,
}; };
enum sc_key_inject_mode {
// Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events.
// This is the default mode.
SC_KEY_INJECT_MODE_MIXED,
// Inject special keys as key events.
// Inject letters and space, numbers and punctuation as text events.
SC_KEY_INJECT_MODE_TEXT,
// Inject everything as key events.
SC_KEY_INJECT_MODE_RAW,
};
#define SC_MAX_SHORTCUT_MODS 8 #define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod { enum sc_shortcut_mod {
@ -98,7 +112,7 @@ struct scrcpy_options {
bool control; bool control;
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
bool prefer_text; enum sc_key_inject_mode key_inject_mode;
bool window_borderless; bool window_borderless;
bool mipmaps; bool mipmaps;
bool stay_awake; bool stay_awake;

View File

@ -30,9 +30,7 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR); bool inherit_stderr = !perr && !(flags & SC_PROCESS_NO_STDERR);
// Add 1 per non-NULL pointer // Add 1 per non-NULL pointer
unsigned handle_count = !!pin unsigned handle_count = !!pin || !!pout || !!perr;
+ (pout || inherit_stdout)
+ (perr || inherit_stderr);
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
@ -81,23 +79,29 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
si.StartupInfo.cb = sizeof(si); si.StartupInfo.cb = sizeof(si);
HANDLE handles[3]; HANDLE handles[3];
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (inherit_stdout) {
si.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (inherit_stderr) {
si.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
if (handle_count) { if (handle_count) {
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
unsigned i = 0; unsigned i = 0;
if (pin) { if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle; si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput; handles[i++] = si.StartupInfo.hStdInput;
} }
if (pout || inherit_stdout) { if (pout) {
si.StartupInfo.hStdOutput = pout ? stdout_write_handle assert(!inherit_stdout);
: GetStdHandle(STD_OUTPUT_HANDLE); si.StartupInfo.hStdOutput = stdout_write_handle;
handles[i++] = si.StartupInfo.hStdOutput; handles[i++] = si.StartupInfo.hStdOutput;
} }
if (perr || inherit_stderr) { if (perr) {
si.StartupInfo.hStdError = perr ? stderr_write_handle assert(!inherit_stderr);
: GetStdHandle(STD_ERROR_HANDLE); si.StartupInfo.hStdError = stderr_write_handle;
handles[i++] = si.StartupInfo.hStdError; handles[i++] = si.StartupInfo.hStdError;
} }
@ -146,15 +150,22 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
goto error_free_attribute_list; goto error_free_attribute_list;
} }
BOOL bInheritHandles = handle_count > 0; BOOL bInheritHandles = handle_count > 0 || inherit_stdout || inherit_stderr;
// DETACHED_PROCESS to disable stdin, stdout and stderr DWORD dwCreationFlags = 0;
DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT if (handle_count > 0) {
: DETACHED_PROCESS; dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
}
if (!inherit_stdout && !inherit_stderr) {
// DETACHED_PROCESS to disable stdin, stdout and stderr
dwCreationFlags |= DETACHED_PROCESS;
}
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles, BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide); free(wide);
if (!ok) { if (!ok) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) { int err = GetLastError();
LOGE("CreateProcessW() error %d", err);
if (err == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY; ret = SC_PROCESS_ERROR_MISSING_BINARY;
} }
goto error_free_attribute_list; goto error_free_attribute_list;

13
app/src/util/intmap.c Normal file
View File

@ -0,0 +1,13 @@
#include "intmap.h"
const struct sc_intmap_entry *
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
int32_t key) {
for (size_t i = 0; i < len; ++i) {
const struct sc_intmap_entry *entry = &entries[i];
if (entry->key == key) {
return entry;
}
}
return NULL;
}

24
app/src/util/intmap.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef SC_ARRAYMAP_H
#define SC_ARRAYMAP_H
#include "common.h"
#include <stdint.h>
struct sc_intmap_entry {
int32_t key;
int32_t value;
};
const struct sc_intmap_entry *
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
int32_t key);
/**
* MAP is expected to be a static array of sc_intmap_entry, so that
* ARRAY_LEN(MAP) can be computed statically.
*/
#define SC_INTMAP_FIND_ENTRY(MAP, KEY) \
sc_intmap_find_entry(MAP, ARRAY_LEN(MAP), KEY)
#endif

View File

@ -89,7 +89,7 @@ static void test_options(void) {
assert(!strcmp(opts->serial, "0123456789abcdef")); assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches); assert(opts->show_touches);
assert(opts->turn_screen_off); assert(opts->turn_screen_off);
assert(opts->prefer_text); assert(opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT);
assert(!strcmp(opts->window_title, "my device")); assert(!strcmp(opts->window_title, "my device"));
assert(opts->window_x == 100); assert(opts->window_x == 100);
assert(opts->window_y == -1); assert(opts->window_y == -1);

View File

@ -210,14 +210,18 @@ static void test_serialize_collapse_panels(void) {
static void test_serialize_get_clipboard(void) { static void test_serialize_get_clipboard(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD, .type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
},
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); size_t size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_GET_CLIPBOARD,
GET_CLIPBOARD_COPY_KEY_COPY,
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }

View File

@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.android.tools.build:gradle:7.0.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21
PREBUILT_SERVER_SHA256=b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34 PREBUILT_SERVER_SHA256=dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 12000 versionCode 12100
versionName "1.20" versionName "1.21"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.20 SCRCPY_VERSION_NAME=1.21
PLATFORM_VERSION=31 PLATFORM_VERSION=31
PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION}

View File

@ -20,6 +20,10 @@ public final class ControlMessage {
public static final long SEQUENCE_INVALID = 0; public static final long SEQUENCE_INVALID = 0;
public static final int COPY_KEY_NONE = 0;
public static final int COPY_KEY_COPY = 1;
public static final int COPY_KEY_CUT = 2;
private int type; private int type;
private String text; private String text;
private int metaState; // KeyEvent.META_* private int metaState; // KeyEvent.META_*
@ -31,6 +35,7 @@ public final class ControlMessage {
private Position position; private Position position;
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
private int copyKey;
private boolean paste; private boolean paste;
private int repeat; private int repeat;
private long sequence; private long sequence;
@ -82,6 +87,13 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createGetClipboard(int copyKey) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_GET_CLIPBOARD;
msg.copyKey = copyKey;
return msg;
}
public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) { public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;
@ -151,6 +163,10 @@ public final class ControlMessage {
return vScroll; return vScroll;
} }
public int getCopyKey() {
return copyKey;
}
public boolean getPaste() { public boolean getPaste() {
return paste; return paste;
} }

View File

@ -13,6 +13,7 @@ public class ControlMessageReader {
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int GET_CLIPBOARD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
@ -70,6 +71,9 @@ public class ControlMessageReader {
case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
msg = parseBackOrScreenOnEvent(); msg = parseBackOrScreenOnEvent();
break; break;
case ControlMessage.TYPE_GET_CLIPBOARD:
msg = parseGetClipboard();
break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
msg = parseSetClipboard(); msg = parseSetClipboard();
break; break;
@ -79,7 +83,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_COLLAPSE_PANELS:
case ControlMessage.TYPE_GET_CLIPBOARD:
case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type); msg = ControlMessage.createEmpty(type);
break; break;
@ -162,6 +165,14 @@ public class ControlMessageReader {
return ControlMessage.createBackOrScreenOn(action); return ControlMessage.createBackOrScreenOn(action);
} }
private ControlMessage parseGetClipboard() {
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
return null;
}
int copyKey = toUnsigned(buffer.get());
return ControlMessage.createGetClipboard(copyKey);
}
private ControlMessage parseSetClipboard() { private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null; return null;

View File

@ -21,6 +21,7 @@ public class Controller {
private final Device device; private final Device device;
private final DesktopConnection connection; private final DesktopConnection connection;
private final DeviceMessageSender sender; private final DeviceMessageSender sender;
private final boolean clipboardAutosync;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
@ -31,9 +32,10 @@ public class Controller {
private boolean keepPowerModeOff; private boolean keepPowerModeOff;
public Controller(Device device, DesktopConnection connection) { public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) {
this.device = device; this.device = device;
this.connection = connection; this.connection = connection;
this.clipboardAutosync = clipboardAutosync;
initPointers(); initPointers();
sender = new DeviceMessageSender(connection); sender = new DeviceMessageSender(connection);
} }
@ -55,7 +57,7 @@ public class Controller {
public void control() throws IOException { public void control() throws IOException {
// on start, power on the device // on start, power on the device
if (!Device.isScreenOn()) { if (!Device.isScreenOn()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
// dirty hack // dirty hack
// After POWER is injected, the device is powered on asynchronously. // After POWER is injected, the device is powered on asynchronously.
@ -114,18 +116,10 @@ public class Controller {
Device.collapsePanels(); Device.collapsePanels();
break; break;
case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = Device.getClipboardText(); getClipboard(msg.getCopyKey());
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
break; break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
long sequence = msg.getSequence(); setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
setClipboard(msg.getText(), msg.getPaste());
if (sequence != ControlMessage.SEQUENCE_INVALID) {
// Acknowledgement requested
sender.pushAckClipboard(sequence);
}
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {
@ -149,7 +143,7 @@ public class Controller {
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
schedulePowerModeOff(); schedulePowerModeOff();
} }
return device.injectKeyEvent(action, keycode, repeat, metaState); return device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
} }
private boolean injectChar(char c) { private boolean injectChar(char c) {
@ -160,7 +154,7 @@ public class Controller {
return false; return false;
} }
for (KeyEvent event : events) { for (KeyEvent event : events) {
if (!device.injectEvent(event)) { if (!device.injectEvent(event, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -224,7 +218,7 @@ public class Controller {
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
0); 0);
return device.injectEvent(event); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }
private boolean injectScroll(Position position, int hScroll, int vScroll) { private boolean injectScroll(Position position, int hScroll, int vScroll) {
@ -247,7 +241,7 @@ public class Controller {
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_MOUSE, 0); InputDevice.SOURCE_MOUSE, 0);
return device.injectEvent(event); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }
/** /**
@ -265,7 +259,7 @@ public class Controller {
private boolean pressBackOrTurnScreenOn(int action) { private boolean pressBackOrTurnScreenOn(int action) {
if (Device.isScreenOn()) { if (Device.isScreenOn()) {
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0); return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
} }
// Screen is off // Screen is off
@ -278,10 +272,29 @@ public class Controller {
if (keepPowerModeOff) { if (keepPowerModeOff) {
schedulePowerModeOff(); schedulePowerModeOff();
} }
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER); return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
} }
private boolean setClipboard(String text, boolean paste) { private void getClipboard(int copyKey) {
// On Android >= 7, press the COPY or CUT key if requested
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT;
// Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one
device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH);
}
// If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in
// particular when COPY or CUT are injected, so it should not be synchronized twice. On Android < 7, do not synchronize at all rather than
// copying an old clipboard content.
if (!clipboardAutosync) {
String clipboardText = Device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
}
}
private boolean setClipboard(String text, boolean paste, long sequence) {
boolean ok = device.setClipboardText(text); boolean ok = device.setClipboardText(text);
if (ok) { if (ok) {
Ln.i("Device clipboard set"); Ln.i("Device clipboard set");
@ -289,7 +302,12 @@ public class Controller {
// On Android >= 7, also press the PASTE key if requested // On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE); device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
}
if (sequence != ControlMessage.SEQUENCE_INVALID) {
// Acknowledgement requested
sender.pushAckClipboard(sequence);
} }
return ok; return ok;

View File

@ -24,6 +24,10 @@ public final class Device {
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF; public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL; public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
public static final int INJECT_MODE_ASYNC = InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
@ -164,7 +168,7 @@ public final class Device {
return supportsInputEvents; return supportsInputEvents;
} }
public static boolean injectEvent(InputEvent inputEvent, int displayId) { public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) {
if (!supportsInputEvents(displayId)) { if (!supportsInputEvents(displayId)) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()"); throw new AssertionError("Could not inject input event if !supportsInputEvents()");
} }
@ -173,30 +177,31 @@ public final class Device {
return false; return false;
} }
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
} }
public boolean injectEvent(InputEvent event) { public boolean injectEvent(InputEvent event, int injectMode) {
return injectEvent(event, displayId); return injectEvent(event, displayId, injectMode);
} }
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) { public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD); InputDevice.SOURCE_KEYBOARD);
return injectEvent(event, displayId); return injectEvent(event, displayId, injectMode);
} }
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) {
return injectKeyEvent(action, keyCode, repeat, metaState, displayId); return injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode);
} }
public static boolean pressReleaseKeycode(int keyCode, int displayId) { public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId); return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode)
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
} }
public boolean pressReleaseKeycode(int keyCode) { public boolean pressReleaseKeycode(int keyCode, int injectMode) {
return pressReleaseKeycode(keyCode, displayId); return pressReleaseKeycode(keyCode, displayId, injectMode);
} }
public static boolean isScreenOn() { public static boolean isScreenOn() {
@ -272,7 +277,7 @@ public final class Device {
if (!isScreenOn()) { if (!isScreenOn()) {
return true; return true;
} }
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId); return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
} }
/** /**

View File

@ -74,7 +74,7 @@ public final class Server {
Thread controllerThread = null; Thread controllerThread = null;
Thread deviceMessageSenderThread = null; Thread deviceMessageSenderThread = null;
if (options.getControl()) { if (options.getControl()) {
final Controller controller = new Controller(device, connection); final Controller controller = new Controller(device, connection, options.getClipboardAutosync());
// asynchronous // asynchronous
controllerThread = startController(controller); controllerThread = startController(controller);

View File

@ -219,6 +219,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD); dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
dos.writeByte(ControlMessage.COPY_KEY_COPY);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -226,6 +227,7 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.next(); ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType()); Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
} }
@Test @Test