Compare commits

..

2 Commits

Author SHA1 Message Date
LuXu
13eb9b7a6a 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 16:43:27 +01:00
Alex Burdusel
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
29 changed files with 502 additions and 768 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 \
gcc git pkg-config meson ninja-build libsdl2-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
@@ -94,7 +94,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-dev
libusb-1.0-0-dev
# server build dependencies
sudo apt install openjdk-11-jdk

View File

@@ -416,27 +416,17 @@ autoadb scrcpy -s '{}'
To connect to a remote device, it is possible to connect a local `adb` client to
a remote `adb` server (provided they use the same version of the _adb_
protocol).
First, make sure the ADB server is running on the remote computer:
protocol):
```bash
adb start-server
```
Then, establish a SSH tunnel:
```bash
# local 5038 --> remote 5037
# local 27183 <-- remote 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
From another terminal:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
@@ -444,16 +434,14 @@ To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`):
```bash
# local 5038 --> remote 5037
# local 27183 --> remote 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
From another terminal:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```
@@ -967,7 +955,7 @@ This README is available in other languages:
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.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)
- [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)会保持最新。
本文根据[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_
![screenshot](assets/screenshot-debian-600.jpg)
专注于:
本应用专注于:
- **轻量** (原生,仅显示设备屏幕)
- **性能** (30~60fps)
- **质量** (分辨率可达 1920×1080 或更高)
- **低延迟** ([35~70ms][lowlatency])
- **快速启动** (最快 1 秒内即可显示第一帧)
- **无侵入性** (不会在设备上遗留任何程序)
- **轻量** 原生,仅显示设备屏幕
- **性能** 30~120fps,取决于设备
- **质量** 分辨率可达 1920×1080 或更高
- **低延迟** [35~70ms][lowlatency]
- **快速启动** 最快 1 秒内即可显示第一帧
- **无侵入性** 不会在设备上遗留任何程序
- **用户利益** 无需帐号,无广告,无需联网
- **自由** 自由和开源软件
[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>
### 概要
- Linux: `apt install scrcpy`
- Windows: [下载][direct-win64]
- macOS: `brew install scrcpy`
从源代码编译: [构建][BUILD] ([简化过程][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux
在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上:
@@ -70,13 +95,12 @@ apt install scrcpy
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
您也可以[自行构建][BUILD] (不必担心,这并不困难)。
您也可以[自行构建][BUILD] ([简化过程][BUILD_simple])。
### Windows
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
在 Windows 上,简便起见,我们提供包含了所有依赖 (包括 `adb`) 的预编译包。
- [README](README.md#windows)
@@ -114,13 +138,17 @@ brew install scrcpy
你还需要在 `PATH` 内有 `adb`。如果还没有:
```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
brew install android-platform-tools
```
或者通过 [MacPorts],该方法同时设置好 adb
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
您也可以[自行构建][BUILD]。
@@ -140,7 +168,7 @@ scrcpy --help
## 功能介绍
### 捕获设置
### 采集设置
#### 降低分辨率
@@ -158,7 +186,7 @@ scrcpy -m 1024 # 简写
#### 修改码率
默认码率是 8Mbps。改变视频码率 (例如改为 2Mbps)
默认码率是 8 Mbps。改变视频码率 (例如改为 2 Mbps)
```bash
scrcpy --bit-rate 2M
@@ -167,7 +195,7 @@ scrcpy -b 2M # 简写
#### 限制帧率
要限制捕获的帧率:
要限制采集的帧率:
```bash
scrcpy --max-fps 15
@@ -194,10 +222,11 @@ scrcpy --crop 1224:1440:0:0 # 以 (0,0) 为原点的 1224x1440 像素
要锁定镜像画面的方向:
```bash
scrcpy --lock-video-orientation 0 # 自然方向
scrcpy --lock-video-orientation 1 # 逆时针旋转 90°
scrcpy --lock-video-orientation 2 # 18
scrcpy --lock-video-orientation 3 # 顺时针旋转 9
scrcpy --lock-video-orientation # 初始(目前)方向
scrcpy --lock-video-orientation=0 # 自然方向
scrcpy --lock-video-orientation=1 # 逆时针旋转 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 _
```
### 屏幕录制
### 采集
#### 屏幕录制
可以在镜像的同时录制视频:
@@ -241,6 +272,75 @@ scrcpy -Nr file.mkv
[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。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
```bash
adb shell ip route | awk '{print $9}'
```
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
3. 启用设备的网络 adb 功能 `adb tcpip 5555`。
4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_.
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_
6. 正常运行 `scrcpy`。
可能需要降低码率和分辨率:
可能降低码率和分辨率会更好一些
```bash
scrcpy --bit-rate 2M --max-size 800
@@ -327,7 +428,7 @@ scrcpy --force-adb-forward
```
类似无线网络连接,可能需要降低画面质量:
类似地,对于无线连接,可能需要降低画面质量:
```
scrcpy -b2M -m800 --max-fps 15
@@ -353,7 +454,7 @@ scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
#### 无边框
关闭边框:
禁用窗口边框:
```bash
scrcpy --window-borderless
@@ -369,7 +470,7 @@ scrcpy --always-on-top
#### 全屏
您可以通过如下命令直接全屏启动scrcpy
您可以通过如下命令直接全屏启动 scrcpy
```bash
scrcpy --fullscreen
@@ -394,7 +495,7 @@ scrcpy --rotation 1
也可以使用 <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ 和 <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ 随时更改。
需要注意的是, _scrcpy_ 有三个不同的方向:
需要注意的是, _scrcpy_ 有三类旋转方向:
- <kbd>MOD</kbd>+<kbd>r</kbd> 请求设备在竖屏和横屏之间切换 (如果前台应用程序不支持请求的朝向,可能会拒绝该请求)。
- [`--lock-video-orientation`](#锁定屏幕方向) 改变镜像的朝向 (设备传输到电脑的画面的朝向)。这会影响录制。
- `--rotation` (或 <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) 只旋转窗口的内容。这只影响显示,不影响录制。
@@ -404,7 +505,7 @@ scrcpy --rotation 1
#### 只读
禁用电脑对设备的控制 (如键盘输入、鼠标事件和文件拖放)
禁用电脑对设备的控制 (任何可与设备交互的方式:如键盘输入、鼠标事件和文件拖放)
```bash
scrcpy --no-control
@@ -430,14 +531,14 @@ adb shell dumpsys display # 在输出中搜索 “mDisplayId=”
#### 保持常亮
阻止设备在连接时休眠:
阻止设备在连接时一段时间后休眠:
```bash
scrcpy --stay-awake
scrcpy -w
```
程序关闭时会恢复设备原来的设置。
scrcpy 关闭时会恢复设备原来的设置。
#### 关闭设备屏幕
@@ -451,7 +552,7 @@ scrcpy -S
或者在任何时候按 <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>),会 (尽最大的努力) 在短暂的延迟后将屏幕关闭。设备上的 `电源` 按钮仍然能打开设备屏幕。
@@ -462,20 +563,17 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw
```
#### 退出时息屏
#### 渲染过期帧
默认状态下,为了降低延迟, _scrcpy_ 永远渲染解码成功的最近一帧,并跳过前面任意帧。
强制渲染所有帧 (可能导致延迟变高)
scrcpy 退出时关闭设备屏幕:
```bash
scrcpy --render-expired-frames
scrcpy --power-off-on-close
```
#### 显示触摸
在演示时,可能会需要显示物理触摸点 (在物理设备上的触摸点)
在演示时,可能会需要显示 (在物理设备上的) 物理触摸点。
Android 在 _开发者选项_ 中提供了这项功能。
@@ -538,10 +636,32 @@ scrcpy --disable-screensaver
更准确的说,在按住鼠标左键时按住 <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]
- _按键事件_ ,代表一个按键被按下或松开。
@@ -557,13 +677,15 @@ scrcpy --prefer-text
(这会导致键盘在游戏中工作不正常)
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### 按键重复
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这可能会导致性能问题。
默认状态下,按住一个按键不放会生成多个重复按键事件。在某些游戏中这通常没有实际用途,且可能会导致性能问题。
避免转发重复按键事件:
@@ -571,10 +693,11 @@ scrcpy --prefer-text
scrcpy --no-key-repeat
```
该选项不影响 HID 键盘 (该模式下,按键重复由 Android 直接管理)。
#### 右键和中键
默认状态下,右键会触发返回键 (或电源键),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
默认状态下,右键会触发返回键 (或电源键开启),中键会触发 HOME 键。要禁用这些快捷键并把所有点击转发到设备:
```bash
scrcpy --forward-all-clicks
@@ -587,27 +710,27 @@ scrcpy --forward-all-clicks
将 APK 文件 (文件名以 `.apk` 结尾) 拖放到 _scrcpy_ 窗口来安装。
该操作在屏幕上不会出现任何变化,而会在控制台输出一条日志。
不会有视觉反馈,终端会输出一条日志。
#### 将文件推送至设备
要推送文件到设备的 `/sdcard/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
要推送文件到设备的 `/sdcard/Download/`,将 (非 APK) 文件拖放至 _scrcpy_ 窗口。
该操作没有可见的响应,只会在控制台输出日志。
不会有视觉反馈,终端会输出一条日志。
在启动时可以修改目标目录:
```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
[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)
| 操作 | 快捷键 |
| --------------------------------- | :------------------------------------------- |
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd> |
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_ |
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_ |
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd> |
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击¹_ |
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _鼠标中键_ |
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _鼠标右键²_ |
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> |
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd> |
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_ |
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_ |
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd> |
| 打开屏幕 | _鼠标右键²_ |
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd> |
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> |
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd> |
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> |
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> |
| 复制到剪贴板³ | <kbd>MOD</kbd>+<kbd>c</kbd> |
| 剪切到剪贴板³ | <kbd>MOD</kbd>+<kbd>x</kbd> |
| 同步剪贴板并粘贴³ | <kbd>MOD</kbd>+<kbd>v</kbd> |
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> |
| 打开/关闭FPS显示 (在 stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> |
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_ |
| 操作 | 快捷键
| --------------------------------- | :-------------------------------------------
| 全屏 | <kbd>MOD</kbd>+<kbd>f</kbd>
| 向左旋转屏幕 | <kbd>MOD</kbd>+<kbd>←</kbd> _(左箭头)_
| 向右旋转屏幕 | <kbd>MOD</kbd>+<kbd>→</kbd> _(右箭头)_
| 将窗口大小重置为1:1 (匹配像素) | <kbd>MOD</kbd>+<kbd>g</kbd>
| 将窗口大小重置为消除黑边 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _双击左键¹_
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
| 打开屏幕 | _鼠标右键²_
| 关闭设备屏幕 (但继续在电脑上显示) | <kbd>MOD</kbd>+<kbd>o</kbd>
| 打开设备屏幕 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| 旋转设备屏幕 | <kbd>MOD</kbd>+<kbd>r</kbd>
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并粘贴⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
| 拖放 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>+_按键_ 的快捷键都会被转发到设备,所以会由当前应用程序进行处理。
@@ -670,18 +803,20 @@ _³需要安卓版本 Android >= 7。_
要使用指定的 _adb_ 二进制文件,可以设置环境变量 `ADB`
ADB=/path/to/adb scrcpy
```bash
ADB=/path/to/adb scrcpy
```
要覆盖 `scrcpy-server` 的路径,可以设置 `SCRCPY_SERVER_PATH`。
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
要覆盖图标,可以设置其路径至 `SCRCPY_ICON_PATH`。
## 为什么叫 _scrcpy_
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
[`strcpy`] 复制一个 **str**ing `scrcpy` 复制一个 **scr**een。
[`strcpy`] 复制一个 **str**ing (字符串) `scrcpy` 复制一个 **scr**een (屏幕)
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
@@ -689,14 +824,12 @@ _³需要安卓版本 Android >= 7。_
## 如何构建?
请查看[BUILD]。
[BUILD]: BUILD.md
请查看 [BUILD]。
## 常见问题
请查看[FAQ](FAQ.md)。
请查看 [FAQ](FAQ.md)。
## 开发者

View File

@@ -7,7 +7,6 @@
#include "util/file.h"
#include "util/log.h"
#include "util/process_intr.h"
#include "util/str.h"
static const char *adb_command;
@@ -110,9 +109,9 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf);
}
static sc_pid
sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, unsigned inherit, sc_pipe *pout) {
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) {
int i;
sc_pid pid;
@@ -133,7 +132,7 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL;
enum sc_process_result r =
sc_process_execute_p(argv, &pid, inherit, NULL, pout, NULL);
sc_process_execute_p(argv, &pid, pin, pout, perr);
if (r != SC_PROCESS_SUCCESS) {
show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE;
@@ -144,54 +143,50 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
}
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned inherit) {
return adb_execute_p(serial, adb_cmd, len, inherit, NULL);
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL);
}
static sc_pid
adb_exec_forward(const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) {
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"forward", local, remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static sc_pid
adb_exec_forward_remove(const char *serial, uint16_t local_port,
unsigned inherit) {
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static sc_pid
adb_exec_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port, unsigned inherit) {
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", remote, local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static sc_pid
adb_exec_reverse_remove(const char *serial, const char *device_socket_name,
unsigned inherit) {
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static sc_pid
adb_exec_push(const char *serial, const char *local, const char *remote,
unsigned inherit) {
sc_pid
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
@@ -207,7 +202,7 @@ adb_exec_push(const char *serial, const char *local, const char *remote,
#endif
const char *const adb_cmd[] = {"push", local, remote};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
@@ -217,8 +212,8 @@ adb_exec_push(const char *serial, const char *local, const char *remote,
return pid;
}
static sc_pid
adb_exec_install(const char *serial, const char *local, unsigned inherit) {
sc_pid
adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
@@ -229,7 +224,7 @@ adb_exec_install(const char *serial, const char *local, unsigned inherit) {
#endif
const char *const adb_cmd[] = {"install", "-r", local};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit);
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
@@ -238,79 +233,45 @@ adb_exec_install(const char *serial, const char *local, unsigned inherit) {
return pid;
}
static sc_pid
adb_exec_get_serialno(unsigned inherit, sc_pipe *pout) {
const char *const adb_cmd[] = {"get-serialno"};
return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), inherit, pout);
static ssize_t
adb_execute_for_output(const char *serial, const char *const adb_cmd[],
size_t adb_cmd_len, char *buf, size_t buf_len,
const char *name) {
sc_pipe pout;
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
ssize_t r = sc_pipe_read_all(pout, buf, buf_len);
sc_pipe_close(pout);
if (!sc_process_check_success(pid, name, true)) {
return -1;
}
return r;
}
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) {
sc_pid pid =
adb_exec_forward(serial, local_port, device_socket_name, inherit);
return sc_process_check_success_intr(intr, pid, "adb forward", true);
}
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned inherit) {
sc_pid pid = adb_exec_forward_remove(serial, local_port, inherit);
return sc_process_check_success_intr(intr, pid, "adb forward --remove",
true);
}
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned inherit) {
sc_pid pid =
adb_exec_reverse(serial, device_socket_name, local_port, inherit);
return sc_process_check_success_intr(intr, pid, "adb reverse", true);
}
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned inherit) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name, inherit);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove",
true);
}
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned inherit) {
sc_pid pid = adb_exec_push(serial, local, remote, inherit);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned inherit) {
sc_pid pid = adb_exec_install(serial, local, inherit);
return sc_process_check_success_intr(intr, pid, "adb install", true);
static size_t
truncate_first_line(char *data, size_t len) {
data[len - 1] = '\0';
char *eol = strpbrk(data, "\r\n");
if (eol) {
*eol = '\0';
len = eol - data;
}
return len;
}
char *
adb_get_serialno(struct sc_intr *intr, unsigned inherit) {
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(inherit, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb get-serialno\"");
return NULL;
}
adb_get_serialno(void) {
char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok =
sc_process_check_success_intr(intr, pid, "adb get-serialno", true);
if (!ok) {
const char *const adb_cmd[] = {"get-serialno"};
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
buf, sizeof(buf), "get-serialno");
if (r <= 0) {
return NULL;
}
sc_str_truncate(buf, r, " \r\n");
truncate_first_line(buf, r);
return strdup(buf);
}

View File

@@ -6,43 +6,37 @@
#include <stdbool.h>
#include <inttypes.h>
#include "util/intr.h"
#include "util/process.h"
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len,
unsigned inherit);
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit);
sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned inherit);
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned inherit);
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port);
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned inherit);
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned inherit);
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name);
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned inherit);
sc_pid
adb_push(const char *serial, const char *local, const char *remote);
/**
* Execute `adb get-serialno`
*
* Return the result, to be freed by the caller, or NULL on error.
*/
sc_pid
adb_install(const char *serial, const char *local);
// Return the result of "adb get-serialno".
char *
adb_get_serialno(struct sc_intr *intr, unsigned inherit);
adb_get_serialno(void);
#endif

View File

@@ -9,6 +9,33 @@
#define SC_SOCKET_NAME "scrcpy"
static bool
enable_tunnel_reverse(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port);
return sc_process_check_success_intr(intr, pid, "adb reverse");
}
static bool
disable_tunnel_reverse(struct sc_intr *intr, const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove");
}
static bool
enable_tunnel_forward(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME);
return sc_process_check_success_intr(intr, pid, "adb forward");
}
static bool
disable_tunnel_forward(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port);
return sc_process_check_success_intr(intr, pid, "adb forward --remove");
}
static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
@@ -20,7 +47,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port, SC_STDERR)) {
if (!enable_tunnel_reverse(intr, serial, port)) {
// the command itself failed, it will fail on any port
return false;
}
@@ -51,7 +78,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
}
// failure, disable tunnel and try another port
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR)) {
if (!disable_tunnel_reverse(intr, serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@@ -81,7 +108,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
uint16_t port = port_range.first;
for (;;) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_STDERR)) {
if (enable_tunnel_forward(intr, serial, port)) {
// success
tunnel->local_port = port;
tunnel->enabled = true;
@@ -146,9 +173,9 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
bool ret;
if (tunnel->forward) {
ret = adb_forward_remove(intr, serial, tunnel->local_port, SC_STDERR);
ret = disable_tunnel_forward(intr, serial, tunnel->local_port);
} else {
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR);
ret = disable_tunnel_reverse(intr, serial);
assert(tunnel->server_socket != SC_SOCKET_NONE);
if (!net_close(tunnel->server_socket)) {

View File

@@ -569,10 +569,6 @@ sc_getopt_adapter_create_longopts(void) {
size_t out_idx = 0;
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
const struct sc_option *in = &options[i];
// If longopt_id is set, then longopt must be set
assert(!in->longopt_id || in->longopt);
if (!in->longopt) {
// The longopts array must only contain long options
continue;
@@ -675,12 +671,12 @@ print_option_usage_header(const struct sc_option *opt) {
}
}
printf("\n %s\n", buf.s);
fprintf(stderr, "\n %s\n", buf.s);
free(buf.s);
return;
error:
printf("<ERROR>\n");
fprintf(stderr, "<ERROR>\n");
}
static void
@@ -696,11 +692,11 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
char *text = sc_str_wrap_lines(opt->text, cols, 8);
if (!text) {
printf("<ERROR>\n");
fprintf(stderr, "<ERROR>\n");
return;
}
printf("%s\n", text);
fprintf(stderr, "%s\n", text);
free(text);
}
@@ -711,11 +707,11 @@ print_shortcuts_intro(unsigned cols) {
"(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4);
if (!intro) {
printf("<ERROR>\n");
fprintf(stderr, "<ERROR>\n");
return;
}
printf("%s\n", intro);
fprintf(stderr, "%s\n", intro);
free(intro);
}
@@ -725,21 +721,21 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text);
printf("\n");
fprintf(stderr, "\n");
unsigned i = 0;
while (shortcut->shortcuts[i]) {
printf(" %s\n", shortcut->shortcuts[i]);
fprintf(stderr, " %s\n", shortcut->shortcuts[i]);
++i;
};
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
if (!text) {
printf("<ERROR>\n");
fprintf(stderr, "<ERROR>\n");
return;
}
printf("%s\n", text);
fprintf(stderr, "%s\n", text);
free(text);
}
@@ -763,14 +759,14 @@ scrcpy_print_usage(const char *arg0) {
}
}
printf("Usage: %s [options]\n\n"
"Options:\n", arg0);
fprintf(stderr, "Usage: %s [options]\n\n"
"Options:\n", arg0);
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
print_option_usage(&options[i], cols);
}
// Print shortcuts section
printf("\nShortcuts:\n\n");
fprintf(stderr, "\nShortcuts:\n\n");
print_shortcuts_intro(cols);
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
print_shortcut(&shortcuts[i], cols);

View File

@@ -5,7 +5,6 @@
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
@@ -17,7 +16,6 @@ file_handler_request_destroy(struct file_handler_request *req) {
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue);
@@ -32,26 +30,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
return false;
}
ok = sc_intr_init(&file_handler->intr);
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
}
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGE("Could not strdup serial");
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
if (serial) {
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGW("Could not strdup serial");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
} else {
file_handler->serial = NULL;
}
// lazy initialization
file_handler->initialized = false;
file_handler->stopped = false;
file_handler->current_process = SC_PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@@ -62,7 +57,6 @@ void
file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial);
struct file_handler_request req;
@@ -71,6 +65,16 @@ file_handler_destroy(struct file_handler *file_handler) {
}
}
static sc_pid
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static sc_pid
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) {
@@ -102,16 +106,10 @@ file_handler_request(struct file_handler *file_handler,
static int
run_file_handler(void *data) {
struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = SC_PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
@@ -124,28 +122,43 @@ run_file_handler(void *data) {
bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty);
(void) non_empty;
sc_pid pid;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
pid = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = pid;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok =
adb_install(intr, serial, req.file, SC_STDOUT | SC_STDERR);
if (ok) {
if (sc_process_check_success(pid, "adb install", false)) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target,
SC_STDOUT | SC_STDERR);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
if (sc_process_check_success(pid, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
LOGE("Failed to push %s to %s", req.file,
file_handler->push_target);
}
}
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessarily already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
}
return 0;
@@ -170,7 +183,11 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
sc_intr_interrupt(&file_handler->intr);
if (file_handler->current_process != SC_PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}
sc_mutex_unlock(&file_handler->mutex);
}

View File

@@ -8,7 +8,6 @@
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
typedef enum {
ACTION_INSTALL_APK,
@@ -30,9 +29,8 @@ struct file_handler {
sc_cond event_cond;
bool stopped;
bool initialized;
sc_pid current_process;
struct file_handler_request_queue queue;
struct sc_intr intr;
};
bool

View File

@@ -47,9 +47,6 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL);
#endif
printf("scrcpy " SCRCPY_VERSION
" <https://github.com/Genymobile/scrcpy>\n");
struct scrcpy_cli_args args = {
.opts = scrcpy_options_default,
.help = false,
@@ -76,6 +73,8 @@ main(int argc, char *argv[]) {
return 0;
}
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all();
#endif

View File

@@ -394,11 +394,8 @@ scrcpy(struct scrcpy_options *options) {
// It is necessarily initialized here, since the device is connected
struct sc_server_info *info = &s->server.info;
const char *serial = s->server.params.serial;
assert(serial);
if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, serial,
if (!file_handler_init(&s->file_handler, options->serial,
options->push_target)) {
goto end;
}
@@ -519,7 +516,21 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
char *serialno = NULL;
const char *serial = options->serial;
if (!serial) {
serialno = adb_get_serialno();
if (!serialno) {
LOGE("Could not get device serial");
goto aoa_hid_end;
}
serial = serialno;
LOGI("Device serial: %s", serial);
}
bool ok = sc_aoa_init(&s->aoa, serial);
free(serialno);
if (!ok) {
goto aoa_hid_end;
}

View File

@@ -112,10 +112,9 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path);
return false;
}
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH,
SC_STDERR);
sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH);
free(server_path);
return ok;
return sc_process_check_success_intr(intr, pid, "adb push");
}
static const char *
@@ -199,8 +198,7 @@ execute_server(struct sc_server *server,
// Port: 5005
// Then click on "Debug"
#endif
// Inherit both stdout and stderr (all server logs are printed to stdout)
return adb_execute(serial, cmd, ARRAY_LEN(cmd), SC_STDOUT | SC_STDERR);
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
}
static bool
@@ -236,12 +234,6 @@ connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
net_close(socket);
}
if (sc_intr_is_interrupted(&server->intr)) {
// Stop immediately
break;
}
if (attempts) {
sc_mutex_lock(&server->mutex);
sc_tick deadline = sc_tick_now() + delay;
@@ -424,43 +416,17 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated");
}
static bool
sc_server_fill_serial(struct sc_server *server) {
// Retrieve the actual device immediately if not provided, so that all
// future adb commands are executed for this specific device, even if other
// devices are connected afterwards (without "more than one
// device/emulator" error)
if (!server->params.serial) {
// The serial is owned by sc_server_params, and will be freed on destroy
server->params.serial = adb_get_serialno(&server->intr, SC_STDERR);
if (!server->params.serial) {
LOGE("Could not get device serial");
return false;
}
}
return true;
}
static int
run_server(void *data) {
struct sc_server *server = data;
if (!sc_server_fill_serial(server)) {
goto error_connection_failed;
}
const struct sc_server_params *params = &server->params;
LOGD("Device serial: %s", params->serial);
bool ok = push_server(&server->intr, params->serial);
if (!ok) {
goto error_connection_failed;
}
LOGI("Server pushed");
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
params->port_range, params->force_adb_forward);
if (!ok) {

View File

@@ -11,16 +11,8 @@
#include "util/log.h"
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
sc_process_execute_p(const char *const argv[], sc_pid *pid,
int *pin, int *pout, int *perr) {
bool inherit_stdout = inherit & SC_STDOUT;
bool inherit_stderr = inherit & SC_STDERR;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT.
assert(!pout || !inherit_stdout);
// If perr is defined, then inherit MUST NOT contain SC_STDERR.
assert(!perr || !inherit_stderr);
int in[2];
int out[2];
int err[2];
@@ -98,31 +90,20 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
}
close(in[1]);
}
// Do not close stdin in the child process, this makes adb fail on
// Linux
if (pout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
} else if (!inherit_stdout) {
// Close stdout in the child process
close(STDOUT_FILENO);
}
if (perr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
}
close(err[0]);
} else if (!inherit_stderr) {
// Close stderr in the child process
close(STDERR_FILENO);
}
close(internal[0]);
enum sc_process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {

View File

@@ -1,10 +1,5 @@
// <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>
#define _WIN32_WINNT 0x0600 // For extended process API
#include "util/process.h"
#include <processthreadsapi.h>
#include <assert.h>
#include "util/log.h"
@@ -27,21 +22,8 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
}
enum sc_process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit,
sc_process_execute_p(const char *const argv[], HANDLE *handle,
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
bool inherit_stdout = inherit & SC_STDOUT;
bool inherit_stderr = inherit & SC_STDERR;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT.
assert(!pout || !inherit_stdout);
// If perr is defined, then inherit MUST NOT contain SC_STDERR.
assert(!perr || !inherit_stderr);
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin
+ (pout || inherit_stdout)
+ (perr || inherit_stderr);
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
@@ -83,92 +65,45 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit,
}
}
STARTUPINFOEXW si;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.StartupInfo.cb = sizeof(si);
HANDLE handles[3];
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
// Must be set even if handle_count == 0, so that stdin, stdout and stderr
// are NOT inherited in that case
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (handle_count) {
unsigned i = 0;
si.cb = sizeof(si);
if (pin || pout || perr) {
si.dwFlags = STARTF_USESTDHANDLES;
if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
si.hStdInput = stdin_read_handle;
}
if (pout || inherit_stdout) {
si.StartupInfo.hStdOutput = pout ? stdout_write_handle
: GetStdHandle(STD_OUTPUT_HANDLE);
handles[i++] = si.StartupInfo.hStdOutput;
if (pout) {
si.hStdOutput = stdout_write_handle;
}
if (perr || inherit_stderr) {
si.StartupInfo.hStdError = perr ? stderr_write_handle
: GetStdHandle(STD_ERROR_HANDLE);
handles[i++] = si.StartupInfo.hStdError;
if (perr) {
si.hStdError = stderr_write_handle;
}
SIZE_T size;
// Call it once to know the required buffer size
BOOL ok =
InitializeProcThreadAttributeList(NULL, 1, 0, &size)
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER;
if (!ok) {
goto error_close_stderr;
}
lpAttributeList = malloc(size);
if (!lpAttributeList) {
goto error_close_stderr;
}
ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
if (!ok) {
free(lpAttributeList);
goto error_close_stderr;
}
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, handle_count * sizeof(HANDLE),
NULL, NULL);
if (!ok) {
goto error_free_attribute_list;
}
si.lpAttributeList = lpAttributeList;
}
char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
goto error_free_attribute_list;
*handle = NULL;
goto error_close_stderr;
}
wchar_t *wide = sc_str_to_wchars(cmd);
free(cmd);
if (!wide) {
LOGC("Could not allocate wide char string");
goto error_free_attribute_list;
goto error_close_stderr;
}
BOOL bInheritHandles = handle_count > 0;
DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 0;
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide);
if (!ok) {
if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si,
&pi)) {
free(wide);
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY;
}
goto error_free_attribute_list;
}
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
goto error_close_stderr;
}
// These handles are used by the child process, close them for this process
@@ -182,15 +117,11 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit,
CloseHandle(stderr_write_handle);
}
free(wide);
*handle = pi.hProcess;
return SC_PROCESS_SUCCESS;
error_free_attribute_list:
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
error_close_stderr:
if (perr) {
CloseHandle(*perr);
@@ -237,7 +168,7 @@ sc_process_close(HANDLE handle) {
}
ssize_t
sc_pipe_read(HANDLE pipe, char *data, size_t len) {
sc_read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
@@ -246,7 +177,7 @@ sc_pipe_read(HANDLE pipe, char *data, size_t len) {
}
void
sc_pipe_close(HANDLE pipe) {
sc_close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe");
}

View File

@@ -5,8 +5,8 @@
#include "log.h"
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned inherit) {
return sc_process_execute_p(argv, pid, inherit, NULL, NULL, NULL);
sc_process_execute(const char *const argv[], sc_pid *pid) {
return sc_process_execute_p(argv, pid, NULL, NULL, NULL);
}
bool

View File

@@ -67,31 +67,20 @@ enum sc_process_result {
SC_PROCESS_ERROR_MISSING_BINARY,
};
#define SC_STDOUT (1 << 0)
#define SC_STDERR (1 << 1)
/**
* Execute the command and write the process id to `pid`
*
* The parameter `inherit` is a OR of any of SC_STDOUT and SC_STDERR. It
* indicates if stdout and stderr must be inherited from the scrcpy process (in
* other words, if the process must output to the scrcpy console).
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned inherit);
sc_process_execute(const char *const argv[], sc_pid *pid);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
* (`perr`).
*
* The parameter `inherit` has the same semantics as in `sc_process_execute()`.
* If `pout` is not NULL, then `inherit` MUST NOT contain SC_STDOUT.
* If `perr` is not NULL, then `inherit` MUST NOT contain SC_STDERR.
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
sc_process_execute_p(const char *const argv[], sc_pid *pid,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**

View File

@@ -2,7 +2,7 @@
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close) {
const char *name) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
@@ -11,40 +11,6 @@ sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
// Always pass close=false, interrupting would be racy otherwise
bool ret = sc_process_check_success(pid, name, false);
sc_intr_set_process(intr, SC_PROCESS_NONE);
if (close) {
// Close separately
sc_process_close(pid);
}
return ret;
}
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read_all(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}

View File

@@ -8,14 +8,6 @@
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close);
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len);
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len);
const char *name);
#endif

View File

@@ -291,11 +291,3 @@ error:
free(buf.s);
return NULL;
}
size_t
sc_str_truncate(char *data, size_t len, const char *endchars) {
data[len - 1] = '\0';
size_t idx = strcspn(data, endchars);
data[idx] = '\0';
return idx;
}

View File

@@ -103,15 +103,4 @@ sc_str_from_wchars(const wchar_t *s);
char *
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
/**
* Truncate the data after any of the characters from `endchars`
*
* An '\0' is always written at the end of the data, even if no newline
* character is encountered.
*
* Return the size of the resulting line.
*/
size_t
sc_str_truncate(char *data, size_t len, const char *endchars);
#endif

View File

@@ -337,32 +337,6 @@ static void test_wrap_lines(void) {
free(formatted);
}
static void test_truncate(void) {
char s[] = "hello\nworld\n!";
size_t len = sc_str_truncate(s, sizeof(s), "\n");
assert(len == 5);
assert(!strcmp("hello", s));
char s2[] = "hello\r\nworkd\r\n!";
len = sc_str_truncate(s2, sizeof(s2), "\n\r");
assert(len == 5);
assert(!strcmp("hello", s));
char s3[] = "hello world\n!";
len = sc_str_truncate(s3, sizeof(s3), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s3));
char s4[] = "hello ";
len = sc_str_truncate(s4, sizeof(s4), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s4));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@@ -382,6 +356,5 @@ int main(int argc, char *argv[]) {
test_parse_integer_with_suffix();
test_strlist_contains();
test_wrap_lines();
test_truncate();
return 0;
}

View File

@@ -1,5 +1,6 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel;
@@ -165,21 +166,14 @@ public final class CleanUp {
if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
Settings settings = new Settings(serviceManager);
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
try {
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
}
}
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
try {
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
}
}
}

View File

@@ -1,33 +0,0 @@
package com.genymobile.scrcpy;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public final class Command {
private Command() {
// not instantiable
}
public static void exec(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
}
public static String execReadLine(String... cmd) throws IOException, InterruptedException {
String result = null;
Process process = Runtime.getRuntime().exec(cmd);
Scanner scanner = new Scanner(process.getInputStream());
if (scanner.hasNextLine()) {
result = scanner.nextLine();
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return result;
}
}

View File

@@ -1,6 +1,7 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
@@ -28,7 +29,6 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
public interface RotationListener {
void onRotationChanged(int rotation);
@@ -296,7 +296,7 @@ public final class Device {
}
}
public static Settings getSettings() {
return SETTINGS;
public static ContentProvider createSettingsProvider() {
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
}
}

View File

@@ -57,18 +57,11 @@ public final class Ln {
}
}
public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) {
Log.w(TAG, message, throwable);
System.out.println(PREFIX + "WARN: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
}
}
public static void w(String message) {
w(message, null);
if (isEnabled(Level.WARN)) {
Log.w(TAG, message);
System.out.println(PREFIX + "WARN: " + message);
}
}
public static void e(String message, Throwable throwable) {

View File

@@ -1,5 +1,7 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -17,25 +19,24 @@ public final class Server {
// not instantiable
}
private static void initAndCleanUp(Options options) {
private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings();
if (options.getShowTouches()) {
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
try (ContentProvider settings = Device.createSettingsProvider()) {
if (options.getShowTouches()) {
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
} catch (SettingsException e) {
Ln.e("Could not change \"show_touches\"", e);
}
}
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try {
restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn == stayOn) {
@@ -45,25 +46,11 @@ public final class Server {
} catch (NumberFormatException e) {
restoreStayOn = 0;
}
} catch (SettingsException e) {
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
}
}
}
try {
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
} catch (IOException e) {
Ln.e("Could not configure cleanup", e);
}
}
private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
Thread initThread = startInitThread(options);
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
boolean tunnelForward = options.isTunnelForward();
@@ -95,7 +82,6 @@ public final class Server {
// this is expected on close
Ln.d("Screen streaming stopped");
} finally {
initThread.interrupt();
if (controllerThread != null) {
controllerThread.interrupt();
}
@@ -106,17 +92,6 @@ public final class Server {
}
}
private static Thread startInitThread(final Options options) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
initAndCleanUp(options);
}
});
thread.start();
return thread;
}
private static Thread startController(final Controller controller) {
Thread thread = new Thread(new Runnable() {
@Override

View File

@@ -1,84 +0,0 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Build;
import java.io.IOException;
public class Settings {
public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
private final ServiceManager serviceManager;
public Settings(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
}
private static void execSettingsPut(String table, String key, String value) throws SettingsException {
try {
Command.exec("settings", "put", table, key, value);
} catch (IOException | InterruptedException e) {
throw new SettingsException("put", table, key, value, e);
}
}
private static String execSettingsGet(String table, String key) throws SettingsException {
try {
return Command.execReadLine("settings", "get", table, key);
} catch (IOException | InterruptedException e) {
throw new SettingsException("get", table, key, null, e);
}
}
public String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
} catch (SettingsException e) {
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
}
}
return execSettingsGet(table, key);
}
public void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
} catch (SettingsException e) {
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
}
}
execSettingsPut(table, key, value);
}
public String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);
if (!value.equals(oldValue)) {
provider.putValue(table, key, value);
}
return oldValue;
} catch (SettingsException e) {
Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e);
}
}
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
}
return oldValue;
}
}

View File

@@ -1,11 +0,0 @@
package com.genymobile.scrcpy;
public class SettingsException extends Exception {
private static String createMessage(String method, String table, String key, String value) {
return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : "");
}
public SettingsException(String method, String table, String key, String value, Throwable cause) {
super(createMessage(method, table, key, value), cause);
}
}

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.SettingsException;
import android.annotation.SuppressLint;
import android.os.Bundle;
@@ -88,8 +87,7 @@ public class ContentProvider implements Closeable {
return attributionSource;
}
private Bundle call(String callMethod, String arg, Bundle extras)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
private Bundle call(String callMethod, String arg, Bundle extras) {
try {
Method method = getCallMethod();
Object[] args;
@@ -110,7 +108,7 @@ public class ContentProvider implements Closeable {
return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
Ln.e("Could not invoke method", e);
throw e;
return null;
}
}
@@ -144,31 +142,30 @@ public class ContentProvider implements Closeable {
}
}
public String getValue(String table, String key) throws SettingsException {
public String getValue(String table, String key) {
String method = getGetMethod(table);
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
try {
Bundle bundle = call(method, key, arg);
if (bundle == null) {
return null;
}
return bundle.getString("value");
} catch (Exception e) {
throw new SettingsException(table, "get", key, null, e);
Bundle bundle = call(method, key, arg);
if (bundle == null) {
return null;
}
return bundle.getString("value");
}
public void putValue(String table, String key, String value) throws SettingsException {
public void putValue(String table, String key, String value) {
String method = getPutMethod(table);
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
arg.putString(NAME_VALUE_TABLE_VALUE, value);
try {
call(method, key, arg);
} catch (Exception e) {
throw new SettingsException(table, "put", key, value, e);
call(method, key, arg);
}
public String getAndPutValue(String table, String key, String value) {
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
}
return oldValue;
}
}