Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
5cbdd567bd | |||
86c91e183d | |||
cb8713eb1f | |||
003e738106 | |||
30c79f2d25 | |||
b25b674c45 | |||
e2b3968c66 | |||
bfcb9d06c3 | |||
dc19ae334d | |||
cbe73b0bc3 | |||
bf97a46b0c | |||
bd56d81f72 | |||
5e918ac0c3 | |||
0c0f62e4ab | |||
c96505200a | |||
82a053015d | |||
01ab503c22 | |||
57fb08e443 | |||
02ae0db6cd |
10
BUILD.md
10
BUILD.md
@ -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:
|
||||||
|
36
README.md
36
README.md
@ -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)
|
||||||
|
|
||||||
|
@ -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_。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
它专注于:
|
本应用专注于:
|
||||||
|
|
||||||
- **轻量** (原生,仅显示设备屏幕)
|
- **轻量**: 原生,仅显示设备屏幕
|
||||||
- **性能** (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 # 180°
|
scrcpy --lock-video-orientation=1 # 逆时针旋转 90°
|
||||||
scrcpy --lock-video-orientation 3 # 顺时针旋转 90°
|
scrcpy --lock-video-orientation=2 # 180°
|
||||||
|
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)。
|
||||||
|
|
||||||
|
|
||||||
## 开发者
|
## 开发者
|
||||||
|
@ -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',
|
||||||
|
10
app/scrcpy.1
10
app/scrcpy.1
@ -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
|
||||||
|
@ -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)) {
|
||||||
|
@ -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;
|
||||||
|
@ -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()
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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
13
app/src/util/intmap.c
Normal 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
24
app/src/util/intmap.h
Normal 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
|
@ -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);
|
||||||
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -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 {
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user