Compare commits
26 Commits
rotatedevi
...
pr1002
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9786e5034 | ||
|
|
78a320a763 | ||
|
|
7d5845196e | ||
|
|
31bd95022b | ||
|
|
4687a0ebac | ||
|
|
6965d051ae | ||
|
|
71df3175bd | ||
|
|
a0f8e7fd9f | ||
|
|
e4cebc8d4c | ||
|
|
ba1b36758e | ||
|
|
ad92a192b5 | ||
|
|
81a8e503e5 | ||
|
|
242e57d69b | ||
|
|
024c2f7e6b | ||
|
|
1eae139b6e | ||
|
|
419c869c9c | ||
|
|
929bf48c7e | ||
|
|
d950383b72 | ||
|
|
61274a7cdb | ||
|
|
64bcac9157 | ||
|
|
3259c60b22 | ||
|
|
e0b117de13 | ||
|
|
684e0abb74 | ||
|
|
86fcd89d80 | ||
|
|
8bc056b9c6 | ||
|
|
8ec077ce1b |
8
BUILD.md
8
BUILD.md
@@ -40,7 +40,7 @@ Install the required packages from your package manager.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# runtime dependencies
|
# runtime dependencies
|
||||||
sudo apt install ffmpeg libsdl2-2.0-0
|
sudo apt install ffmpeg libsdl2-2.0-0 adb
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
sudo apt install gcc git pkg-config meson ninja-build \
|
sudo apt install gcc git pkg-config meson ninja-build \
|
||||||
@@ -233,10 +233,10 @@ You can then [run](README.md#run) _scrcpy_.
|
|||||||
|
|
||||||
## Prebuilt server
|
## Prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.11`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.12.1`][direct-scrcpy-server]
|
||||||
_(SHA-256: ff3a454012e91d9185cfe8ca7691cea16c43a7dcc08e92fa47ab9f0ea675abd1)_
|
_(SHA-256: 63e569c8a1d0c1df31d48c4214871c479a601782945fed50c1e61167d78266ea)_
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-server-v1.11
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-server-v1.12.1
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
84
FAQ.ko.md
Normal file
84
FAQ.ko.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 자주하는 질문 (FAQ)
|
||||||
|
|
||||||
|
다음은 자주 제보되는 문제들과 그들의 현황입니다.
|
||||||
|
|
||||||
|
|
||||||
|
### Window 운영체제에서, 디바이스가 발견되지 않습니다.
|
||||||
|
|
||||||
|
가장 흔한 제보는 `adb`에 발견되지 않는 디바이스 혹은 권한 관련 문제입니다.
|
||||||
|
다음 명령어를 호출하여 모든 것들에 이상이 없는지 확인하세요:
|
||||||
|
|
||||||
|
adb devices
|
||||||
|
|
||||||
|
Window는 당신의 디바이스를 감지하기 위해 [drivers]가 필요할 수도 있습니다.
|
||||||
|
|
||||||
|
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||||
|
|
||||||
|
|
||||||
|
### 내 디바이스의 미러링만 가능하고, 디바이스와 상호작용을 할 수 없습니다.
|
||||||
|
|
||||||
|
일부 디바이스에서는, [simulating input]을 허용하기 위해서 한가지 옵션을 활성화해야 할 수도 있습니다.
|
||||||
|
개발자 옵션에서 (developer options) 다음을 활성화 하세요:
|
||||||
|
|
||||||
|
> **USB debugging (Security settings)**
|
||||||
|
> _권한 부여와 USB 디버깅을 통한 simulating input을 허용한다_
|
||||||
|
|
||||||
|
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
|
||||||
|
### 마우스 클릭이 다른 곳에 적용됩니다.
|
||||||
|
|
||||||
|
Mac 운영체제에서, HiDPI support 와 여러 스크린 창이 있는 경우, 입력 위치가 잘못 파악될 수 있습니다.
|
||||||
|
[issue 15]를 참고하세요.
|
||||||
|
|
||||||
|
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
|
||||||
|
|
||||||
|
차선책은 HiDPI support을 비활성화 하고 build하는 방법입니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
meson x --buildtype release -Dhidpi_support=false
|
||||||
|
```
|
||||||
|
|
||||||
|
하지만, 동영상은 낮은 해상도로 재생될 것 입니다.
|
||||||
|
|
||||||
|
|
||||||
|
### HiDPI display의 화질이 낮습니다.
|
||||||
|
|
||||||
|
Windows에서는, [scaling behavior] 환경을 설정해야 할 수도 있습니다.
|
||||||
|
|
||||||
|
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
|
||||||
|
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
||||||
|
|
||||||
|
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||||
|
|
||||||
|
|
||||||
|
### KWin compositor가 실행되지 않습니다
|
||||||
|
|
||||||
|
Plasma Desktop에서는,_scrcpy_ 가 실행중에는 compositor가 비활성화 됩니다.
|
||||||
|
|
||||||
|
차석책으로는, ["Block compositing"를 비활성화하세요][kwin].
|
||||||
|
|
||||||
|
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
|
||||||
|
|
||||||
|
|
||||||
|
###비디오 스트림을 열 수 없는 에러가 발생합니다.(Could not open video stream).
|
||||||
|
|
||||||
|
여러가지 원인이 있을 수 있습니다. 가장 흔한 원인은 디바이스의 하드웨어 인코더(hardware encoder)가
|
||||||
|
주어진 해상도를 인코딩할 수 없는 경우입니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR: Exception on thread Thread[main,5,main]
|
||||||
|
android.media.MediaCodec$CodecException: Error 0xfffffc0e
|
||||||
|
...
|
||||||
|
Exit due to uncaughtException in main thread:
|
||||||
|
ERROR: Could not open video stream
|
||||||
|
INFO: Initial texture: 1080x2336
|
||||||
|
```
|
||||||
|
|
||||||
|
더 낮은 해상도로 시도 해보세요:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -m 1920
|
||||||
|
scrcpy -m 1024
|
||||||
|
scrcpy -m 800
|
||||||
|
```
|
||||||
498
README.ko.md
Normal file
498
README.ko.md
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
# scrcpy (v1.11)
|
||||||
|
|
||||||
|
This document will be updated frequently along with the original Readme file
|
||||||
|
이 문서는 원어 리드미 파일의 업데이트에 따라 종종 업데이트 될 것입니다
|
||||||
|
|
||||||
|
이 어플리케이션은 UBS ( 혹은 [TCP/IP][article-tcpip] ) 로 연결된 Android 디바이스를 화면에 보여주고 관리하는 것을 제공합니다.
|
||||||
|
_GNU/Linux_, _Windows_ 와 _macOS_ 상에서 작동합니다.
|
||||||
|
(아래 설명에서 디바이스는 안드로이드 핸드폰을 의미합니다.)
|
||||||
|
|
||||||
|
[article-tcpip]:https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
주요 기능은 다음과 같습니다.
|
||||||
|
|
||||||
|
- **가벼움** (기본적이며 디바이스의 화면만을 보여줌)
|
||||||
|
- **뛰어난 성능** (30~60fps)
|
||||||
|
- **높은 품질** (1920×1080 이상의 해상도)
|
||||||
|
- **빠른 반응 속도** ([35~70ms][lowlatency])
|
||||||
|
- **짧은 부팅 시간** (첫 사진을 보여주는데 최대 1초 소요됨)
|
||||||
|
- **장치 설치와는 무관함** (디바이스에 설치하지 않아도 됨)
|
||||||
|
|
||||||
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
|
|
||||||
|
## 요구사항
|
||||||
|
|
||||||
|
안드로이드 장치는 최소 API 21 (Android 5.0) 을 필요로 합니다.
|
||||||
|
|
||||||
|
디바이스에 [adb debugging][enable-adb]이 가능한지 확인하십시오.
|
||||||
|
|
||||||
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
|
||||||
|
어떤 디바이스에서는, 키보드와 마우스를 사용하기 위해서 [추가 옵션][control] 이 필요하기도 합니다.
|
||||||
|
|
||||||
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
|
||||||
|
## 앱 설치하기
|
||||||
|
|
||||||
|
|
||||||
|
### Linux (리눅스)
|
||||||
|
|
||||||
|
리눅스 상에서는 보통 [어플을 직접 설치][BUILD] 해야합니다. 어렵지 않으므로 걱정하지 않아도 됩니다.
|
||||||
|
|
||||||
|
[BUILD]:https://github.com/Genymobile/scrcpy/blob/master/BUILD.md
|
||||||
|
|
||||||
|
[Snap] 패키지가 가능합니다 : [`scrcpy`][snap-link].
|
||||||
|
|
||||||
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
|
|
||||||
|
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
|
||||||
|
|
||||||
|
Arch Linux에서, [AUR] 패키지가 가능합니다 : [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
|
Gentoo에서 ,[Ebuild] 가 가능합니다 : [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
|
|
||||||
|
### Windows (윈도우)
|
||||||
|
|
||||||
|
윈도우 상에서, 간단하게 설치하기 위해 종속성이 있는 사전 구축된 아카이브가 제공됩니다 (`adb` 포함) :
|
||||||
|
해당 파일은 Readme원본 링크를 통해서 다운로드가 가능합니다.
|
||||||
|
- [`scrcpy-win`][direct-win]
|
||||||
|
|
||||||
|
[direct-win]: https://github.com/Genymobile/scrcpy/blob/master/README.md#windows
|
||||||
|
|
||||||
|
|
||||||
|
[어플을 직접 설치][BUILD] 할 수도 있습니다.
|
||||||
|
|
||||||
|
|
||||||
|
### macOS (맥 OS)
|
||||||
|
|
||||||
|
이 어플리케이션은 아래 사항을 따라 설치한다면 [Homebrew] 에서도 사용 가능합니다 :
|
||||||
|
|
||||||
|
[Homebrew]: https://brew.sh/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
`PATH` 로부터 접근 가능한 `adb` 가 필요합니다. 아직 설치하지 않았다면 다음을 따라 설치해야 합니다 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew cask install android-platform-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
[어플을 직접 설치][BUILD] 할 수도 있습니다.
|
||||||
|
|
||||||
|
|
||||||
|
## 실행
|
||||||
|
|
||||||
|
안드로이드 디바이스를 연결하고 실행하십시오:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
다음과 같이 명령창 옵션 기능도 제공합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## 기능
|
||||||
|
|
||||||
|
### 캡쳐 환경 설정
|
||||||
|
|
||||||
|
|
||||||
|
###사이즈 재정의
|
||||||
|
|
||||||
|
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
|
||||||
|
|
||||||
|
너비와 높이를 제한하기 위해 특정 값으로 지정할 수 있습니다 (e.g. 1024) :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-size 1024
|
||||||
|
scrcpy -m 1024 # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
이 외의 크기도 디바이스의 가로 세로 비율이 유지된 상태에서 계산됩니다.
|
||||||
|
이러한 방식으로 디바이스 상에서 1920×1080 는 모니터 상에서1024×576로 미러링될 것 입니다.
|
||||||
|
|
||||||
|
|
||||||
|
### bit-rate 변경
|
||||||
|
|
||||||
|
기본 bit-rate 는 8 Mbps입니다. 비디오 bit-rate 를 변경하기 위해선 다음과 같이 입력하십시오 (e.g. 2 Mbps로 변경):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M
|
||||||
|
scrcpy -b 2M # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
###프레임 비율 제한
|
||||||
|
|
||||||
|
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Crop (잘라내기)
|
||||||
|
|
||||||
|
디바이스 화면은 화면의 일부만 미러링하기 위해 잘라질 것입니다.
|
||||||
|
|
||||||
|
예를 들어, *Oculus Go* 의 한 쪽 눈만 미러링할 때 유용합니다 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||||
|
scrcpy -c 1224:1440:0:0 # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
만약 `--max-size` 도 지정하는 경우, 잘라낸 다음에 재정의된 크기가 적용될 것입니다.
|
||||||
|
|
||||||
|
|
||||||
|
### 화면 녹화
|
||||||
|
|
||||||
|
미러링하는 동안 화면 녹화를 할 수 있습니다 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --record file.mp4
|
||||||
|
scrcpy -r file.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
녹화하는 동안 미러링을 멈출 수 있습니다 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-display --record file.mp4
|
||||||
|
scrcpy -Nr file.mkv
|
||||||
|
# Ctrl+C 로 녹화를 중단할 수 있습니다.
|
||||||
|
# 윈도우 상에서 Ctrl+C 는 정상정으로 종료되지 않을 수 있으므로, 디바이스 연결을 해제하십시오.
|
||||||
|
```
|
||||||
|
|
||||||
|
"skipped frames" 은 모니터 화면에 보여지지 않았지만 녹화되었습니다 ( 성능 문제로 인해 ). 프레임은 디바이스 상에서 _타임 스탬프 ( 어느 시점에 데이터가 존재했다는 사실을 증명하기 위해 특정 위치에 시각을 표시 )_ 되었으므로, [packet delay
|
||||||
|
variation] 은 녹화된 파일에 영향을 끼치지 않습니다.
|
||||||
|
|
||||||
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
## 연결
|
||||||
|
|
||||||
|
### 무선연결
|
||||||
|
|
||||||
|
_Scrcpy_ 장치와 정보를 주고받기 위해 `adb` 를 사용합니다. `adb` 는 TCIP/IP 를 통해 디바이스와 [연결][connect] 할 수 있습니다 :
|
||||||
|
|
||||||
|
1. 컴퓨터와 디바이스를 동일한 Wi-Fi 에 연결합니다.
|
||||||
|
2. 디바이스의 IP address 를 확인합니다 (설정 → 내 기기 → 상태 / 혹은 인터넷에 '내 IP'검색 시 확인 가능합니다. ).
|
||||||
|
3. TCP/IP 를 통해 디바이스에서 adb 를 사용할 수 있게 합니다: `adb tcpip 5555`.
|
||||||
|
4. 디바이스 연결을 해제합니다.
|
||||||
|
5. adb 를 통해 디바이스에 연결을 합니다\: `adb connect DEVICE_IP:5555` _(`DEVICE_IP` 대신)_.
|
||||||
|
6. `scrcpy` 실행합니다.
|
||||||
|
|
||||||
|
다음은 bit-rate 와 해상도를 줄이는데 유용합니다 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
|
scrcpy -b2M -m800 # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 여러 디바이스 사용 가능
|
||||||
|
|
||||||
|
만약에 여러 디바이스들이 `adb devices` 목록에 표시되었다면, _serial_ 을 명시해야합니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 0123456789abcdef
|
||||||
|
scrcpy -s 0123456789abcdef # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
_scrcpy_ 로 여러 디바이스를 연결해 사용할 수 있습니다.
|
||||||
|
|
||||||
|
|
||||||
|
#### SSH tunnel
|
||||||
|
|
||||||
|
떨어져 있는 디바이스와 연결하기 위해서는, 로컬 `adb` client와 떨어져 있는 `adb` 서버를 연결해야 합니다. (디바이스와 클라이언트가 동일한 버전의 _adb_ protocol을 사용할 경우에 제공됩니다.):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # 5037의 로컬 local adb server를 중단
|
||||||
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
|
# 실행 유지
|
||||||
|
```
|
||||||
|
|
||||||
|
다른 터미널에서는 :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
무선 연결과 동일하게, 화질을 줄이는 것이 나을 수 있습니다:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -b2M -m800 --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
## Window에서의 배치
|
||||||
|
|
||||||
|
### 맞춤형 window 제목
|
||||||
|
|
||||||
|
기본적으로, window의 이름은 디바이스의 모델명 입니다.
|
||||||
|
다음의 명령어를 통해 변경하세요.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-title 'My device'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 배치와 크기
|
||||||
|
|
||||||
|
초기 window창의 배치와 크기는 다음과 같이 설정할 수 있습니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 경계 없애기
|
||||||
|
|
||||||
|
윈도우 장식(경계선 등)을 다음과 같이 제거할 수 있습니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-borderless
|
||||||
|
```
|
||||||
|
|
||||||
|
### 항상 모든 윈도우 위에 실행창 고정
|
||||||
|
|
||||||
|
이 어플리케이션의 윈도우 창은 다음의 명령어로 다른 window 위에 디스플레이 할 수 있습니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --always-on-top
|
||||||
|
scrcpy -T # 축약 버전
|
||||||
|
```
|
||||||
|
|
||||||
|
### 전체 화면
|
||||||
|
|
||||||
|
이 어플리케이션은 전체화면으로 바로 시작할 수 있습니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --fullscreen
|
||||||
|
scrcpy -f # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
전체 화면은 `Ctrl`+`f`키로 끄거나 켤 수 있습니다.
|
||||||
|
|
||||||
|
|
||||||
|
## 다른 미러링 옵션
|
||||||
|
|
||||||
|
### 읽기 전용(Read-only)
|
||||||
|
|
||||||
|
권한을 제한하기 위해서는 (디바이스와 관련된 모든 것: 입력 키, 마우스 이벤트 , 파일의 드래그 앤 드랍(drag&drop)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-control
|
||||||
|
scrcpy -n
|
||||||
|
```
|
||||||
|
|
||||||
|
### 화면 끄기
|
||||||
|
|
||||||
|
미러링을 실행하는 와중에 디바이스의 화면을 끌 수 있게 하기 위해서는
|
||||||
|
다음의 커맨드 라인 옵션을(command line option) 입력하세요:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off
|
||||||
|
scrcpy -S
|
||||||
|
```
|
||||||
|
|
||||||
|
혹은 `Ctrl`+`o`을 눌러 언제든지 디바이스의 화면을 끌 수 있습니다.
|
||||||
|
|
||||||
|
다시 화면을 켜기 위해서는`POWER` (혹은 `Ctrl`+`p`)를 누르세요.
|
||||||
|
|
||||||
|
|
||||||
|
### 유효기간이 지난 프레임 제공 (Render expired frames)
|
||||||
|
|
||||||
|
디폴트로, 대기시간을 최소화하기 위해 _scrcpy_ 는 항상 마지막으로 디코딩된 프레임을 제공합니다
|
||||||
|
과거의 프레임은 하나씩 삭제합니다.
|
||||||
|
|
||||||
|
모든 프레임을 강제로 렌더링하기 위해서는 (대기 시간이 증가될 수 있습니다)
|
||||||
|
다음의 명령어를 사용하세요:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --render-expired-frames
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 화면에 터치 나타내기
|
||||||
|
|
||||||
|
발표를 할 때, 물리적인 기기에 한 물리적 터치를 나타내는 것이 유용할 수 있습니다.
|
||||||
|
|
||||||
|
안드로이드 운영체제는 이런 기능을 _Developers options_에서 제공합니다.
|
||||||
|
|
||||||
|
_Scrcpy_ 는 이런 기능을 시작할 때와 종료할 때 옵션으로 제공합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --show-touches
|
||||||
|
scrcpy -t
|
||||||
|
```
|
||||||
|
|
||||||
|
화면에 _물리적인 터치만_ 나타나는 것에 유의하세요 (손가락을 디바이스에 대는 행위).
|
||||||
|
|
||||||
|
|
||||||
|
### 입력 제어
|
||||||
|
|
||||||
|
#### 복사-붙여넣기
|
||||||
|
|
||||||
|
컴퓨터와 디바이스 양방향으로 클립보드를 복사하는 것이 가능합니다:
|
||||||
|
|
||||||
|
- `Ctrl`+`c` 디바이스의 클립보드를 컴퓨터로 복사합니다;
|
||||||
|
- `Ctrl`+`Shift`+`v` 컴퓨터의 클립보드를 디바이스로 복사합니다;
|
||||||
|
- `Ctrl`+`v` 컴퓨터의 클립보드를 text event 로써 _붙여넣습니다_ ( 그러나, ASCII 코드가 아닌 경우 실행되지 않습니다 )
|
||||||
|
|
||||||
|
#### 텍스트 삽입 우선 순위
|
||||||
|
|
||||||
|
텍스트를 입력할 때 생성되는 두 가지의 [events][textevents] 가 있습니다:
|
||||||
|
- _key events_, 키가 눌려있는 지에 대한 신호;
|
||||||
|
- _text events_, 텍스트가 입력되었는지에 대한 신호.
|
||||||
|
|
||||||
|
기본적으로, 글자들은 key event 를 이용해 입력되기 때문에, 키보드는 게임에서처럼 처리합니다 ( 보통 WASD 키에 대해서 ).
|
||||||
|
|
||||||
|
그러나 이는 [issues 를 발생][prefertext]시킵니다. 이와 관련된 문제를 접할 경우, 아래와 같이 피할 수 있습니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --prefer-text
|
||||||
|
```
|
||||||
|
|
||||||
|
( 그러나 이는 게임에서의 처리를 중단할 수 있습니다 )
|
||||||
|
|
||||||
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
### 파일 드랍
|
||||||
|
|
||||||
|
### APK 실행하기
|
||||||
|
|
||||||
|
APK를 실행하기 위해서는, APK file(파일명이`.apk`로 끝나는 파일)을 드래그하고 _scrcpy_ window에 드랍하세요 (drag and drop)
|
||||||
|
|
||||||
|
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
|
||||||
|
|
||||||
|
### 디바이스에 파일 push하기
|
||||||
|
|
||||||
|
디바이스의`/sdcard/`에 파일을 push하기 위해서는,
|
||||||
|
APK파일이 아닌 파일을_scrcpy_ window에 드래그하고 드랍하세요.(drag and drop).
|
||||||
|
|
||||||
|
시각적인 피드백은 없고,log 하나가 콘솔에 출력될 것입니다.
|
||||||
|
|
||||||
|
해당 디렉토리는 시작할 때 변경이 가능합니다:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --push-target /sdcard/foo/bar/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 오디오의 전달
|
||||||
|
|
||||||
|
_scrcpy_는 오디오를 직접 전달해주지 않습니다. [USBaudio] (Linux-only)를 사용하세요.
|
||||||
|
|
||||||
|
추가적으로 [issue #14]를 참고하세요.
|
||||||
|
|
||||||
|
[USBaudio]: https://github.com/rom1v/usbaudio
|
||||||
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
|
|
||||||
|
## 단축키
|
||||||
|
|
||||||
|
| 실행내용 | 단축키 | 단축키 (macOS)
|
||||||
|
| -------------------------------------- |:----------------------------- |:-----------------------------
|
||||||
|
| 전체화면 모드로 전환 | `Ctrl`+`f` | `Cmd`+`f`
|
||||||
|
| window를 1:1비율로 전환하기(픽셀 맞춤) | `Ctrl`+`g` | `Cmd`+`g`
|
||||||
|
| 검은 공백 제거 위한 window 크기 조정 | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|
||||||
|
|`HOME` 클릭 | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
||||||
|
| `BACK` 클릭 | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
||||||
|
| `APP_SWITCH` 클릭 | `Ctrl`+`s` | `Cmd`+`s`
|
||||||
|
| `MENU` 클릭 | `Ctrl`+`m` | `Ctrl`+`m`
|
||||||
|
| `VOLUME_UP` 클릭 | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
||||||
|
| `VOLUME_DOWN` 클릭 | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
||||||
|
| `POWER` 클릭 | `Ctrl`+`p` | `Cmd`+`p`
|
||||||
|
| 전원 켜기 | _Right-click²_ | _Right-click²_
|
||||||
|
| 미러링 중 디바이스 화면 끄기 | `Ctrl`+`o` | `Cmd`+`o`
|
||||||
|
| 알림 패널 늘리기 | `Ctrl`+`n` | `Cmd`+`n`
|
||||||
|
| 알림 패널 닫기 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||||
|
| 디바이스의 clipboard 컴퓨터로 복사하기 | `Ctrl`+`c` | `Cmd`+`c`
|
||||||
|
| 컴퓨터의 clipboard 디바이스에 붙여넣기 | `Ctrl`+`v` | `Cmd`+`v`
|
||||||
|
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||||
|
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
||||||
|
|
||||||
|
_¹검은 공백을 제거하기 위해서는 그 부분을 더블 클릭하세요_
|
||||||
|
_²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상태에서는 뒤로 돌아갑니다.
|
||||||
|
|
||||||
|
## 맞춤 경로 (custom path)
|
||||||
|
|
||||||
|
특정한 _adb_ binary를 사용하기 위해서는, 그것의 경로를 환경변수로 설정하세요.
|
||||||
|
`ADB`:
|
||||||
|
|
||||||
|
ADB=/path/to/adb scrcpy
|
||||||
|
|
||||||
|
`scrcpy-server.jar`파일의 경로에 오버라이드 하기 위해서는, 그것의 경로를 `SCRCPY_SERVER_PATH`에 저장하세요.
|
||||||
|
|
||||||
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|
||||||
|
|
||||||
|
## _scrcpy_ 인 이유?
|
||||||
|
|
||||||
|
한 동료가 [gnirehtet]와 같이 발음하기 어려운 이름을 찾을 수 있는지 도발했습니다.
|
||||||
|
|
||||||
|
[`strcpy`] 는 **str**ing을 copy하고; `scrcpy`는 **scr**een을 copy합니다.
|
||||||
|
|
||||||
|
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||||
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 빌드하는 방법?
|
||||||
|
|
||||||
|
[BUILD]을 참고하세요.
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
|
||||||
|
## 흔한 issue
|
||||||
|
|
||||||
|
[FAQ](FAQ.md)을 참고하세요.
|
||||||
|
|
||||||
|
|
||||||
|
## 개발자들
|
||||||
|
|
||||||
|
[developers page]를 참고하세요.
|
||||||
|
|
||||||
|
[developers page]: DEVELOP.md
|
||||||
|
|
||||||
|
|
||||||
|
## 라이선스
|
||||||
|
|
||||||
|
Copyright (C) 2018 Genymobile
|
||||||
|
Copyright (C) 2018-2019 Romain Vimont
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
## 관련 글 (articles)
|
||||||
|
|
||||||
|
- [scrcpy 소개][article-intro]
|
||||||
|
- [무선으로 연결하는 Scrcpy][article-tcpip]
|
||||||
|
|
||||||
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
21
README.md
21
README.md
@@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.11)
|
# scrcpy (v1.12.1)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected on
|
This application provides display and control of Android devices connected on
|
||||||
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||||
@@ -62,13 +62,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
|||||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||||
(including `adb`) are available:
|
(including `adb`) are available:
|
||||||
|
|
||||||
- [`scrcpy-win32-v1.11.zip`][direct-win32]
|
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
|
||||||
_(SHA-256: f25ed46e6f3e81e0ff9b9b4df7fe1a4bbd13f8396b7391be0a488b64c675b41e)_
|
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
|
||||||
- [`scrcpy-win64-v1.11.zip`][direct-win64]
|
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
|
||||||
_(SHA-256: 3802c9ea0307d437947ff150ec65e53990b0beaacd0c8d0bed19c7650ce141bd)_
|
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
|
||||||
|
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win32-v1.11.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.11/scrcpy-win64-v1.11.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
|
||||||
|
|
||||||
You can also [build the app manually][BUILD].
|
You can also [build the app manually][BUILD].
|
||||||
|
|
||||||
@@ -343,6 +343,13 @@ Note that it only shows _physical_ touches (with the finger on the device).
|
|||||||
|
|
||||||
### Input control
|
### Input control
|
||||||
|
|
||||||
|
#### Rotate device screen
|
||||||
|
|
||||||
|
Press `Ctrl`+`r` to switch between portrait and landscape modes.
|
||||||
|
|
||||||
|
Note that it rotates only if the application in foreground supports the
|
||||||
|
requested orientation.
|
||||||
|
|
||||||
#### Copy-paste
|
#### Copy-paste
|
||||||
|
|
||||||
It is possible to synchronize clipboards between the computer and the device, in
|
It is possible to synchronize clipboards between the computer and the device, in
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
src = [
|
src = [
|
||||||
'src/main.c',
|
'src/main.c',
|
||||||
|
'src/cli.c',
|
||||||
'src/command.c',
|
'src/command.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
@@ -140,34 +141,43 @@ install_man('scrcpy.1')
|
|||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
|
|
||||||
tests = [
|
# do not build tests in release (assertions would not be executed at all)
|
||||||
['test_buffer_util', [
|
if get_option('buildtype') == 'debug'
|
||||||
'tests/test_buffer_util.c'
|
tests = [
|
||||||
]],
|
['test_buffer_util', [
|
||||||
['test_cbuf', [
|
'tests/test_buffer_util.c'
|
||||||
'tests/test_cbuf.c',
|
]],
|
||||||
]],
|
['test_cbuf', [
|
||||||
['test_control_event_serialize', [
|
'tests/test_cbuf.c',
|
||||||
'tests/test_control_msg_serialize.c',
|
]],
|
||||||
'src/control_msg.c',
|
['test_cli', [
|
||||||
'src/util/str_util.c'
|
'tests/test_cli.c',
|
||||||
]],
|
'src/cli.c',
|
||||||
['test_device_event_deserialize', [
|
'src/util/str_util.c',
|
||||||
'tests/test_device_msg_deserialize.c',
|
]],
|
||||||
'src/device_msg.c'
|
['test_control_event_serialize', [
|
||||||
]],
|
'tests/test_control_msg_serialize.c',
|
||||||
['test_queue', [
|
'src/control_msg.c',
|
||||||
'tests/test_queue.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_strutil', [
|
['test_device_event_deserialize', [
|
||||||
'tests/test_strutil.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
'src/util/str_util.c'
|
'src/device_msg.c',
|
||||||
]],
|
]],
|
||||||
]
|
['test_queue', [
|
||||||
|
'tests/test_queue.c',
|
||||||
|
]],
|
||||||
|
['test_strutil', [
|
||||||
|
'tests/test_strutil.c',
|
||||||
|
'src/util/str_util.c',
|
||||||
|
]],
|
||||||
|
]
|
||||||
|
|
||||||
foreach t : tests
|
foreach t : tests
|
||||||
exe = executable(t[0], t[1],
|
exe = executable(t[0], t[1],
|
||||||
include_directories: src_dir,
|
include_directories: src_dir,
|
||||||
dependencies: dependencies)
|
dependencies: dependencies,
|
||||||
test(t[0], exe)
|
c_args: ['-DSDL_MAIN_HANDLED'])
|
||||||
endforeach
|
test(t[0], exe)
|
||||||
|
endforeach
|
||||||
|
endif
|
||||||
|
|||||||
16
app/scrcpy.1
16
app/scrcpy.1
@@ -26,7 +26,7 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
|
|||||||
Default is 8000000.
|
Default is 8000000.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
Crop the device screen on the server.
|
Crop the device screen on the server.
|
||||||
|
|
||||||
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
|
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
|
||||||
@@ -42,7 +42,7 @@ Start in fullscreen.
|
|||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
Limit the framerate of screen capture (only supported on devices with Android >= 10).
|
Limit the framerate of screen capture (only supported on devices with Android >= 10).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -88,7 +88,7 @@ The format is determined by the
|
|||||||
option if set, or by the file extension (.mp4 or .mkv).
|
option if set, or by the file extension (.mp4 or .mkv).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-record\-format " format
|
.BI "\-\-record\-format " format
|
||||||
Force recording format (either mp4 or mkv).
|
Force recording format (either mp4 or mkv).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -118,29 +118,29 @@ Print the version of scrcpy.
|
|||||||
Disable window decorations (display borderless window).
|
Disable window decorations (display borderless window).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-window\-title " text
|
.BI "\-\-window\-title " text
|
||||||
Set a custom window title.
|
Set a custom window title.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-window\-x " value
|
.BI "\-\-window\-x " value
|
||||||
Set the initial window horizontal position.
|
Set the initial window horizontal position.
|
||||||
|
|
||||||
Default is -1 (automatic).\n
|
Default is -1 (automatic).\n
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-window\-y " value
|
.BI "\-\-window\-y " value
|
||||||
Set the initial window vertical position.
|
Set the initial window vertical position.
|
||||||
|
|
||||||
Default is -1 (automatic).\n
|
Default is -1 (automatic).\n
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-window\-width " value
|
.BI "\-\-window\-width " value
|
||||||
Set the initial window width.
|
Set the initial window width.
|
||||||
|
|
||||||
Default is 0 (automatic).\n
|
Default is 0 (automatic).\n
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-\-window\-height " value
|
.BI "\-\-window\-height " value
|
||||||
Set the initial window height.
|
Set the initial window height.
|
||||||
|
|
||||||
Default is 0 (automatic).\n
|
Default is 0 (automatic).\n
|
||||||
|
|||||||
529
app/src/cli.c
Normal file
529
app/src/cli.c
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
#include "cli.h"
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "recorder.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
scrcpy_print_usage(const char *arg0) {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# define CTRL_OR_CMD "Cmd"
|
||||||
|
#else
|
||||||
|
# define CTRL_OR_CMD "Ctrl"
|
||||||
|
#endif
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: %s [options]\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n"
|
||||||
|
"\n"
|
||||||
|
" --always-on-top\n"
|
||||||
|
" Make scrcpy window always on top (above other windows).\n"
|
||||||
|
"\n"
|
||||||
|
" -b, --bit-rate value\n"
|
||||||
|
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
||||||
|
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
|
" Default is %d.\n"
|
||||||
|
"\n"
|
||||||
|
" --crop width:height:x:y\n"
|
||||||
|
" Crop the device screen on the server.\n"
|
||||||
|
" The values are expressed in the device natural orientation\n"
|
||||||
|
" (typically, portrait for a phone, landscape for a tablet).\n"
|
||||||
|
" Any --max-size value is computed on the cropped size.\n"
|
||||||
|
"\n"
|
||||||
|
" -f, --fullscreen\n"
|
||||||
|
" Start in fullscreen.\n"
|
||||||
|
"\n"
|
||||||
|
" -h, --help\n"
|
||||||
|
" Print this help.\n"
|
||||||
|
"\n"
|
||||||
|
" --max-fps value\n"
|
||||||
|
" Limit the frame rate of screen capture (only supported on\n"
|
||||||
|
" devices with Android >= 10).\n"
|
||||||
|
"\n"
|
||||||
|
" -m, --max-size value\n"
|
||||||
|
" Limit both the width and height of the video to value. The\n"
|
||||||
|
" other dimension is computed so that the device aspect-ratio\n"
|
||||||
|
" is preserved.\n"
|
||||||
|
" Default is %d%s.\n"
|
||||||
|
"\n"
|
||||||
|
" -n, --no-control\n"
|
||||||
|
" Disable device control (mirror the device in read-only).\n"
|
||||||
|
"\n"
|
||||||
|
" -N, --no-display\n"
|
||||||
|
" Do not display device (only when screen recording is\n"
|
||||||
|
" enabled).\n"
|
||||||
|
"\n"
|
||||||
|
" -p, --port port\n"
|
||||||
|
" Set the TCP port the client listens on.\n"
|
||||||
|
" Default is %d.\n"
|
||||||
|
"\n"
|
||||||
|
" --prefer-text\n"
|
||||||
|
" Inject alpha characters and space as text events instead of\n"
|
||||||
|
" key events.\n"
|
||||||
|
" This avoids issues when combining multiple keys to enter a\n"
|
||||||
|
" special character, but breaks the expected behavior of alpha\n"
|
||||||
|
" keys in games (typically WASD).\n"
|
||||||
|
"\n"
|
||||||
|
" --push-target path\n"
|
||||||
|
" Set the target directory for pushing files to the device by\n"
|
||||||
|
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||||
|
" Default is \"/sdcard/\".\n"
|
||||||
|
"\n"
|
||||||
|
" -r, --record file.mp4\n"
|
||||||
|
" Record screen to file.\n"
|
||||||
|
" The format is determined by the --record-format option if\n"
|
||||||
|
" set, or by the file extension (.mp4 or .mkv).\n"
|
||||||
|
"\n"
|
||||||
|
" --record-format format\n"
|
||||||
|
" Force recording format (either mp4 or mkv).\n"
|
||||||
|
"\n"
|
||||||
|
" --render-expired-frames\n"
|
||||||
|
" By default, to minimize latency, scrcpy always renders the\n"
|
||||||
|
" last available decoded frame, and drops any previous ones.\n"
|
||||||
|
" This flag forces to render all frames, at a cost of a\n"
|
||||||
|
" possible increased latency.\n"
|
||||||
|
"\n"
|
||||||
|
" -s, --serial serial\n"
|
||||||
|
" The device serial number. Mandatory only if several devices\n"
|
||||||
|
" are connected to adb.\n"
|
||||||
|
"\n"
|
||||||
|
" -S, --turn-screen-off\n"
|
||||||
|
" Turn the device screen off immediately.\n"
|
||||||
|
"\n"
|
||||||
|
" -t, --show-touches\n"
|
||||||
|
" Enable \"show touches\" on start, disable on quit.\n"
|
||||||
|
" It only shows physical touches (not clicks from scrcpy).\n"
|
||||||
|
"\n"
|
||||||
|
" -v, --version\n"
|
||||||
|
" Print the version of scrcpy.\n"
|
||||||
|
"\n"
|
||||||
|
" --window-borderless\n"
|
||||||
|
" Disable window decorations (display borderless window).\n"
|
||||||
|
"\n"
|
||||||
|
" --window-title text\n"
|
||||||
|
" Set a custom window title.\n"
|
||||||
|
"\n"
|
||||||
|
" --window-x value\n"
|
||||||
|
" Set the initial window horizontal position.\n"
|
||||||
|
" Default is -1 (automatic).\n"
|
||||||
|
"\n"
|
||||||
|
" --window-y value\n"
|
||||||
|
" Set the initial window vertical position.\n"
|
||||||
|
" Default is -1 (automatic).\n"
|
||||||
|
"\n"
|
||||||
|
" --window-width value\n"
|
||||||
|
" Set the initial window width.\n"
|
||||||
|
" Default is 0 (automatic).\n"
|
||||||
|
"\n"
|
||||||
|
" --window-height value\n"
|
||||||
|
" Set the initial window width.\n"
|
||||||
|
" Default is 0 (automatic).\n"
|
||||||
|
"\n"
|
||||||
|
"Shortcuts:\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+f\n"
|
||||||
|
" switch fullscreen mode\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+g\n"
|
||||||
|
" resize window to 1:1 (pixel-perfect)\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+x\n"
|
||||||
|
" Double-click on black borders\n"
|
||||||
|
" resize window to remove black borders\n"
|
||||||
|
"\n"
|
||||||
|
" Ctrl+h\n"
|
||||||
|
" Middle-click\n"
|
||||||
|
" click on HOME\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+b\n"
|
||||||
|
" " CTRL_OR_CMD "+Backspace\n"
|
||||||
|
" Right-click (when screen is on)\n"
|
||||||
|
" click on BACK\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+s\n"
|
||||||
|
" click on APP_SWITCH\n"
|
||||||
|
"\n"
|
||||||
|
" Ctrl+m\n"
|
||||||
|
" click on MENU\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Up\n"
|
||||||
|
" click on VOLUME_UP\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Down\n"
|
||||||
|
" click on VOLUME_DOWN\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+p\n"
|
||||||
|
" click on POWER (turn screen on/off)\n"
|
||||||
|
"\n"
|
||||||
|
" Right-click (when screen is off)\n"
|
||||||
|
" power on\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+o\n"
|
||||||
|
" turn device screen off (keep mirroring)\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+r\n"
|
||||||
|
" rotate device screen\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+n\n"
|
||||||
|
" expand notification panel\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Shift+n\n"
|
||||||
|
" collapse notification panel\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+c\n"
|
||||||
|
" copy device clipboard to computer\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+v\n"
|
||||||
|
" paste computer clipboard to device\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Shift+v\n"
|
||||||
|
" copy computer clipboard to device\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+i\n"
|
||||||
|
" enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
|
"\n"
|
||||||
|
" Drag & drop APK file\n"
|
||||||
|
" install APK from computer\n"
|
||||||
|
"\n",
|
||||||
|
arg0,
|
||||||
|
DEFAULT_BIT_RATE,
|
||||||
|
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||||
|
DEFAULT_LOCAL_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
||||||
|
long max, const char *name) {
|
||||||
|
long value;
|
||||||
|
bool ok;
|
||||||
|
if (accept_suffix) {
|
||||||
|
ok = parse_integer_with_suffix(s, &value);
|
||||||
|
} else {
|
||||||
|
ok = parse_integer(s, &value);
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not parse %s: %s", name, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < min || value > max) {
|
||||||
|
LOGE("Could not parse %s: value (%ld) out-of-range (%ld; %ld)",
|
||||||
|
name, value, min, max);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||||
|
long value;
|
||||||
|
// long may be 32 bits (it is the case on mingw), so do not use more than
|
||||||
|
// 31 bits (long is signed)
|
||||||
|
bool ok = parse_integer_arg(s, &value, true, 0, 0x7FFFFFFF, "bit-rate");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*bit_rate = (uint32_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_max_size(const char *s, uint16_t *max_size) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max size");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*max_size = (uint16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_max_fps(const char *s, uint16_t *max_fps) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 1000, "max fps");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*max_fps = (uint16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_window_position(const char *s, int16_t *position) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, -1, 0x7FFF,
|
||||||
|
"window position");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*position = (int16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_window_dimension(const char *s, uint16_t *dimension) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF,
|
||||||
|
"window dimension");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*dimension = (uint16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_port(const char *s, uint16_t *port) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*port = (uint16_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_record_format(const char *optarg, enum recorder_format *format) {
|
||||||
|
if (!strcmp(optarg, "mp4")) {
|
||||||
|
*format = RECORDER_FORMAT_MP4;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!strcmp(optarg, "mkv")) {
|
||||||
|
*format = RECORDER_FORMAT_MKV;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum recorder_format
|
||||||
|
guess_record_format(const char *filename) {
|
||||||
|
size_t len = strlen(filename);
|
||||||
|
if (len < 4) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const char *ext = &filename[len - 4];
|
||||||
|
if (!strcmp(ext, ".mp4")) {
|
||||||
|
return RECORDER_FORMAT_MP4;
|
||||||
|
}
|
||||||
|
if (!strcmp(ext, ".mkv")) {
|
||||||
|
return RECORDER_FORMAT_MKV;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
||||||
|
#define OPT_WINDOW_TITLE 1001
|
||||||
|
#define OPT_PUSH_TARGET 1002
|
||||||
|
#define OPT_ALWAYS_ON_TOP 1003
|
||||||
|
#define OPT_CROP 1004
|
||||||
|
#define OPT_RECORD_FORMAT 1005
|
||||||
|
#define OPT_PREFER_TEXT 1006
|
||||||
|
#define OPT_WINDOW_X 1007
|
||||||
|
#define OPT_WINDOW_Y 1008
|
||||||
|
#define OPT_WINDOW_WIDTH 1009
|
||||||
|
#define OPT_WINDOW_HEIGHT 1010
|
||||||
|
#define OPT_WINDOW_BORDERLESS 1011
|
||||||
|
#define OPT_MAX_FPS 1012
|
||||||
|
|
||||||
|
bool
|
||||||
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
|
static const struct option long_options[] = {
|
||||||
|
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
||||||
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
|
{"crop", required_argument, NULL, OPT_CROP},
|
||||||
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
|
{"no-control", no_argument, NULL, 'n'},
|
||||||
|
{"no-display", no_argument, NULL, 'N'},
|
||||||
|
{"port", required_argument, NULL, 'p'},
|
||||||
|
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||||
|
{"record", required_argument, NULL, 'r'},
|
||||||
|
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
||||||
|
{"render-expired-frames", no_argument, NULL,
|
||||||
|
OPT_RENDER_EXPIRED_FRAMES},
|
||||||
|
{"serial", required_argument, NULL, 's'},
|
||||||
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
|
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||||
|
{"version", no_argument, NULL, 'v'},
|
||||||
|
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||||
|
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
||||||
|
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
|
||||||
|
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
|
||||||
|
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
|
||||||
|
{"window-borderless", no_argument, NULL,
|
||||||
|
OPT_WINDOW_BORDERLESS},
|
||||||
|
{NULL, 0, NULL, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scrcpy_options *opts = &args->opts;
|
||||||
|
|
||||||
|
optind = 0; // reset to start from the first argument in tests
|
||||||
|
|
||||||
|
int c;
|
||||||
|
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
||||||
|
NULL)) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'b':
|
||||||
|
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
LOGW("Deprecated option -c. Use --crop instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_CROP:
|
||||||
|
opts->crop = optarg;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
opts->fullscreen = true;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
LOGW("Deprecated option -F. Use --record-format instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_RECORD_FORMAT:
|
||||||
|
if (!parse_record_format(optarg, &opts->record_format)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
args->help = true;
|
||||||
|
break;
|
||||||
|
case OPT_MAX_FPS:
|
||||||
|
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if (!parse_max_size(optarg, &opts->max_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
opts->control = false;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
opts->display = false;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (!parse_port(optarg, &opts->port)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
opts->record_filename = optarg;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
opts->serial = optarg;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
opts->turn_screen_off = true;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
opts->show_touches = true;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
LOGW("Deprecated option -T. Use --always-on-top instead.");
|
||||||
|
// fall through
|
||||||
|
case OPT_ALWAYS_ON_TOP:
|
||||||
|
opts->always_on_top = true;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
args->version = true;
|
||||||
|
break;
|
||||||
|
case OPT_RENDER_EXPIRED_FRAMES:
|
||||||
|
opts->render_expired_frames = true;
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_TITLE:
|
||||||
|
opts->window_title = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_X:
|
||||||
|
if (!parse_window_position(optarg, &opts->window_x)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_Y:
|
||||||
|
if (!parse_window_position(optarg, &opts->window_y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_WIDTH:
|
||||||
|
if (!parse_window_dimension(optarg, &opts->window_width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_HEIGHT:
|
||||||
|
if (!parse_window_dimension(optarg, &opts->window_height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_WINDOW_BORDERLESS:
|
||||||
|
opts->window_borderless = true;
|
||||||
|
break;
|
||||||
|
case OPT_PUSH_TARGET:
|
||||||
|
opts->push_target = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_PREFER_TEXT:
|
||||||
|
opts->prefer_text = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// getopt prints the error message on stderr
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts->display && !opts->record_filename) {
|
||||||
|
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts->display && opts->fullscreen) {
|
||||||
|
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = optind;
|
||||||
|
if (index < argc) {
|
||||||
|
LOGE("Unexpected additional argument: %s", argv[index]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->record_format && !opts->record_filename) {
|
||||||
|
LOGE("Record format specified without recording");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->record_filename && !opts->record_format) {
|
||||||
|
opts->record_format = guess_record_format(opts->record_filename);
|
||||||
|
if (!opts->record_format) {
|
||||||
|
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
||||||
|
opts->record_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts->control && opts->turn_screen_off) {
|
||||||
|
LOGE("Could not request to turn screen off if control is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
21
app/src/cli.h
Normal file
21
app/src/cli.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SCRCPY_CLI_H
|
||||||
|
#define SCRCPY_CLI_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
|
struct scrcpy_cli_args {
|
||||||
|
struct scrcpy_options opts;
|
||||||
|
bool help;
|
||||||
|
bool version;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
scrcpy_print_usage(const char *arg0);
|
||||||
|
|
||||||
|
bool
|
||||||
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -85,4 +85,8 @@ process_check_success(process_t proc, const char *name);
|
|||||||
char *
|
char *
|
||||||
get_executable_path(void);
|
get_executable_path(void);
|
||||||
|
|
||||||
|
// returns true if the file exists and is not a directory
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
578
app/src/main.c
578
app/src/main.c
@@ -1,210 +1,16 @@
|
|||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "cli.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "recorder.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
struct args {
|
|
||||||
struct scrcpy_options opts;
|
|
||||||
bool help;
|
|
||||||
bool version;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void usage(const char *arg0) {
|
|
||||||
#ifdef __APPLE__
|
|
||||||
# define CTRL_OR_CMD "Cmd"
|
|
||||||
#else
|
|
||||||
# define CTRL_OR_CMD "Ctrl"
|
|
||||||
#endif
|
|
||||||
fprintf(stderr,
|
|
||||||
"Usage: %s [options]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\n"
|
|
||||||
"\n"
|
|
||||||
" --always-on-top\n"
|
|
||||||
" Make scrcpy window always on top (above other windows).\n"
|
|
||||||
"\n"
|
|
||||||
" -b, --bit-rate value\n"
|
|
||||||
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
|
||||||
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
|
||||||
" Default is %d.\n"
|
|
||||||
"\n"
|
|
||||||
" --crop width:height:x:y\n"
|
|
||||||
" Crop the device screen on the server.\n"
|
|
||||||
" The values are expressed in the device natural orientation\n"
|
|
||||||
" (typically, portrait for a phone, landscape for a tablet).\n"
|
|
||||||
" Any --max-size value is computed on the cropped size.\n"
|
|
||||||
"\n"
|
|
||||||
" -f, --fullscreen\n"
|
|
||||||
" Start in fullscreen.\n"
|
|
||||||
"\n"
|
|
||||||
" -h, --help\n"
|
|
||||||
" Print this help.\n"
|
|
||||||
"\n"
|
|
||||||
" --max-fps value\n"
|
|
||||||
" Limit the frame rate of screen capture (only supported on\n"
|
|
||||||
" devices with Android >= 10).\n"
|
|
||||||
"\n"
|
|
||||||
" -m, --max-size value\n"
|
|
||||||
" Limit both the width and height of the video to value. The\n"
|
|
||||||
" other dimension is computed so that the device aspect-ratio\n"
|
|
||||||
" is preserved.\n"
|
|
||||||
" Default is %d%s.\n"
|
|
||||||
"\n"
|
|
||||||
" -n, --no-control\n"
|
|
||||||
" Disable device control (mirror the device in read-only).\n"
|
|
||||||
"\n"
|
|
||||||
" -N, --no-display\n"
|
|
||||||
" Do not display device (only when screen recording is\n"
|
|
||||||
" enabled).\n"
|
|
||||||
"\n"
|
|
||||||
" -p, --port port\n"
|
|
||||||
" Set the TCP port the client listens on.\n"
|
|
||||||
" Default is %d.\n"
|
|
||||||
"\n"
|
|
||||||
" --prefer-text\n"
|
|
||||||
" Inject alpha characters and space as text events instead of\n"
|
|
||||||
" key events.\n"
|
|
||||||
" This avoids issues when combining multiple keys to enter a\n"
|
|
||||||
" special character, but breaks the expected behavior of alpha\n"
|
|
||||||
" keys in games (typically WASD).\n"
|
|
||||||
"\n"
|
|
||||||
" --push-target path\n"
|
|
||||||
" Set the target directory for pushing files to the device by\n"
|
|
||||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
|
||||||
" Default is \"/sdcard/\".\n"
|
|
||||||
"\n"
|
|
||||||
" -r, --record file.mp4\n"
|
|
||||||
" Record screen to file.\n"
|
|
||||||
" The format is determined by the --record-format option if\n"
|
|
||||||
" set, or by the file extension (.mp4 or .mkv).\n"
|
|
||||||
"\n"
|
|
||||||
" --record-format format\n"
|
|
||||||
" Force recording format (either mp4 or mkv).\n"
|
|
||||||
"\n"
|
|
||||||
" --render-expired-frames\n"
|
|
||||||
" By default, to minimize latency, scrcpy always renders the\n"
|
|
||||||
" last available decoded frame, and drops any previous ones.\n"
|
|
||||||
" This flag forces to render all frames, at a cost of a\n"
|
|
||||||
" possible increased latency.\n"
|
|
||||||
"\n"
|
|
||||||
" -s, --serial serial\n"
|
|
||||||
" The device serial number. Mandatory only if several devices\n"
|
|
||||||
" are connected to adb.\n"
|
|
||||||
"\n"
|
|
||||||
" -S, --turn-screen-off\n"
|
|
||||||
" Turn the device screen off immediately.\n"
|
|
||||||
"\n"
|
|
||||||
" -t, --show-touches\n"
|
|
||||||
" Enable \"show touches\" on start, disable on quit.\n"
|
|
||||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
|
||||||
"\n"
|
|
||||||
" -v, --version\n"
|
|
||||||
" Print the version of scrcpy.\n"
|
|
||||||
"\n"
|
|
||||||
" --window-borderless\n"
|
|
||||||
" Disable window decorations (display borderless window).\n"
|
|
||||||
"\n"
|
|
||||||
" --window-title text\n"
|
|
||||||
" Set a custom window title.\n"
|
|
||||||
"\n"
|
|
||||||
" --window-x value\n"
|
|
||||||
" Set the initial window horizontal position.\n"
|
|
||||||
" Default is -1 (automatic).\n"
|
|
||||||
"\n"
|
|
||||||
" --window-y value\n"
|
|
||||||
" Set the initial window vertical position.\n"
|
|
||||||
" Default is -1 (automatic).\n"
|
|
||||||
"\n"
|
|
||||||
" --window-width value\n"
|
|
||||||
" Set the initial window width.\n"
|
|
||||||
" Default is 0 (automatic).\n"
|
|
||||||
"\n"
|
|
||||||
" --window-height value\n"
|
|
||||||
" Set the initial window width.\n"
|
|
||||||
" Default is 0 (automatic).\n"
|
|
||||||
"\n"
|
|
||||||
"Shortcuts:\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+f\n"
|
|
||||||
" switch fullscreen mode\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+g\n"
|
|
||||||
" resize window to 1:1 (pixel-perfect)\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+x\n"
|
|
||||||
" Double-click on black borders\n"
|
|
||||||
" resize window to remove black borders\n"
|
|
||||||
"\n"
|
|
||||||
" Ctrl+h\n"
|
|
||||||
" Middle-click\n"
|
|
||||||
" click on HOME\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+b\n"
|
|
||||||
" " CTRL_OR_CMD "+Backspace\n"
|
|
||||||
" Right-click (when screen is on)\n"
|
|
||||||
" click on BACK\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+s\n"
|
|
||||||
" click on APP_SWITCH\n"
|
|
||||||
"\n"
|
|
||||||
" Ctrl+m\n"
|
|
||||||
" click on MENU\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+Up\n"
|
|
||||||
" click on VOLUME_UP\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+Down\n"
|
|
||||||
" click on VOLUME_DOWN\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+p\n"
|
|
||||||
" click on POWER (turn screen on/off)\n"
|
|
||||||
"\n"
|
|
||||||
" Right-click (when screen is off)\n"
|
|
||||||
" power on\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+o\n"
|
|
||||||
" turn device screen off (keep mirroring)\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+r\n"
|
|
||||||
" rotate device screen\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+n\n"
|
|
||||||
" expand notification panel\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+Shift+n\n"
|
|
||||||
" collapse notification panel\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+c\n"
|
|
||||||
" copy device clipboard to computer\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+v\n"
|
|
||||||
" paste computer clipboard to device\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+Shift+v\n"
|
|
||||||
" copy computer clipboard to device\n"
|
|
||||||
"\n"
|
|
||||||
" " CTRL_OR_CMD "+i\n"
|
|
||||||
" enable/disable FPS counter (print frames/second in logs)\n"
|
|
||||||
"\n"
|
|
||||||
" Drag & drop APK file\n"
|
|
||||||
" install APK from computer\n"
|
|
||||||
"\n",
|
|
||||||
arg0,
|
|
||||||
DEFAULT_BIT_RATE,
|
|
||||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
|
||||||
DEFAULT_LOCAL_PORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
print_version(void) {
|
print_version(void) {
|
||||||
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
|
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
|
||||||
@@ -223,373 +29,6 @@ print_version(void) {
|
|||||||
LIBAVUTIL_VERSION_MICRO);
|
LIBAVUTIL_VERSION_MICRO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_bit_rate(char *optarg, uint32_t *bit_rate) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Bit-rate parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
int mul = 1;
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
if (optarg == endptr) {
|
|
||||||
LOGE("Invalid bit-rate: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
|
|
||||||
mul = 1000000;
|
|
||||||
} else if ((*endptr == 'K' || *endptr == 'k') && endptr[1] == '\0') {
|
|
||||||
mul = 1000;
|
|
||||||
} else {
|
|
||||||
LOGE("Invalid bit-rate unit: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value < 0 || ((uint32_t) -1) / mul < (unsigned long) value) {
|
|
||||||
LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*bit_rate = (uint32_t) value * mul;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_max_size(char *optarg, uint16_t *max_size) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Max size parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
LOGE("Invalid max size: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (value & ~0xffff) {
|
|
||||||
LOGE("Max size must be between 0 and 65535: %ld", value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*max_size = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_max_fps(const char *optarg, uint16_t *max_fps) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Max FPS parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
LOGE("Invalid max FPS: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (value & ~0xffff) {
|
|
||||||
// in practice, it should not be higher than 60
|
|
||||||
LOGE("Max FPS value is invalid: %ld", value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*max_fps = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_window_position(char *optarg, int16_t *position) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Window position parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
LOGE("Invalid window position: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (value < -1 || value > 0x7fff) {
|
|
||||||
LOGE("Window position must be between -1 and 32767: %ld", value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*position = (int16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_window_dimension(char *optarg, uint16_t *dimension) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Window dimension parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
LOGE("Invalid window dimension: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (value & ~0xffff) {
|
|
||||||
LOGE("Window position must be between 0 and 65535: %ld", value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*dimension = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_port(char *optarg, uint16_t *port) {
|
|
||||||
char *endptr;
|
|
||||||
if (*optarg == '\0') {
|
|
||||||
LOGE("Port parameter is empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long value = strtol(optarg, &endptr, 0);
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
LOGE("Invalid port: %s", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (value & ~0xffff) {
|
|
||||||
LOGE("Port out of range: %ld", value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*port = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_record_format(const char *optarg, enum recorder_format *format) {
|
|
||||||
if (!strcmp(optarg, "mp4")) {
|
|
||||||
*format = RECORDER_FORMAT_MP4;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(optarg, "mkv")) {
|
|
||||||
*format = RECORDER_FORMAT_MKV;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum recorder_format
|
|
||||||
guess_record_format(const char *filename) {
|
|
||||||
size_t len = strlen(filename);
|
|
||||||
if (len < 4) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const char *ext = &filename[len - 4];
|
|
||||||
if (!strcmp(ext, ".mp4")) {
|
|
||||||
return RECORDER_FORMAT_MP4;
|
|
||||||
}
|
|
||||||
if (!strcmp(ext, ".mkv")) {
|
|
||||||
return RECORDER_FORMAT_MKV;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
|
||||||
#define OPT_WINDOW_TITLE 1001
|
|
||||||
#define OPT_PUSH_TARGET 1002
|
|
||||||
#define OPT_ALWAYS_ON_TOP 1003
|
|
||||||
#define OPT_CROP 1004
|
|
||||||
#define OPT_RECORD_FORMAT 1005
|
|
||||||
#define OPT_PREFER_TEXT 1006
|
|
||||||
#define OPT_WINDOW_X 1007
|
|
||||||
#define OPT_WINDOW_Y 1008
|
|
||||||
#define OPT_WINDOW_WIDTH 1009
|
|
||||||
#define OPT_WINDOW_HEIGHT 1010
|
|
||||||
#define OPT_WINDOW_BORDERLESS 1011
|
|
||||||
#define OPT_MAX_FPS 1012
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_args(struct args *args, int argc, char *argv[]) {
|
|
||||||
static const struct option long_options[] = {
|
|
||||||
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
|
||||||
{"bit-rate", required_argument, NULL, 'b'},
|
|
||||||
{"crop", required_argument, NULL, OPT_CROP},
|
|
||||||
{"fullscreen", no_argument, NULL, 'f'},
|
|
||||||
{"help", no_argument, NULL, 'h'},
|
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
|
||||||
{"no-control", no_argument, NULL, 'n'},
|
|
||||||
{"no-display", no_argument, NULL, 'N'},
|
|
||||||
{"port", required_argument, NULL, 'p'},
|
|
||||||
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
|
||||||
{"record", required_argument, NULL, 'r'},
|
|
||||||
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
|
||||||
{"render-expired-frames", no_argument, NULL,
|
|
||||||
OPT_RENDER_EXPIRED_FRAMES},
|
|
||||||
{"serial", required_argument, NULL, 's'},
|
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
|
||||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
|
||||||
{"version", no_argument, NULL, 'v'},
|
|
||||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
|
||||||
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
|
||||||
{"window-y", required_argument, NULL, OPT_WINDOW_Y},
|
|
||||||
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
|
|
||||||
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
|
|
||||||
{"window-borderless", no_argument, NULL,
|
|
||||||
OPT_WINDOW_BORDERLESS},
|
|
||||||
{NULL, 0, NULL, 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
struct scrcpy_options *opts = &args->opts;
|
|
||||||
|
|
||||||
int c;
|
|
||||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
|
|
||||||
NULL)) != -1) {
|
|
||||||
switch (c) {
|
|
||||||
case 'b':
|
|
||||||
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
LOGW("Deprecated option -c. Use --crop instead.");
|
|
||||||
// fall through
|
|
||||||
case OPT_CROP:
|
|
||||||
opts->crop = optarg;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
opts->fullscreen = true;
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
LOGW("Deprecated option -F. Use --record-format instead.");
|
|
||||||
// fall through
|
|
||||||
case OPT_RECORD_FORMAT:
|
|
||||||
if (!parse_record_format(optarg, &opts->record_format)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
args->help = true;
|
|
||||||
break;
|
|
||||||
case OPT_MAX_FPS:
|
|
||||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
if (!parse_max_size(optarg, &opts->max_size)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
opts->control = false;
|
|
||||||
break;
|
|
||||||
case 'N':
|
|
||||||
opts->display = false;
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
if (!parse_port(optarg, &opts->port)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
opts->record_filename = optarg;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
opts->serial = optarg;
|
|
||||||
break;
|
|
||||||
case 'S':
|
|
||||||
opts->turn_screen_off = true;
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
opts->show_touches = true;
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
LOGW("Deprecated option -T. Use --always-on-top instead.");
|
|
||||||
// fall through
|
|
||||||
case OPT_ALWAYS_ON_TOP:
|
|
||||||
opts->always_on_top = true;
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
args->version = true;
|
|
||||||
break;
|
|
||||||
case OPT_RENDER_EXPIRED_FRAMES:
|
|
||||||
opts->render_expired_frames = true;
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_TITLE:
|
|
||||||
opts->window_title = optarg;
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_X:
|
|
||||||
if (!parse_window_position(optarg, &opts->window_x)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_Y:
|
|
||||||
if (!parse_window_position(optarg, &opts->window_y)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_WIDTH:
|
|
||||||
if (!parse_window_dimension(optarg, &opts->window_width)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_HEIGHT:
|
|
||||||
if (!parse_window_dimension(optarg, &opts->window_height)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_WINDOW_BORDERLESS:
|
|
||||||
opts->window_borderless = true;
|
|
||||||
break;
|
|
||||||
case OPT_PUSH_TARGET:
|
|
||||||
opts->push_target = optarg;
|
|
||||||
break;
|
|
||||||
case OPT_PREFER_TEXT:
|
|
||||||
opts->prefer_text = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// getopt prints the error message on stderr
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts->display && !opts->record_filename) {
|
|
||||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts->display && opts->fullscreen) {
|
|
||||||
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = optind;
|
|
||||||
if (index < argc) {
|
|
||||||
LOGE("Unexpected additional argument: %s", argv[index]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts->record_format && !opts->record_filename) {
|
|
||||||
LOGE("Record format specified without recording");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts->record_filename && !opts->record_format) {
|
|
||||||
opts->record_format = guess_record_format(opts->record_filename);
|
|
||||||
if (!opts->record_format) {
|
|
||||||
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
|
||||||
opts->record_filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opts->control && opts->turn_screen_off) {
|
|
||||||
LOGE("Could not request to turn screen off if control is disabled");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@@ -598,18 +37,23 @@ main(int argc, char *argv[]) {
|
|||||||
setbuf(stdout, NULL);
|
setbuf(stdout, NULL);
|
||||||
setbuf(stderr, NULL);
|
setbuf(stderr, NULL);
|
||||||
#endif
|
#endif
|
||||||
struct args args = {
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct scrcpy_cli_args args = {
|
||||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!parse_args(&args, argc, argv)) {
|
if (!scrcpy_parse_args(&args, argc, argv)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,10 +72,6 @@ main(int argc, char *argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int res = scrcpy(&args.opts) ? 0 : 1;
|
int res = scrcpy(&args.opts) ? 0 : 1;
|
||||||
|
|
||||||
avformat_network_deinit(); // ignore failure
|
avformat_network_deinit(); // ignore failure
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct scrcpy_options {
|
|||||||
.push_target = NULL, \
|
.push_target = NULL, \
|
||||||
.record_format = RECORDER_FORMAT_AUTO, \
|
.record_format = RECORDER_FORMAT_AUTO, \
|
||||||
.port = DEFAULT_LOCAL_PORT, \
|
.port = DEFAULT_LOCAL_PORT, \
|
||||||
.max_size = DEFAULT_LOCAL_PORT, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.window_x = -1, \
|
.window_x = -1, \
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define SOCKET_NAME "scrcpy"
|
#define SOCKET_NAME "scrcpy"
|
||||||
#define SERVER_FILENAME "scrcpy-server"
|
#define SERVER_FILENAME "scrcpy-server"
|
||||||
@@ -18,20 +20,39 @@
|
|||||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||||
|
|
||||||
static const char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH");
|
||||||
|
#else
|
||||||
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
||||||
|
#endif
|
||||||
if (server_path_env) {
|
if (server_path_env) {
|
||||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
|
|
||||||
// if the envvar is set, use it
|
// if the envvar is set, use it
|
||||||
return server_path_env;
|
#ifdef __WINDOWS__
|
||||||
|
char *server_path = utf8_from_wide_char(server_path_env);
|
||||||
|
#else
|
||||||
|
char *server_path = SDL_strdup(server_path_env);
|
||||||
|
#endif
|
||||||
|
if (!server_path) {
|
||||||
|
LOGE("Could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
||||||
|
return server_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||||
|
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
|
||||||
|
if (!server_path) {
|
||||||
|
LOGE("Could not allocate memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
// the absolute path is hardcoded
|
// the absolute path is hardcoded
|
||||||
return DEFAULT_SERVER_PATH;
|
return server_path;
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// use scrcpy-server in the same directory as the executable
|
// use scrcpy-server in the same directory as the executable
|
||||||
char *executable_path = get_executable_path();
|
char *executable_path = get_executable_path();
|
||||||
if (!executable_path) {
|
if (!executable_path) {
|
||||||
@@ -67,7 +88,17 @@ get_server_path(void) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_server(const char *serial) {
|
push_server(const char *serial) {
|
||||||
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
|
char *server_path = get_server_path();
|
||||||
|
if (!server_path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!is_regular_file(server_path)) {
|
||||||
|
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
||||||
|
SDL_free(server_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||||
|
SDL_free(server_path);
|
||||||
return process_check_success(process, "adb push");
|
return process_check_success(process, "adb push");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -127,3 +128,14 @@ get_executable_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
struct stat path_stat;
|
||||||
|
|
||||||
|
if (stat(path, &path_stat)) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
static int
|
static int
|
||||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||||
// Windows command-line parsing is WTF:
|
// Windows command-line parsing is WTF:
|
||||||
@@ -90,3 +92,22 @@ get_executable_path(void) {
|
|||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
return utf8_from_wide_char(buf);
|
return utf8_from_wide_char(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||||
|
if (!wide_path) {
|
||||||
|
LOGC("Could not allocate wide char string");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _stat path_stat;
|
||||||
|
int r = _wstat(wide_path, &path_stat);
|
||||||
|
SDL_free(wide_path);
|
||||||
|
|
||||||
|
if (r) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "str_util.h"
|
#include "str_util.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -60,6 +62,59 @@ strquote(const char *src) {
|
|||||||
return quoted;
|
return quoted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
parse_integer(const char *s, long *out) {
|
||||||
|
char *endptr;
|
||||||
|
if (*s == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
long value = strtol(s, &endptr, 0);
|
||||||
|
if (errno == ERANGE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
parse_integer_with_suffix(const char *s, long *out) {
|
||||||
|
char *endptr;
|
||||||
|
if (*s == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
long value = strtol(s, &endptr, 0);
|
||||||
|
if (errno == ERANGE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int mul = 1;
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
if (s == endptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
|
||||||
|
mul = 1000000;
|
||||||
|
} else if ((*endptr == 'K' || *endptr == 'k') && endptr[1] == '\0') {
|
||||||
|
mul = 1000;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((value < 0 && LONG_MIN / mul > value) ||
|
||||||
|
(value > 0 && LONG_MAX / mul < value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = value * mul;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
utf8_truncation_index(const char *utf8, size_t max_len) {
|
utf8_truncation_index(const char *utf8, size_t max_len) {
|
||||||
size_t len = strlen(utf8);
|
size_t len = strlen(utf8);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef STRUTIL_H
|
#ifndef STRUTIL_H
|
||||||
#define STRUTIL_H
|
#define STRUTIL_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -25,6 +26,18 @@ xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
|
|||||||
char *
|
char *
|
||||||
strquote(const char *src);
|
strquote(const char *src);
|
||||||
|
|
||||||
|
// parse s as an integer into value
|
||||||
|
// returns true if the conversion succeeded, false otherwise
|
||||||
|
bool
|
||||||
|
parse_integer(const char *s, long *out);
|
||||||
|
|
||||||
|
// parse s as an integer into value
|
||||||
|
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
|
||||||
|
// suffix
|
||||||
|
// returns true if the conversion succeeded, false otherwise
|
||||||
|
bool
|
||||||
|
parse_integer_with_suffix(const char *s, long *out);
|
||||||
|
|
||||||
// return the index to truncate a UTF-8 string at a valid position
|
// return the index to truncate a UTF-8 string at a valid position
|
||||||
size_t
|
size_t
|
||||||
utf8_truncation_index(const char *utf8, size_t max_len);
|
utf8_truncation_index(const char *utf8, size_t max_len);
|
||||||
|
|||||||
128
app/tests/test_cli.c
Normal file
128
app/tests/test_cli.c
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "cli.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
static void test_flag_version(void) {
|
||||||
|
struct scrcpy_cli_args args = {
|
||||||
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
|
.help = false,
|
||||||
|
.version = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
char *argv[] = {"scrcpy", "-v"};
|
||||||
|
|
||||||
|
bool ok = scrcpy_parse_args(&args, 2, argv);
|
||||||
|
assert(ok);
|
||||||
|
assert(!args.help);
|
||||||
|
assert(args.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_flag_help(void) {
|
||||||
|
struct scrcpy_cli_args args = {
|
||||||
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
|
.help = false,
|
||||||
|
.version = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
char *argv[] = {"scrcpy", "-v"};
|
||||||
|
|
||||||
|
bool ok = scrcpy_parse_args(&args, 2, argv);
|
||||||
|
assert(ok);
|
||||||
|
assert(!args.help);
|
||||||
|
assert(args.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_options(void) {
|
||||||
|
struct scrcpy_cli_args args = {
|
||||||
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
|
.help = false,
|
||||||
|
.version = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
char *argv[] = {
|
||||||
|
"scrcpy",
|
||||||
|
"--always-on-top",
|
||||||
|
"--bit-rate", "5M",
|
||||||
|
"--crop", "100:200:300:400",
|
||||||
|
"--fullscreen",
|
||||||
|
"--max-fps", "30",
|
||||||
|
"--max-size", "1024",
|
||||||
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
|
// "--no-display" is not compatible with "--fulscreen"
|
||||||
|
"--port", "1234",
|
||||||
|
"--push-target", "/sdcard/Movies",
|
||||||
|
"--record", "file",
|
||||||
|
"--record-format", "mkv",
|
||||||
|
"--render-expired-frames",
|
||||||
|
"--serial", "0123456789abcdef",
|
||||||
|
"--show-touches",
|
||||||
|
"--turn-screen-off",
|
||||||
|
"--prefer-text",
|
||||||
|
"--window-title", "my device",
|
||||||
|
"--window-x", "100",
|
||||||
|
"--window-y", "-1",
|
||||||
|
"--window-width", "600",
|
||||||
|
"--window-height", "0",
|
||||||
|
"--window-borderless",
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
const struct scrcpy_options *opts = &args.opts;
|
||||||
|
assert(opts->always_on_top);
|
||||||
|
fprintf(stderr, "%d\n", (int) opts->bit_rate);
|
||||||
|
assert(opts->bit_rate == 5000000);
|
||||||
|
assert(!strcmp(opts->crop, "100:200:300:400"));
|
||||||
|
assert(opts->fullscreen);
|
||||||
|
assert(opts->max_fps == 30);
|
||||||
|
assert(opts->max_size == 1024);
|
||||||
|
assert(opts->port == 1234);
|
||||||
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
|
assert(!strcmp(opts->record_filename, "file"));
|
||||||
|
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
||||||
|
assert(opts->render_expired_frames);
|
||||||
|
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
||||||
|
assert(opts->show_touches);
|
||||||
|
assert(opts->turn_screen_off);
|
||||||
|
assert(opts->prefer_text);
|
||||||
|
assert(!strcmp(opts->window_title, "my device"));
|
||||||
|
assert(opts->window_x == 100);
|
||||||
|
assert(opts->window_y == -1);
|
||||||
|
assert(opts->window_width == 600);
|
||||||
|
assert(opts->window_height == 0);
|
||||||
|
assert(opts->window_borderless);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_options2(void) {
|
||||||
|
struct scrcpy_cli_args args = {
|
||||||
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
|
.help = false,
|
||||||
|
.version = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
char *argv[] = {
|
||||||
|
"scrcpy",
|
||||||
|
"--no-control",
|
||||||
|
"--no-display",
|
||||||
|
"--record", "file.mp4", // cannot enable --no-display without recording
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
const struct scrcpy_options *opts = &args.opts;
|
||||||
|
assert(!opts->control);
|
||||||
|
assert(!opts->display);
|
||||||
|
assert(!strcmp(opts->record_filename, "file.mp4"));
|
||||||
|
assert(opts->record_format == RECORDER_FORMAT_MP4);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_flag_version();
|
||||||
|
test_flag_help();
|
||||||
|
test_options();
|
||||||
|
test_options2();
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
@@ -168,6 +170,73 @@ static void test_utf8_truncate(void) {
|
|||||||
assert(count == 7); // no more chars
|
assert(count == 7); // no more chars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_parse_integer(void) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer("1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234);
|
||||||
|
|
||||||
|
ok = parse_integer("-1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234);
|
||||||
|
|
||||||
|
ok = parse_integer("1234k", &value);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
ok = parse_integer("123456789876543212345678987654321", &value);
|
||||||
|
assert(!ok); // out-of-range
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parse_integer_with_suffix(void) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_with_suffix("1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("1234k", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("1234m", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234000000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234k", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234m", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234000000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
|
||||||
|
assert(!ok); // out-of-range
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
|
||||||
|
sprintf(buf, "%ldk", LONG_MAX / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == LONG_MAX / 2000 * 1000);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldm", LONG_MAX / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldk", LONG_MIN / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == LONG_MIN / 2000 * 1000);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldm", LONG_MIN / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(!ok);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
test_xstrncpy_simple();
|
test_xstrncpy_simple();
|
||||||
test_xstrncpy_just_fit();
|
test_xstrncpy_just_fit();
|
||||||
@@ -179,5 +248,7 @@ int main(void) {
|
|||||||
test_xstrjoin_truncated_after_sep();
|
test_xstrjoin_truncated_after_sep();
|
||||||
test_strquote();
|
test_strquote();
|
||||||
test_utf8_truncate();
|
test_utf8_truncate();
|
||||||
|
test_parse_integer();
|
||||||
|
test_parse_integer_with_suffix();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.11',
|
version: '1.12.1',
|
||||||
meson_version: '>= 0.37',
|
meson_version: '>= 0.37',
|
||||||
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 29
|
targetSdkVersion 29
|
||||||
versionCode 12
|
versionCode 14
|
||||||
versionName "1.11"
|
versionName "1.12.1"
|
||||||
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.11
|
SCRCPY_VERSION_NAME=1.12.1
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
private static void suggestFix(Throwable e) {
|
private static void suggestFix(Throwable e) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (e instanceof MediaCodec.CodecException) {
|
if (e instanceof MediaCodec.CodecException) {
|
||||||
|
|||||||
Reference in New Issue
Block a user