Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
8fb01922a3 | |||
521f2fe994 | |||
edc4f7675f | |||
712f1fa6b2 | |||
1ba06037f8 | |||
da63e3774b | |||
cf9d44979c | |||
84f1d9e375 | |||
65d06a3663 | |||
74079ea5e4 | |||
0870d8620f | |||
d49cffb938 | |||
dfb7324d7b | |||
d8b3ba170c | |||
7ad47dfaab | |||
56a115b5c5 | |||
8f64a5984b | |||
bccd12bf5c | |||
20d3925099 | |||
1223a72eb8 | |||
7683be8159 | |||
d4ca85d6a8 | |||
e6e528f228 | |||
a5f8b577c5 | |||
e4bb7c1d1f | |||
1b76d9fd78 | |||
63cb93d7d7 | |||
9d9dd1f143 | |||
eabaf6f7bd | |||
199c74f62f | |||
322f1512ea | |||
30714aba34 | |||
a973757fd1 | |||
deea29f52a | |||
5086e7b744 | |||
5fa46ad0c7 | |||
334964c380 | |||
f7d4b6d0db | |||
e99b896ae2 | |||
e8a565f9ea | |||
8c27f59aa5 | |||
3c1ed5d86c | |||
0ba74fbd9a | |||
29e5af76d4 | |||
dc7b60e619 | |||
42641d2737 | |||
1e4ee547b5 | |||
488d22d4e2 | |||
00d292b2f5 | |||
245999aec4 | |||
d91c5dcfd5 | |||
d202d7b205 | |||
08c0c64af6 | |||
a00a8763d6 | |||
8f314c74b0 | |||
3c0fc8f54f | |||
1b73eff3c9 | |||
6e1069a822 | |||
c4323df976 | |||
8ff07e0c88 | |||
e4efd75766 | |||
0e4a6f462b | |||
8b73c90427 | |||
ef91ab2841 | |||
44fa4a090e | |||
dcde578a50 | |||
93a5c5149d | |||
2ca8318b9d | |||
d499ee53c9 | |||
8f619f337b | |||
fc1dec0270 | |||
274b591d18 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,8 @@
|
|||||||
build/
|
build/
|
||||||
/dist/
|
/dist/
|
||||||
|
/build-*/
|
||||||
|
/build_*/
|
||||||
|
/release-*/
|
||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
/x/
|
/x/
|
||||||
|
19
BUILD.md
19
BUILD.md
@ -176,8 +176,8 @@ Additionally, if you want to build the server, install Java 8 from Caskroom, and
|
|||||||
make it avaliable from the `PATH`:
|
make it avaliable from the `PATH`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew tap caskroom/versions
|
brew tap homebrew/cask-versions
|
||||||
brew cask install java8
|
brew cask install adoptopenjdk/openjdk/adoptopenjdk8
|
||||||
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
|
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
|
||||||
export PATH="$JAVA_HOME/bin:$PATH"
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
```
|
```
|
||||||
@ -190,12 +190,17 @@ See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
|
|||||||
## Common steps
|
## Common steps
|
||||||
|
|
||||||
If you want to build the server, install the [Android SDK] (_Android Studio_),
|
If you want to build the server, install the [Android SDK] (_Android Studio_),
|
||||||
and set `ANDROID_HOME` to its directory. For example:
|
and set `ANDROID_SDK_ROOT` to its directory. For example:
|
||||||
|
|
||||||
[Android SDK]: https://developer.android.com/studio/index.html
|
[Android SDK]: https://developer.android.com/studio/index.html
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ANDROID_HOME=~/android/sdk
|
# Linux
|
||||||
|
export ANDROID_SDK_ROOT=~/Android/Sdk
|
||||||
|
# Mac
|
||||||
|
export ANDROID_SDK_ROOT=~/Library/Android/sdk
|
||||||
|
# Windows
|
||||||
|
set ANDROID_SDK_ROOT=%LOCALAPPDATA%\Android\sdk
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to build the server, use the [prebuilt server].
|
If you don't want to build the server, use the [prebuilt server].
|
||||||
@ -249,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
|
|||||||
|
|
||||||
## Prebuilt server
|
## Prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.15`][direct-scrcpy-server]
|
||||||
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
|
_(SHA-256: e160c46784f30566eae621cad88bfb34e124f945cb8274b8dfc615b613b86dd8)_
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-server-v1.15
|
||||||
|
|
||||||
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:
|
||||||
|
@ -100,11 +100,11 @@ dist-win32: build-server build-win32 build-win32-noconsole
|
|||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
@ -115,11 +115,11 @@ dist-win64: build-server build-win64 build-win64-noconsole
|
|||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.2-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
182
README.md
182
README.md
@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.13)
|
# scrcpy (v1.15)
|
||||||
|
|
||||||
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.
|
||||||
@ -49,6 +49,11 @@ A [Snap] package is available: [`scrcpy`][snap-link].
|
|||||||
|
|
||||||
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
|
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
|
||||||
|
|
||||||
|
For Fedora, a [COPR] package is available: [`scrcpy`][copr-link].
|
||||||
|
|
||||||
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
|
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
@ -69,10 +74,10 @@ hard).
|
|||||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||||
(including `adb`) is available:
|
(including `adb`) is available:
|
||||||
|
|
||||||
- [`scrcpy-win64-v1.13.zip`][direct-win64]
|
- [`scrcpy-win64-v1.15.zip`][direct-win64]
|
||||||
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
|
_(SHA-256: dd514bb591e63ef4cd52a53c30f1153a28f59722d64690eb07bd017849edcba2)_
|
||||||
|
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.15/scrcpy-win64-v1.15.zip
|
||||||
|
|
||||||
It is also available in [Chocolatey]:
|
It is also available in [Chocolatey]:
|
||||||
|
|
||||||
@ -301,7 +306,7 @@ ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
|||||||
From another terminal:
|
From another terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --force-adb-forwrad
|
scrcpy --force-adb-forward
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -354,7 +359,7 @@ scrcpy --fullscreen
|
|||||||
scrcpy -f # short version
|
scrcpy -f # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
Fullscreen can then be toggled dynamically with <kbd>MOD</kbd>+<kbd>f</kbd>.
|
||||||
|
|
||||||
#### Rotation
|
#### Rotation
|
||||||
|
|
||||||
@ -370,18 +375,19 @@ Possibles values are:
|
|||||||
- `2`: 180 degrees
|
- `2`: 180 degrees
|
||||||
- `3`: 90 degrees clockwise
|
- `3`: 90 degrees clockwise
|
||||||
|
|
||||||
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
|
The rotation can also be changed dynamically with <kbd>MOD</kbd>+<kbd>←</kbd>
|
||||||
`Ctrl`+`→` _(right)_.
|
_(left)_ and <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_.
|
||||||
|
|
||||||
Note that _scrcpy_ manages 3 different rotations:
|
Note that _scrcpy_ manages 3 different rotations:
|
||||||
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
|
- <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait and
|
||||||
current running app may refuse, if it does support the requested
|
landscape (the current running app may refuse, if it does support the
|
||||||
orientation).
|
requested orientation).
|
||||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||||
of the video sent from the device to the computer). This affects the
|
of the video sent from the device to the computer). This affects the
|
||||||
recording.
|
recording.
|
||||||
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
- `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
|
||||||
affects only the display, not the recording.
|
rotates only the window content. This affects only the display, not the
|
||||||
|
recording.
|
||||||
|
|
||||||
|
|
||||||
### Other mirroring options
|
### Other mirroring options
|
||||||
@ -417,7 +423,7 @@ The secondary display may only be controlled if the device runs at least Android
|
|||||||
|
|
||||||
#### Stay awake
|
#### Stay awake
|
||||||
|
|
||||||
To prevent the device to sleep after some delay:
|
To prevent the device to sleep after some delay when the device is plugged in:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --stay-awake
|
scrcpy --stay-awake
|
||||||
@ -437,11 +443,16 @@ scrcpy --turn-screen-off
|
|||||||
scrcpy -S
|
scrcpy -S
|
||||||
```
|
```
|
||||||
|
|
||||||
Or by pressing `Ctrl`+`o` at any time.
|
Or by pressing <kbd>MOD</kbd>+<kbd>o</kbd> at any time.
|
||||||
|
|
||||||
To turn it back on, press `POWER` (or `Ctrl`+`p`).
|
To turn it back on, press <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
||||||
|
|
||||||
It can be useful to also prevent the device to sleep:
|
On Android, the `POWER` button always turns the screen on. For convenience, if
|
||||||
|
`POWER` is sent via scrcpy (via right-click or <kbd>Ctrl</kbd>+<kbd>p</kbd>), it
|
||||||
|
will force to turn the screen off after a small delay (on a best effort basis).
|
||||||
|
The physical `POWER` button will still cause the screen to be turned on.
|
||||||
|
|
||||||
|
It can also be useful to prevent the device from sleeping:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --turn-screen-off --stay-awake
|
scrcpy --turn-screen-off --stay-awake
|
||||||
@ -479,27 +490,60 @@ scrcpy -t
|
|||||||
Note that it only shows _physical_ touches (with the finger on the device).
|
Note that it only shows _physical_ touches (with the finger on the device).
|
||||||
|
|
||||||
|
|
||||||
|
#### Disable screensaver
|
||||||
|
|
||||||
|
By default, scrcpy does not prevent the screensaver to run on the computer.
|
||||||
|
|
||||||
|
To disable it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --disable-screensaver
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Input control
|
### Input control
|
||||||
|
|
||||||
#### Rotate device screen
|
#### Rotate device screen
|
||||||
|
|
||||||
Press `Ctrl`+`r` to switch between portrait and landscape modes.
|
Press <kbd>MOD</kbd>+<kbd>r</kbd> to switch between portrait and landscape
|
||||||
|
modes.
|
||||||
|
|
||||||
Note that it rotates only if the application in foreground supports the
|
Note that it rotates only if the application in foreground supports the
|
||||||
requested orientation.
|
requested orientation.
|
||||||
|
|
||||||
#### Copy-paste
|
#### Copy-paste
|
||||||
|
|
||||||
It is possible to synchronize clipboards between the computer and the device, in
|
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||||
both directions:
|
computer clipboard.
|
||||||
|
|
||||||
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
Any <kbd>Ctrl</kbd> shortcut is forwarded to the device. In particular:
|
||||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
|
- <kbd>Ctrl</kbd>+<kbd>c</kbd> typically copies
|
||||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
- <kbd>Ctrl</kbd>+<kbd>x</kbd> typically cuts
|
||||||
breaks non-ASCII characters).
|
- <kbd>Ctrl</kbd>+<kbd>v</kbd> typically pastes (after computer-to-device
|
||||||
|
clipboard synchronization)
|
||||||
|
|
||||||
Moreover, any time the Android clipboard changes, it is automatically
|
This typically works as you expect.
|
||||||
synchronized to the computer clipboard.
|
|
||||||
|
The actual behavior depends on the active application though. For example,
|
||||||
|
_Termux_ sends SIGINT on <kbd>Ctrl</kbd>+<kbd>c</kbd> instead, and _K-9 Mail_
|
||||||
|
composes a new message.
|
||||||
|
|
||||||
|
To copy, cut and paste in such cases (but only supported on Android >= 7):
|
||||||
|
- <kbd>MOD</kbd>+<kbd>c</kbd> injects `COPY`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>x</kbd> injects `CUT`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>v</kbd> injects `PASTE` (after computer-to-device
|
||||||
|
clipboard synchronization)
|
||||||
|
|
||||||
|
In addition, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> allows to inject the
|
||||||
|
computer clipboard text as a sequence of key events. This is useful when the
|
||||||
|
component does not accept text pasting (for example in _Termux_), but it can
|
||||||
|
break non-ASCII content.
|
||||||
|
|
||||||
|
**WARNING:** Pasting the computer clipboard to the device (either via
|
||||||
|
<kbd>Ctrl</kbd>+<kbd>v</kbd> or <kbd>MOD</kbd>+<kbd>v</kbd>) copies the content
|
||||||
|
into the device clipboard. As a consequence, any Android application could read
|
||||||
|
its content. You should avoid to paste sensitive content (like passwords) that
|
||||||
|
way.
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
@ -523,6 +567,18 @@ scrcpy --prefer-text
|
|||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
#### Key repeat
|
||||||
|
|
||||||
|
By default, holding a key down generates repeated key events. This can cause
|
||||||
|
performance problems in some games, where these events are useless anyway.
|
||||||
|
|
||||||
|
To avoid forwarding repeated key events:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-key-repeat
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### File drop
|
### File drop
|
||||||
|
|
||||||
#### Install APK
|
#### Install APK
|
||||||
@ -549,42 +605,66 @@ scrcpy --push-target /sdcard/foo/bar/
|
|||||||
|
|
||||||
### Audio forwarding
|
### Audio forwarding
|
||||||
|
|
||||||
Audio is not forwarded by _scrcpy_. Use [USBaudio] (Linux-only).
|
Audio is not forwarded by _scrcpy_. Use [sndcpy].
|
||||||
|
|
||||||
Also see [issue #14].
|
Also see [issue #14].
|
||||||
|
|
||||||
[USBaudio]: https://github.com/rom1v/usbaudio
|
[sndcpy]: https://github.com/rom1v/sndcpy
|
||||||
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
|
|
||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
| Action | Shortcut | Shortcut (macOS)
|
In the following list, <kbd>MOD</kbd> is the shortcut modifier. By default, it's
|
||||||
| -------------------------------------- |:----------------------------- |:-----------------------------
|
(left) <kbd>Alt</kbd> or (left) <kbd>Super</kbd>.
|
||||||
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
|
||||||
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
|
||||||
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
`lalt`, `ralt`, `lsuper` and `rsuper`. For example:
|
||||||
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
|
|
||||||
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
|
```bash
|
||||||
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
# use RCtrl for shortcuts
|
||||||
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
scrcpy --shortcut-mod=rctrl
|
||||||
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
|
||||||
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
# use either LCtrl+LAlt or LSuper for shortcuts
|
||||||
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||||
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
```
|
||||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
|
||||||
| Power on | _Right-click²_ | _Right-click²_
|
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||||
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
|
||||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
||||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
|
||||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
| Action | Shortcut
|
||||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
| ------------------------------------------- |:-----------------------------
|
||||||
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
| Switch fullscreen mode | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
|
||||||
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
||||||
|
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
|
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-click¹_
|
||||||
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
|
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
||||||
|
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
|
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
|
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
||||||
|
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
|
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
|
| Power on | _Right-click²_
|
||||||
|
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
|
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
|
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
|
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
|
| Collapse notification panel | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
|
| Copy to clipboard³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
|
| Cut to clipboard³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
|
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
|
_³Only on Android >= 7._
|
||||||
|
|
||||||
|
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
|
||||||
|
handled by the active application.
|
||||||
|
|
||||||
|
|
||||||
## Custom paths
|
## Custom paths
|
||||||
|
@ -164,12 +164,12 @@ if get_option('buildtype') == 'debug'
|
|||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_control_event_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_device_event_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
'tests/test_device_msg_deserialize.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
]],
|
]],
|
||||||
@ -186,7 +186,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
exe = executable(t[0], t[1],
|
exe = executable(t[0], t[1],
|
||||||
include_directories: src_dir,
|
include_directories: src_dir,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
c_args: ['-DSDL_MAIN_HANDLED'])
|
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
|
||||||
test(t[0], exe)
|
test(t[0], exe)
|
||||||
endforeach
|
endforeach
|
||||||
endif
|
endif
|
||||||
|
78
app/scrcpy.1
78
app/scrcpy.1
@ -43,6 +43,10 @@ The values are expressed in the device natural orientation (typically, portrait
|
|||||||
.B \-\-max\-size
|
.B \-\-max\-size
|
||||||
value is computed on the cropped size.
|
value is computed on the cropped size.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-disable-screensaver"
|
||||||
|
Disable screensaver while scrcpy is running.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-display " id
|
.BI "\-\-display " id
|
||||||
Specify the display id to mirror.
|
Specify the display id to mirror.
|
||||||
@ -92,6 +96,10 @@ Do not display device (only when screen recording is enabled).
|
|||||||
.B \-\-no\-mipmaps
|
.B \-\-no\-mipmaps
|
||||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-key\-repeat
|
||||||
|
Do not forward repeated key events when a key is held down.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port[:port]
|
.BI "\-p, \-\-port " port[:port]
|
||||||
Set the TCP port (range) used by the client to listen.
|
Set the TCP port (range) used by the client to listen.
|
||||||
@ -129,6 +137,7 @@ Force recording format (either mp4 or mkv).
|
|||||||
Request SDL to use the given render driver (this is just a hint).
|
Request SDL to use the given render driver (this is just a hint).
|
||||||
|
|
||||||
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
|
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
|
||||||
|
|
||||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||||
.UE
|
.UE
|
||||||
|
|
||||||
@ -144,6 +153,16 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
|
|||||||
.BI "\-s, \-\-serial " number
|
.BI "\-s, \-\-serial " number
|
||||||
The device serial number. Mandatory only if several devices are connected to adb.
|
The device serial number. Mandatory only if several devices are connected to adb.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-shortcut\-mod " key[+...]][,...]
|
||||||
|
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||||
|
|
||||||
|
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||||
|
|
||||||
|
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
|
||||||
|
|
||||||
|
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-S, \-\-turn\-screen\-off
|
.B \-S, \-\-turn\-screen\-off
|
||||||
Turn the device screen off immediately.
|
Turn the device screen off immediately.
|
||||||
@ -166,7 +185,7 @@ Default is "info" for release builds, "debug" for debug builds.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-w, \-\-stay-awake
|
.B \-w, \-\-stay-awake
|
||||||
Keep the device on while scrcpy is running.
|
Keep the device on while scrcpy is running, when the device is plugged in.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-window\-borderless
|
.B \-\-window\-borderless
|
||||||
@ -202,52 +221,55 @@ Default is 0 (automatic).\n
|
|||||||
|
|
||||||
.SH SHORTCUTS
|
.SH SHORTCUTS
|
||||||
|
|
||||||
|
In the following list, MOD is the shortcut modifier. By default, it's (left)
|
||||||
|
Alt or (left) Super, but it can be configured by \-\-shortcut-mod.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+f
|
.B MOD+f
|
||||||
Switch fullscreen mode
|
Switch fullscreen mode
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Left
|
.B MOD+Left
|
||||||
Rotate display left
|
Rotate display left
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Right
|
.B MOD+Right
|
||||||
Rotate display right
|
Rotate display right
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+g
|
.B MOD+g
|
||||||
Resize window to 1:1 (pixel\-perfect)
|
Resize window to 1:1 (pixel\-perfect)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+x, Double\-click on black borders
|
.B MOD+w, Double\-click on black borders
|
||||||
Resize window to remove black borders
|
Resize window to remove black borders
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+h, Home, Middle\-click
|
.B MOD+h, Home, Middle\-click
|
||||||
Click on HOME
|
Click on HOME
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
|
.B MOD+b, MOD+Backspace, Right\-click (when screen is on)
|
||||||
Click on BACK
|
Click on BACK
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+s
|
.B MOD+s
|
||||||
Click on APP_SWITCH
|
Click on APP_SWITCH
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+m
|
.B MOD+m
|
||||||
Click on MENU
|
Click on MENU
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Up
|
.B MOD+Up
|
||||||
Click on VOLUME_UP
|
Click on VOLUME_UP
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Down
|
.B MOD+Down
|
||||||
Click on VOLUME_DOWN
|
Click on VOLUME_DOWN
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+p
|
.B MOD+p
|
||||||
Click on POWER (turn screen on/off)
|
Click on POWER (turn screen on/off)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -255,35 +277,43 @@ Click on POWER (turn screen on/off)
|
|||||||
Turn screen on
|
Turn screen on
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+o
|
.B MOD+o
|
||||||
Turn device screen off (keep mirroring)
|
Turn device screen off (keep mirroring)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+r
|
.B MOD+Shift+o
|
||||||
|
Turn device screen on
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B MOD+r
|
||||||
Rotate device screen
|
Rotate device screen
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+n
|
.B MOD+n
|
||||||
Expand notification panel
|
Expand notification panel
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Shift+n
|
.B MOD+Shift+n
|
||||||
Collapse notification panel
|
Collapse notification panel
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+c
|
.B Mod+c
|
||||||
Copy device clipboard to computer
|
Copy to clipboard (inject COPY keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+v
|
.B Mod+x
|
||||||
Paste computer clipboard to device
|
Cut to clipboard (inject CUT keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+Shift+v
|
.B MOD+v
|
||||||
Copy computer clipboard to device
|
Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+i
|
.B MOD+Shift+v
|
||||||
|
Inject computer clipboard text as a sequence of key events
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B MOD+i
|
||||||
Enable/disable FPS counter (print frames/second in logs)
|
Enable/disable FPS counter (print frames/second in logs)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
|
220
app/src/cli.c
220
app/src/cli.c
@ -3,20 +3,16 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "recorder.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
#ifdef __APPLE__
|
|
||||||
# define CTRL_OR_CMD "Cmd"
|
|
||||||
#else
|
|
||||||
# define CTRL_OR_CMD "Ctrl"
|
|
||||||
#endif
|
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: %s [options]\n"
|
"Usage: %s [options]\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -45,6 +41,9 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" (typically, portrait for a phone, landscape for a tablet).\n"
|
" (typically, portrait for a phone, landscape for a tablet).\n"
|
||||||
" Any --max-size value is computed on the cropped size.\n"
|
" Any --max-size value is computed on the cropped size.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --disable-screensaver\n"
|
||||||
|
" Disable screensaver while scrcpy is running.\n"
|
||||||
|
"\n"
|
||||||
" --display id\n"
|
" --display id\n"
|
||||||
" Specify the display id to mirror.\n"
|
" Specify the display id to mirror.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -93,6 +92,9 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" mipmaps are automatically generated to improve downscaling\n"
|
" mipmaps are automatically generated to improve downscaling\n"
|
||||||
" quality. This option disables the generation of mipmaps.\n"
|
" quality. This option disables the generation of mipmaps.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --no-key-repeat\n"
|
||||||
|
" Do not forward repeated key events when a key is held down.\n"
|
||||||
|
"\n"
|
||||||
" -p, --port port[:port]\n"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port (range) used by the client to listen.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is %d:%d.\n"
|
" Default is %d:%d.\n"
|
||||||
@ -139,6 +141,19 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" The device serial number. Mandatory only if several devices\n"
|
" The device serial number. Mandatory only if several devices\n"
|
||||||
" are connected to adb.\n"
|
" are connected to adb.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --shortcut-mod key[+...]][,...]\n"
|
||||||
|
" Specify the modifiers to use for scrcpy shortcuts.\n"
|
||||||
|
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
|
||||||
|
" \"lsuper\" and \"rsuper\".\n"
|
||||||
|
"\n"
|
||||||
|
" A shortcut can consist in several keys, separated by '+'.\n"
|
||||||
|
" Several shortcuts can be specified, separated by ','.\n"
|
||||||
|
"\n"
|
||||||
|
" For example, to use either LCtrl+LAlt or LSuper for scrcpy\n"
|
||||||
|
" shortcuts, pass \"lctrl+lalt,lsuper\".\n"
|
||||||
|
"\n"
|
||||||
|
" Default is \"lalt,lsuper\" (left-Alt or left-Super).\n"
|
||||||
|
"\n"
|
||||||
" -S, --turn-screen-off\n"
|
" -S, --turn-screen-off\n"
|
||||||
" Turn the device screen off immediately.\n"
|
" Turn the device screen off immediately.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -159,7 +174,8 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
#endif
|
#endif
|
||||||
"\n"
|
"\n"
|
||||||
" -w, --stay-awake\n"
|
" -w, --stay-awake\n"
|
||||||
" Keep the device on while scrcpy is running.\n"
|
" Keep the device on while scrcpy is running, when the device\n"
|
||||||
|
" is plugged in.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --window-borderless\n"
|
" --window-borderless\n"
|
||||||
" Disable window decorations (display borderless window).\n"
|
" Disable window decorations (display borderless window).\n"
|
||||||
@ -185,71 +201,82 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
"Shortcuts:\n"
|
"Shortcuts:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+f\n"
|
" In the following list, MOD is the shortcut modifier. By default,\n"
|
||||||
|
" it's (left) Alt or (left) Super, but it can be configured by\n"
|
||||||
|
" --shortcut-mod.\n"
|
||||||
|
"\n"
|
||||||
|
" MOD+f\n"
|
||||||
" Switch fullscreen mode\n"
|
" Switch fullscreen mode\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Left\n"
|
" MOD+Left\n"
|
||||||
" Rotate display left\n"
|
" Rotate display left\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Right\n"
|
" MOD+Right\n"
|
||||||
" Rotate display right\n"
|
" Rotate display right\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+g\n"
|
" MOD+g\n"
|
||||||
" Resize window to 1:1 (pixel-perfect)\n"
|
" Resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+x\n"
|
" MOD+w\n"
|
||||||
" Double-click on black borders\n"
|
" Double-click on black borders\n"
|
||||||
" Resize window to remove black borders\n"
|
" Resize window to remove black borders\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+h\n"
|
" MOD+h\n"
|
||||||
" Middle-click\n"
|
" Middle-click\n"
|
||||||
" Click on HOME\n"
|
" Click on HOME\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+b\n"
|
" MOD+b\n"
|
||||||
" " CTRL_OR_CMD "+Backspace\n"
|
" MOD+Backspace\n"
|
||||||
" Right-click (when screen is on)\n"
|
" Right-click (when screen is on)\n"
|
||||||
" Click on BACK\n"
|
" Click on BACK\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+s\n"
|
" MOD+s\n"
|
||||||
" Click on APP_SWITCH\n"
|
" Click on APP_SWITCH\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+m\n"
|
" MOD+m\n"
|
||||||
" Click on MENU\n"
|
" Click on MENU\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Up\n"
|
" MOD+Up\n"
|
||||||
" Click on VOLUME_UP\n"
|
" Click on VOLUME_UP\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Down\n"
|
" MOD+Down\n"
|
||||||
" Click on VOLUME_DOWN\n"
|
" Click on VOLUME_DOWN\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+p\n"
|
" MOD+p\n"
|
||||||
" Click on POWER (turn screen on/off)\n"
|
" Click on POWER (turn screen on/off)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Right-click (when screen is off)\n"
|
" Right-click (when screen is off)\n"
|
||||||
" Power on\n"
|
" Power on\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+o\n"
|
" MOD+o\n"
|
||||||
" Turn device screen off (keep mirroring)\n"
|
" Turn device screen off (keep mirroring)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+r\n"
|
" MOD+Shift+o\n"
|
||||||
|
" Turn device screen on\n"
|
||||||
|
"\n"
|
||||||
|
" MOD+r\n"
|
||||||
" Rotate device screen\n"
|
" Rotate device screen\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+n\n"
|
" MOD+n\n"
|
||||||
" Expand notification panel\n"
|
" Expand notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Shift+n\n"
|
" MOD+Shift+n\n"
|
||||||
" Collapse notification panel\n"
|
" Collapse notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+c\n"
|
" MOD+c\n"
|
||||||
" Copy device clipboard to computer\n"
|
" Copy to clipboard (inject COPY keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+v\n"
|
" MOD+x\n"
|
||||||
" Paste computer clipboard to device\n"
|
" Cut to clipboard (inject CUT keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+Shift+v\n"
|
" MOD+v\n"
|
||||||
" Copy computer clipboard to device\n"
|
" Copy computer clipboard to device, then paste (inject PASTE\n"
|
||||||
|
" keycode, Android >= 7 only)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" " CTRL_OR_CMD "+i\n"
|
" MOD+Shift+v\n"
|
||||||
|
" Inject computer clipboard text as a sequence of key events\n"
|
||||||
|
"\n"
|
||||||
|
" MOD+i\n"
|
||||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
@ -374,10 +401,10 @@ parse_rotation(const char *s, uint8_t *rotation) {
|
|||||||
static bool
|
static bool
|
||||||
parse_window_position(const char *s, int16_t *position) {
|
parse_window_position(const char *s, int16_t *position) {
|
||||||
// special value for "auto"
|
// special value for "auto"
|
||||||
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
|
static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
|
||||||
|
|
||||||
if (!strcmp(s, "auto")) {
|
if (!strcmp(s, "auto")) {
|
||||||
*position = WINDOW_POSITION_UNDEFINED;
|
*position = SC_WINDOW_POSITION_UNDEFINED;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +433,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_port_range(const char *s, struct port_range *port_range) {
|
parse_port_range(const char *s, struct sc_port_range *port_range) {
|
||||||
long values[2];
|
long values[2];
|
||||||
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
|
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
|
||||||
if (!count) {
|
if (!count) {
|
||||||
@ -471,21 +498,116 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
|
||||||
|
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
|
||||||
|
static unsigned
|
||||||
|
parse_shortcut_mods_item(const char *item, size_t len) {
|
||||||
|
unsigned mod = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char *plus = strchr(item, '+');
|
||||||
|
// strchr() does not consider the "len" parameter, to it could find an
|
||||||
|
// occurrence too far in the string (there is no strnchr())
|
||||||
|
bool has_plus = plus && plus < item + len;
|
||||||
|
|
||||||
|
assert(!has_plus || plus > item);
|
||||||
|
size_t key_len = has_plus ? (size_t) (plus - item) : len;
|
||||||
|
|
||||||
|
#define STREQ(literal, s, len) \
|
||||||
|
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
|
||||||
|
|
||||||
|
if (STREQ("lctrl", item, key_len)) {
|
||||||
|
mod |= SC_MOD_LCTRL;
|
||||||
|
} else if (STREQ("rctrl", item, key_len)) {
|
||||||
|
mod |= SC_MOD_RCTRL;
|
||||||
|
} else if (STREQ("lalt", item, key_len)) {
|
||||||
|
mod |= SC_MOD_LALT;
|
||||||
|
} else if (STREQ("ralt", item, key_len)) {
|
||||||
|
mod |= SC_MOD_RALT;
|
||||||
|
} else if (STREQ("lsuper", item, key_len)) {
|
||||||
|
mod |= SC_MOD_LSUPER;
|
||||||
|
} else if (STREQ("rsuper", item, key_len)) {
|
||||||
|
mod |= SC_MOD_RSUPER;
|
||||||
|
} else {
|
||||||
|
LOGW("Unknown modifier key: %.*s", (int) key_len, item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#undef STREQ
|
||||||
|
|
||||||
|
if (!has_plus) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = plus + 1;
|
||||||
|
assert(len >= key_len + 1);
|
||||||
|
len -= key_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_record_format(const char *optarg, enum recorder_format *format) {
|
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||||
|
unsigned count = 0;
|
||||||
|
unsigned current = 0;
|
||||||
|
|
||||||
|
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper"
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char *comma = strchr(s, ',');
|
||||||
|
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
|
||||||
|
assert(count < SC_MAX_SHORTCUT_MODS);
|
||||||
|
LOGW("Too many shortcut modifiers alternatives");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!comma || comma > s);
|
||||||
|
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
|
||||||
|
|
||||||
|
unsigned mod = parse_shortcut_mods_item(s, limit);
|
||||||
|
if (!mod) {
|
||||||
|
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mods->data[current++] = mod;
|
||||||
|
++count;
|
||||||
|
|
||||||
|
if (!comma) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = comma + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mods->count = count;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SC_TEST
|
||||||
|
// expose the function to unit-tests
|
||||||
|
bool
|
||||||
|
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||||
|
return parse_shortcut_mods(s, mods);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_record_format(const char *optarg, enum sc_record_format *format) {
|
||||||
if (!strcmp(optarg, "mp4")) {
|
if (!strcmp(optarg, "mp4")) {
|
||||||
*format = RECORDER_FORMAT_MP4;
|
*format = SC_RECORD_FORMAT_MP4;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!strcmp(optarg, "mkv")) {
|
if (!strcmp(optarg, "mkv")) {
|
||||||
*format = RECORDER_FORMAT_MKV;
|
*format = SC_RECORD_FORMAT_MKV;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
|
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum recorder_format
|
static enum sc_record_format
|
||||||
guess_record_format(const char *filename) {
|
guess_record_format(const char *filename) {
|
||||||
size_t len = strlen(filename);
|
size_t len = strlen(filename);
|
||||||
if (len < 4) {
|
if (len < 4) {
|
||||||
@ -493,10 +615,10 @@ guess_record_format(const char *filename) {
|
|||||||
}
|
}
|
||||||
const char *ext = &filename[len - 4];
|
const char *ext = &filename[len - 4];
|
||||||
if (!strcmp(ext, ".mp4")) {
|
if (!strcmp(ext, ".mp4")) {
|
||||||
return RECORDER_FORMAT_MP4;
|
return SC_RECORD_FORMAT_MP4;
|
||||||
}
|
}
|
||||||
if (!strcmp(ext, ".mkv")) {
|
if (!strcmp(ext, ".mkv")) {
|
||||||
return RECORDER_FORMAT_MKV;
|
return SC_RECORD_FORMAT_MKV;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -521,6 +643,9 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_NO_MIPMAPS 1017
|
#define OPT_NO_MIPMAPS 1017
|
||||||
#define OPT_CODEC_OPTIONS 1018
|
#define OPT_CODEC_OPTIONS 1018
|
||||||
#define OPT_FORCE_ADB_FORWARD 1019
|
#define OPT_FORCE_ADB_FORWARD 1019
|
||||||
|
#define OPT_DISABLE_SCREENSAVER 1020
|
||||||
|
#define OPT_SHORTCUT_MOD 1021
|
||||||
|
#define OPT_NO_KEY_REPEAT 1022
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
@ -529,6 +654,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"bit-rate", required_argument, NULL, 'b'},
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
|
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
|
||||||
{"crop", required_argument, NULL, OPT_CROP},
|
{"crop", required_argument, NULL, OPT_CROP},
|
||||||
|
{"disable-screensaver", no_argument, NULL,
|
||||||
|
OPT_DISABLE_SCREENSAVER},
|
||||||
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
||||||
{"force-adb-forward", no_argument, NULL,
|
{"force-adb-forward", no_argument, NULL,
|
||||||
OPT_FORCE_ADB_FORWARD},
|
OPT_FORCE_ADB_FORWARD},
|
||||||
@ -541,6 +668,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"no-control", no_argument, NULL, 'n'},
|
{"no-control", no_argument, NULL, 'n'},
|
||||||
{"no-display", no_argument, NULL, 'N'},
|
{"no-display", no_argument, NULL, 'N'},
|
||||||
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
||||||
|
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
|
||||||
{"port", required_argument, NULL, 'p'},
|
{"port", required_argument, NULL, 'p'},
|
||||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||||
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||||
@ -551,6 +679,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
OPT_RENDER_EXPIRED_FRAMES},
|
OPT_RENDER_EXPIRED_FRAMES},
|
||||||
{"rotation", required_argument, NULL, OPT_ROTATION},
|
{"rotation", required_argument, NULL, OPT_ROTATION},
|
||||||
{"serial", required_argument, NULL, 's'},
|
{"serial", required_argument, NULL, 's'},
|
||||||
|
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
{"stay-awake", no_argument, NULL, 'w'},
|
{"stay-awake", no_argument, NULL, 'w'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
@ -705,12 +834,23 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case OPT_NO_MIPMAPS:
|
case OPT_NO_MIPMAPS:
|
||||||
opts->mipmaps = false;
|
opts->mipmaps = false;
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_KEY_REPEAT:
|
||||||
|
opts->forward_key_repeat = false;
|
||||||
|
break;
|
||||||
case OPT_CODEC_OPTIONS:
|
case OPT_CODEC_OPTIONS:
|
||||||
opts->codec_options = optarg;
|
opts->codec_options = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_FORCE_ADB_FORWARD:
|
case OPT_FORCE_ADB_FORWARD:
|
||||||
opts->force_adb_forward = true;
|
opts->force_adb_forward = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_DISABLE_SCREENSAVER:
|
||||||
|
opts->disable_screensaver = true;
|
||||||
|
break;
|
||||||
|
case OPT_SHORTCUT_MOD:
|
||||||
|
if (!parse_shortcut_mods(optarg, &opts->shortcut_mods)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
@ -18,4 +18,9 @@ scrcpy_print_usage(const char *arg0);
|
|||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
||||||
|
|
||||||
|
#ifdef SC_TEST
|
||||||
|
bool
|
||||||
|
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,9 +20,9 @@ write_position(uint8_t *buf, const struct position *position) {
|
|||||||
static size_t
|
static size_t
|
||||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||||
size_t len = utf8_truncation_index(utf8, max_len);
|
size_t len = utf8_truncation_index(utf8, max_len);
|
||||||
buffer_write16be(buf, (uint16_t) len);
|
buffer_write32be(buf, len);
|
||||||
memcpy(&buf[2], utf8, len);
|
memcpy(&buf[4], utf8, len);
|
||||||
return 2 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
static uint16_t
|
||||||
@ -42,8 +42,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||||
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
|
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||||
return 10;
|
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||||
|
return 14;
|
||||||
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
size_t len =
|
size_t len =
|
||||||
write_string(msg->inject_text.text,
|
write_string(msg->inject_text.text,
|
||||||
@ -67,10 +68,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
return 21;
|
return 21;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
size_t len = write_string(msg->inject_text.text,
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
&buf[1]);
|
&buf[2]);
|
||||||
return 1 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
|
|
||||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||||
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ struct control_msg {
|
|||||||
struct {
|
struct {
|
||||||
enum android_keyevent_action action;
|
enum android_keyevent_action action;
|
||||||
enum android_keycode keycode;
|
enum android_keycode keycode;
|
||||||
|
uint32_t repeat;
|
||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} inject_keycode;
|
} inject_keycode;
|
||||||
struct {
|
struct {
|
||||||
@ -62,6 +64,7 @@ struct control_msg {
|
|||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by SDL_free()
|
||||||
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
enum screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
@ -69,7 +72,7 @@ struct control_msg {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// buf size must be at least CONTROL_MSG_SERIALIZED_MAX_SIZE
|
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
||||||
// return the number of bytes written
|
// return the number of bytes written
|
||||||
size_t
|
size_t
|
||||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
@ -60,7 +60,7 @@ controller_push_msg(struct controller *controller,
|
|||||||
static bool
|
static bool
|
||||||
process_msg(struct controller *controller,
|
process_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||||
int length = control_msg_serialize(msg, serialized_msg);
|
int length = control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
ssize_t
|
ssize_t
|
||||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||||
struct device_msg *msg) {
|
struct device_msg *msg) {
|
||||||
if (len < 3) {
|
if (len < 5) {
|
||||||
// at least type + empty string length
|
// at least type + empty string length
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
@ -17,8 +17,8 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
msg->type = buf[0];
|
msg->type = buf[0];
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
uint16_t clipboard_len = buffer_read16be(&buf[1]);
|
size_t clipboard_len = buffer_read32be(&buf[1]);
|
||||||
if (clipboard_len > len - 3) {
|
if (clipboard_len > len - 5) {
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
char *text = SDL_malloc(clipboard_len + 1);
|
char *text = SDL_malloc(clipboard_len + 1);
|
||||||
@ -27,12 +27,12 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (clipboard_len) {
|
if (clipboard_len) {
|
||||||
memcpy(text, &buf[3], clipboard_len);
|
memcpy(text, &buf[5], clipboard_len);
|
||||||
}
|
}
|
||||||
text[clipboard_len] = '\0';
|
text[clipboard_len] = '\0';
|
||||||
|
|
||||||
msg->clipboard.text = text;
|
msg->clipboard.text = text;
|
||||||
return 3 + clipboard_len;
|
return 5 + clipboard_len;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
// type: 1 byte; length: 4 bytes
|
||||||
|
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
|
||||||
|
|
||||||
enum device_msg_type {
|
enum device_msg_type {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
|
@ -92,6 +92,10 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|||||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
|
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||||
|
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
|
||||||
|
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||||
|
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||||
@ -111,8 +115,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer_text) {
|
if (prefer_text && !(mod & KMOD_CTRL)) {
|
||||||
// do not forward alpha and space key events
|
// do not forward alpha and space key events (unless Ctrl is pressed)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <SDL2/SDL_keycode.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
@ -10,6 +11,67 @@
|
|||||||
static const int ACTION_DOWN = 1;
|
static const int ACTION_DOWN = 1;
|
||||||
static const int ACTION_UP = 1 << 1;
|
static const int ACTION_UP = 1 << 1;
|
||||||
|
|
||||||
|
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
|
||||||
|
|
||||||
|
static inline uint16_t
|
||||||
|
to_sdl_mod(unsigned mod) {
|
||||||
|
uint16_t sdl_mod = 0;
|
||||||
|
if (mod & SC_MOD_LCTRL) {
|
||||||
|
sdl_mod |= KMOD_LCTRL;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_RCTRL) {
|
||||||
|
sdl_mod |= KMOD_RCTRL;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_LALT) {
|
||||||
|
sdl_mod |= KMOD_LALT;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_RALT) {
|
||||||
|
sdl_mod |= KMOD_RALT;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_LSUPER) {
|
||||||
|
sdl_mod |= KMOD_LGUI;
|
||||||
|
}
|
||||||
|
if (mod & SC_MOD_RSUPER) {
|
||||||
|
sdl_mod |= KMOD_RGUI;
|
||||||
|
}
|
||||||
|
return sdl_mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
|
||||||
|
// keep only the relevant modifier keys
|
||||||
|
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
|
||||||
|
|
||||||
|
assert(im->sdl_shortcut_mods.count);
|
||||||
|
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
|
||||||
|
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
|
||||||
|
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_init(struct input_manager *im,
|
||||||
|
const struct scrcpy_options *options)
|
||||||
|
{
|
||||||
|
im->control = options->control;
|
||||||
|
im->forward_key_repeat = options->forward_key_repeat;
|
||||||
|
im->prefer_text = options->prefer_text;
|
||||||
|
|
||||||
|
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
|
||||||
|
assert(shortcut_mods->count);
|
||||||
|
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
||||||
|
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
||||||
|
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
|
||||||
|
assert(sdl_mod);
|
||||||
|
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
||||||
|
}
|
||||||
|
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
send_keycode(struct controller *controller, enum android_keycode keycode,
|
send_keycode(struct controller *controller, enum android_keycode keycode,
|
||||||
int actions, const char *name) {
|
int actions, const char *name) {
|
||||||
@ -18,6 +80,7 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
|
|||||||
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||||
msg.inject_keycode.keycode = keycode;
|
msg.inject_keycode.keycode = keycode;
|
||||||
msg.inject_keycode.metastate = 0;
|
msg.inject_keycode.metastate = 0;
|
||||||
|
msg.inject_keycode.repeat = 0;
|
||||||
|
|
||||||
if (actions & ACTION_DOWN) {
|
if (actions & ACTION_DOWN) {
|
||||||
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
|
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
|
||||||
@ -70,6 +133,16 @@ action_menu(struct controller *controller, int actions) {
|
|||||||
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
action_copy(struct controller *controller, int actions) {
|
||||||
|
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
action_cut(struct controller *controller, int actions) {
|
||||||
|
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
|
||||||
|
}
|
||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller) {
|
||||||
@ -102,17 +175,7 @@ collapse_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
request_device_clipboard(struct controller *controller) {
|
set_device_clipboard(struct controller *controller, bool paste) {
|
||||||
struct control_msg msg;
|
|
||||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
|
||||||
LOGW("Could not request device clipboard");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
set_device_clipboard(struct controller *controller) {
|
|
||||||
char *text = SDL_GetClipboardText();
|
char *text = SDL_GetClipboardText();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
||||||
@ -127,6 +190,7 @@ set_device_clipboard(struct controller *controller) {
|
|||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||||
msg.set_clipboard.text = text;
|
msg.set_clipboard.text = text;
|
||||||
|
msg.set_clipboard.paste = paste;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
SDL_free(text);
|
||||||
@ -209,6 +273,10 @@ rotate_client_right(struct screen *screen) {
|
|||||||
void
|
void
|
||||||
input_manager_process_text_input(struct input_manager *im,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
|
if (is_shortcut_mod(im, SDL_GetModState())) {
|
||||||
|
// A shortcut must never generate text events
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!im->prefer_text) {
|
if (!im->prefer_text) {
|
||||||
char c = event->text[0];
|
char c = event->text[0];
|
||||||
if (isalpha(c) || c == ' ') {
|
if (isalpha(c) || c == ' ') {
|
||||||
@ -233,7 +301,7 @@ input_manager_process_text_input(struct input_manager *im,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
bool prefer_text) {
|
bool prefer_text, uint32_t repeat) {
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||||
|
|
||||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||||
@ -246,6 +314,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
to->inject_keycode.repeat = repeat;
|
||||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -253,139 +322,125 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_key(struct input_manager *im,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event) {
|
||||||
bool control) {
|
|
||||||
// control: indicates the state of the command-line option --no-control
|
// control: indicates the state of the command-line option --no-control
|
||||||
// ctrl: the Ctrl key
|
bool control = im->control;
|
||||||
|
|
||||||
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
bool smod = is_shortcut_mod(im, event->keysym.mod);
|
||||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
|
||||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
|
||||||
|
|
||||||
// use Cmd on macOS, Ctrl on other platforms
|
|
||||||
#ifdef __APPLE__
|
|
||||||
bool cmd = !ctrl && meta;
|
|
||||||
#else
|
|
||||||
if (meta) {
|
|
||||||
// no shortcuts involve Meta on platforms other than macOS, and it must
|
|
||||||
// not be forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool cmd = ctrl; // && !meta, already guaranteed
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (alt) {
|
|
||||||
// no shortcuts involve Alt, and it must not be forwarded to the device
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
// capture all Ctrl events
|
|
||||||
if (ctrl || cmd) {
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
|
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||||
bool repeat = event->repeat;
|
bool repeat = event->repeat;
|
||||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
|
// The shortcut modifier is pressed
|
||||||
|
if (smod) {
|
||||||
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
if (control && !shift && !repeat) {
|
||||||
// the system on macOS to hide the window
|
|
||||||
if (control && ctrl && !meta && !shift && !repeat) {
|
|
||||||
action_home(controller, action);
|
action_home(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_b: // fall-through
|
case SDLK_b: // fall-through
|
||||||
case SDLK_BACKSPACE:
|
case SDLK_BACKSPACE:
|
||||||
if (control && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_back(controller, action);
|
action_back(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_s:
|
case SDLK_s:
|
||||||
if (control && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_app_switch(controller, action);
|
action_app_switch(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_m:
|
case SDLK_m:
|
||||||
// Ctrl+m on all platform, since Cmd+m is already captured by
|
if (control && !shift && !repeat) {
|
||||||
// the system on macOS to minimize the window
|
|
||||||
if (control && ctrl && !meta && !shift && !repeat) {
|
|
||||||
action_menu(controller, action);
|
action_menu(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (control && cmd && !shift && !repeat) {
|
if (control && !shift && !repeat) {
|
||||||
action_power(controller, action);
|
action_power(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
if (control && cmd && !shift && down) {
|
if (control && !repeat && down) {
|
||||||
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
|
enum screen_power_mode mode = shift
|
||||||
|
? SCREEN_POWER_MODE_NORMAL
|
||||||
|
: SCREEN_POWER_MODE_OFF;
|
||||||
|
set_screen_power_mode(controller, mode);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
if (control && cmd && !shift) {
|
if (control && !shift) {
|
||||||
// forward repeated events
|
// forward repeated events
|
||||||
action_volume_down(controller, action);
|
action_volume_down(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_UP:
|
case SDLK_UP:
|
||||||
if (control && cmd && !shift) {
|
if (control && !shift) {
|
||||||
// forward repeated events
|
// forward repeated events
|
||||||
action_volume_up(controller, action);
|
action_volume_up(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_LEFT:
|
case SDLK_LEFT:
|
||||||
if (cmd && !shift && down) {
|
if (!shift && !repeat && down) {
|
||||||
rotate_client_left(im->screen);
|
rotate_client_left(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_RIGHT:
|
case SDLK_RIGHT:
|
||||||
if (cmd && !shift && down) {
|
if (!shift && !repeat && down) {
|
||||||
rotate_client_right(im->screen);
|
rotate_client_right(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_c:
|
case SDLK_c:
|
||||||
if (control && cmd && !shift && !repeat && down) {
|
if (control && !shift && !repeat) {
|
||||||
request_device_clipboard(controller);
|
action_copy(controller, action);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case SDLK_x:
|
||||||
|
if (control && !shift && !repeat) {
|
||||||
|
action_cut(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && cmd && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
// store the text in the device clipboard
|
|
||||||
set_device_clipboard(controller);
|
|
||||||
} else {
|
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
|
} else {
|
||||||
|
// store the text in the device clipboard and paste
|
||||||
|
set_device_clipboard(controller, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
screen_switch_fullscreen(im->screen);
|
screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_w:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_g:
|
case SDLK_g:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
screen_resize_to_pixel_perfect(im->screen);
|
screen_resize_to_pixel_perfect(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_i:
|
case SDLK_i:
|
||||||
if (!shift && cmd && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
struct fps_counter *fps_counter =
|
struct fps_counter *fps_counter =
|
||||||
im->video_buffer->fps_counter;
|
im->video_buffer->fps_counter;
|
||||||
switch_fps_counter_state(fps_counter);
|
switch_fps_counter_state(fps_counter);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
if (control && cmd && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_notification_panel(controller);
|
collapse_notification_panel(controller);
|
||||||
} else {
|
} else {
|
||||||
@ -394,7 +449,7 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && cmd && !shift && !repeat && down) {
|
if (control && !shift && !repeat && down) {
|
||||||
rotate_device(controller);
|
rotate_device(controller);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -407,8 +462,23 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->repeat) {
|
||||||
|
if (!im->forward_key_repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++im->repeat;
|
||||||
|
} else {
|
||||||
|
im->repeat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
|
||||||
|
// Synchronize the computer clipboard to the device clipboard before
|
||||||
|
// sending Ctrl+v, to allow seamless copy-paste.
|
||||||
|
set_device_clipboard(controller, false);
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
@ -423,7 +493,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
|||||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, from->x, from->y);
|
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||||
to->inject_touch_event.pressure = 1.f;
|
to->inject_touch_event.pressure = 1.f;
|
||||||
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
|
||||||
|
|
||||||
@ -461,15 +531,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
|||||||
to->inject_touch_event.pointer_id = from->fingerId;
|
to->inject_touch_event.pointer_id = from->fingerId;
|
||||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
|
|
||||||
int ww;
|
int dw;
|
||||||
int wh;
|
int dh;
|
||||||
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||||
int32_t x = from->x * ww;
|
int32_t x = from->x * dw;
|
||||||
int32_t y = from->y * wh;
|
int32_t y = from->y * dh;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, x, y);
|
screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||||
|
|
||||||
to->inject_touch_event.pressure = from->pressure;
|
to->inject_touch_event.pressure = from->pressure;
|
||||||
to->inject_touch_event.buttons = 0;
|
to->inject_touch_event.buttons = 0;
|
||||||
@ -499,8 +569,9 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
|||||||
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
|
||||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, from->x, from->y);
|
screen_convert_window_to_frame_coords(screen, from->x, from->y);
|
||||||
to->inject_touch_event.pressure = 1.f;
|
to->inject_touch_event.pressure =
|
||||||
|
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
|
||||||
to->inject_touch_event.buttons =
|
to->inject_touch_event.buttons =
|
||||||
convert_mouse_buttons(SDL_BUTTON(from->button));
|
convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||||
|
|
||||||
@ -509,8 +580,9 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
|||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_button(struct input_manager *im,
|
input_manager_process_mouse_button(struct input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event,
|
const SDL_MouseButtonEvent *event) {
|
||||||
bool control) {
|
bool control = im->control;
|
||||||
|
|
||||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
// simulated from touch events, so it's a duplicate
|
// simulated from touch events, so it's a duplicate
|
||||||
return;
|
return;
|
||||||
@ -564,7 +636,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
|||||||
|
|
||||||
struct position position = {
|
struct position position = {
|
||||||
.screen_size = screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
|
.point = screen_convert_window_to_frame_coords(screen,
|
||||||
|
mouse_x, mouse_y),
|
||||||
};
|
};
|
||||||
|
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
@ -3,28 +3,46 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "video_buffer.h"
|
#include "scrcpy.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
#include "video_buffer.h"
|
||||||
|
|
||||||
struct input_manager {
|
struct input_manager {
|
||||||
struct controller *controller;
|
struct controller *controller;
|
||||||
struct video_buffer *video_buffer;
|
struct video_buffer *video_buffer;
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
|
|
||||||
|
// SDL reports repeated events as a boolean, but Android expects the actual
|
||||||
|
// number of repetitions. This variable keeps track of the count.
|
||||||
|
unsigned repeat;
|
||||||
|
|
||||||
|
bool control;
|
||||||
|
bool forward_key_repeat;
|
||||||
bool prefer_text;
|
bool prefer_text;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
|
unsigned count;
|
||||||
|
} sdl_shortcut_mods;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_init(struct input_manager *im,
|
||||||
|
const struct scrcpy_options *options);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_text_input(struct input_manager *im,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event);
|
const SDL_TextInputEvent *event);
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_key(struct input_manager *im,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event);
|
||||||
bool control);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_motion(struct input_manager *im,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
@ -36,8 +54,7 @@ input_manager_process_touch(struct input_manager *im,
|
|||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_button(struct input_manager *im,
|
input_manager_process_mouse_button(struct input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event,
|
const SDL_MouseButtonEvent *event);
|
||||||
bool control);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
@ -42,7 +43,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
|
|||||||
return SDL_LOG_PRIORITY_ERROR;
|
return SDL_LOG_PRIORITY_ERROR;
|
||||||
default:
|
default:
|
||||||
assert(!"unexpected log level");
|
assert(!"unexpected log level");
|
||||||
return SC_LOG_LEVEL_INFO;
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
||||||
SDL_LogSetAllPriority(sdl_log);
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
scrcpy_print_usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
|
@ -25,12 +25,21 @@ receiver_destroy(struct receiver *receiver) {
|
|||||||
static void
|
static void
|
||||||
process_msg(struct device_msg *msg) {
|
process_msg(struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD:
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
|
char *current = SDL_GetClipboardText();
|
||||||
|
bool same = current && !strcmp(current, msg->clipboard.text);
|
||||||
|
SDL_free(current);
|
||||||
|
if (same) {
|
||||||
|
LOGD("Computer clipboard unchanged");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LOGI("Device clipboard copied");
|
LOGI("Device clipboard copied");
|
||||||
SDL_SetClipboardText(msg->clipboard.text);
|
SDL_SetClipboardText(msg->clipboard.text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
process_msgs(const unsigned char *buf, size_t len) {
|
process_msgs(const unsigned char *buf, size_t len) {
|
||||||
@ -60,28 +69,29 @@ static int
|
|||||||
run_receiver(void *data) {
|
run_receiver(void *data) {
|
||||||
struct receiver *receiver = data;
|
struct receiver *receiver = data;
|
||||||
|
|
||||||
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
|
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||||
ssize_t r = net_recv(receiver->control_socket, buf,
|
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||||
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
DEVICE_MSG_MAX_SIZE - head);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
LOGD("Receiver stopped");
|
LOGD("Receiver stopped");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t consumed = process_msgs(buf, r);
|
head += r;
|
||||||
|
ssize_t consumed = process_msgs(buf, head);
|
||||||
if (consumed == -1) {
|
if (consumed == -1) {
|
||||||
// an error occurred
|
// an error occurred
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumed) {
|
if (consumed) {
|
||||||
|
head -= consumed;
|
||||||
// shift the remaining data in the buffer
|
// shift the remaining data in the buffer
|
||||||
memmove(buf, &buf[consumed], r - consumed);
|
memmove(buf, &buf[consumed], head);
|
||||||
head = r - consumed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ recorder_queue_clear(struct recorder_queue *queue) {
|
|||||||
bool
|
bool
|
||||||
recorder_init(struct recorder *recorder,
|
recorder_init(struct recorder *recorder,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
enum recorder_format format,
|
enum sc_record_format format,
|
||||||
struct size declared_frame_size) {
|
struct size declared_frame_size) {
|
||||||
recorder->filename = SDL_strdup(filename);
|
recorder->filename = SDL_strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
@ -105,10 +105,10 @@ recorder_destroy(struct recorder *recorder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
recorder_get_format_name(enum recorder_format format) {
|
recorder_get_format_name(enum sc_record_format format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case RECORDER_FORMAT_MP4: return "mp4";
|
case SC_RECORD_FORMAT_MP4: return "mp4";
|
||||||
case RECORDER_FORMAT_MKV: return "matroska";
|
case SC_RECORD_FORMAT_MKV: return "matroska";
|
||||||
default: return NULL;
|
default: return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,9 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
|
|
||||||
enum recorder_format {
|
|
||||||
RECORDER_FORMAT_AUTO,
|
|
||||||
RECORDER_FORMAT_MP4,
|
|
||||||
RECORDER_FORMAT_MKV,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct record_packet {
|
struct record_packet {
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
struct record_packet *next;
|
struct record_packet *next;
|
||||||
@ -25,7 +20,7 @@ struct recorder_queue QUEUE(struct record_packet);
|
|||||||
|
|
||||||
struct recorder {
|
struct recorder {
|
||||||
char *filename;
|
char *filename;
|
||||||
enum recorder_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
@ -46,7 +41,7 @@ struct recorder {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
recorder_init(struct recorder *recorder, const char *filename,
|
recorder_init(struct recorder *recorder, const char *filename,
|
||||||
enum recorder_format format, struct size declared_frame_size);
|
enum sc_record_format format, struct size declared_frame_size);
|
||||||
|
|
||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
// not needed here, but winsock2.h must never be included AFTER windows.h
|
||||||
|
# include <winsock2.h>
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -46,7 +48,14 @@ static struct input_manager input_manager = {
|
|||||||
.controller = &controller,
|
.controller = &controller,
|
||||||
.video_buffer = &video_buffer,
|
.video_buffer = &video_buffer,
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
.prefer_text = false, // initialized later
|
.repeat = 0,
|
||||||
|
|
||||||
|
// initialized later
|
||||||
|
.prefer_text = false,
|
||||||
|
.sdl_shortcut_mods = {
|
||||||
|
.data = {0},
|
||||||
|
.count = 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -63,7 +72,8 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
|||||||
|
|
||||||
// init SDL and set appropriate hints
|
// init SDL and set appropriate hints
|
||||||
static bool
|
static bool
|
||||||
sdl_init_and_configure(bool display, const char *render_driver) {
|
sdl_init_and_configure(bool display, const char *render_driver,
|
||||||
|
bool disable_screensaver) {
|
||||||
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
||||||
if (SDL_Init(flags)) {
|
if (SDL_Init(flags)) {
|
||||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
@ -112,8 +122,13 @@ sdl_init_and_configure(bool display, const char *render_driver) {
|
|||||||
LOGW("Could not disable minimize on focus loss");
|
LOGW("Could not disable minimize on focus loss");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not disable the screensaver when scrcpy is running
|
if (disable_screensaver) {
|
||||||
|
LOGD("Screensaver disabled");
|
||||||
|
SDL_DisableScreenSaver();
|
||||||
|
} else {
|
||||||
|
LOGD("Screensaver enabled");
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -155,7 +170,7 @@ enum event_result {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static enum event_result
|
static enum event_result
|
||||||
handle_event(SDL_Event *event, bool control) {
|
handle_event(SDL_Event *event, const struct scrcpy_options *options) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_STREAM_STOPPED:
|
case EVENT_STREAM_STOPPED:
|
||||||
LOGD("Video stream stopped");
|
LOGD("Video stream stopped");
|
||||||
@ -177,7 +192,7 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
screen_handle_window_event(&screen, &event->window);
|
screen_handle_window_event(&screen, &event->window);
|
||||||
break;
|
break;
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT:
|
||||||
if (!control) {
|
if (!options->control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input_manager_process_text_input(&input_manager, &event->text);
|
input_manager_process_text_input(&input_manager, &event->text);
|
||||||
@ -186,16 +201,16 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
// some key events do not interact with the device, so process the
|
// some key events do not interact with the device, so process the
|
||||||
// event even if control is disabled
|
// event even if control is disabled
|
||||||
input_manager_process_key(&input_manager, &event->key, control);
|
input_manager_process_key(&input_manager, &event->key);
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEMOTION:
|
case SDL_MOUSEMOTION:
|
||||||
if (!control) {
|
if (!options->control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input_manager_process_mouse_motion(&input_manager, &event->motion);
|
input_manager_process_mouse_motion(&input_manager, &event->motion);
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
if (!control) {
|
if (!options->control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
|
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
|
||||||
@ -204,8 +219,7 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
// some mouse events do not interact with the device, so process
|
// some mouse events do not interact with the device, so process
|
||||||
// the event even if control is disabled
|
// the event even if control is disabled
|
||||||
input_manager_process_mouse_button(&input_manager, &event->button,
|
input_manager_process_mouse_button(&input_manager, &event->button);
|
||||||
control);
|
|
||||||
break;
|
break;
|
||||||
case SDL_FINGERMOTION:
|
case SDL_FINGERMOTION:
|
||||||
case SDL_FINGERDOWN:
|
case SDL_FINGERDOWN:
|
||||||
@ -213,7 +227,7 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
input_manager_process_touch(&input_manager, &event->tfinger);
|
input_manager_process_touch(&input_manager, &event->tfinger);
|
||||||
break;
|
break;
|
||||||
case SDL_DROPFILE: {
|
case SDL_DROPFILE: {
|
||||||
if (!control) {
|
if (!options->control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
file_handler_action_t action;
|
file_handler_action_t action;
|
||||||
@ -230,16 +244,15 @@ handle_event(SDL_Event *event, bool control) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
event_loop(bool display, bool control) {
|
event_loop(const struct scrcpy_options *options) {
|
||||||
(void) display;
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
if (display) {
|
if (options->display) {
|
||||||
SDL_AddEventWatch(event_watcher, NULL);
|
SDL_AddEventWatch(event_watcher, NULL);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
enum event_result result = handle_event(&event, control);
|
enum event_result result = handle_event(&event, options);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case EVENT_RESULT_STOPPED_BY_USER:
|
case EVENT_RESULT_STOPPED_BY_USER:
|
||||||
return true;
|
return true;
|
||||||
@ -321,7 +334,8 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
|
|
||||||
if (!sdl_init_and_configure(options->display, options->render_driver)) {
|
if (!sdl_init_and_configure(options->display, options->render_driver,
|
||||||
|
options->disable_screensaver)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,9 +441,9 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input_manager.prefer_text = options->prefer_text;
|
input_manager_init(&input_manager, options);
|
||||||
|
|
||||||
ret = event_loop(options->display, options->control);
|
ret = event_loop(options);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
screen_destroy(&screen);
|
screen_destroy(&screen);
|
||||||
|
@ -2,13 +2,46 @@
|
|||||||
#define SCRCPY_H
|
#define SCRCPY_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common.h"
|
|
||||||
#include "input_manager.h"
|
enum sc_log_level {
|
||||||
#include "recorder.h"
|
SC_LOG_LEVEL_DEBUG,
|
||||||
#include "util/log.h"
|
SC_LOG_LEVEL_INFO,
|
||||||
|
SC_LOG_LEVEL_WARN,
|
||||||
|
SC_LOG_LEVEL_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_record_format {
|
||||||
|
SC_RECORD_FORMAT_AUTO,
|
||||||
|
SC_RECORD_FORMAT_MP4,
|
||||||
|
SC_RECORD_FORMAT_MKV,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
|
enum sc_shortcut_mod {
|
||||||
|
SC_MOD_LCTRL = 1 << 0,
|
||||||
|
SC_MOD_RCTRL = 1 << 1,
|
||||||
|
SC_MOD_LALT = 1 << 2,
|
||||||
|
SC_MOD_RALT = 1 << 3,
|
||||||
|
SC_MOD_LSUPER = 1 << 4,
|
||||||
|
SC_MOD_RSUPER = 1 << 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_shortcut_mods {
|
||||||
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
|
unsigned count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_port_range {
|
||||||
|
uint16_t first;
|
||||||
|
uint16_t last;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
||||||
|
|
||||||
struct scrcpy_options {
|
struct scrcpy_options {
|
||||||
const char *serial;
|
const char *serial;
|
||||||
@ -19,15 +52,16 @@ struct scrcpy_options {
|
|||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum recorder_format record_format;
|
enum sc_record_format record_format;
|
||||||
struct port_range port_range;
|
struct sc_port_range port_range;
|
||||||
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
uint16_t window_width;
|
uint16_t window_width;
|
||||||
uint16_t window_height;
|
uint16_t window_height;
|
||||||
uint16_t display_id;
|
uint16_t display_id;
|
||||||
@ -43,6 +77,8 @@ struct scrcpy_options {
|
|||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
|
bool disable_screensaver;
|
||||||
|
bool forward_key_repeat;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
@ -54,18 +90,22 @@ struct scrcpy_options {
|
|||||||
.render_driver = NULL, \
|
.render_driver = NULL, \
|
||||||
.codec_options = NULL, \
|
.codec_options = NULL, \
|
||||||
.log_level = SC_LOG_LEVEL_INFO, \
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
.record_format = RECORDER_FORMAT_AUTO, \
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
.port_range = { \
|
.port_range = { \
|
||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
||||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
|
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
|
||||||
}, \
|
}, \
|
||||||
|
.shortcut_mods = { \
|
||||||
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
|
.count = 2, \
|
||||||
|
}, \
|
||||||
.max_size = DEFAULT_MAX_SIZE, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
|
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.window_x = WINDOW_POSITION_UNDEFINED, \
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_y = WINDOW_POSITION_UNDEFINED, \
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_width = 0, \
|
.window_width = 0, \
|
||||||
.window_height = 0, \
|
.window_height = 0, \
|
||||||
.display_id = 0, \
|
.display_id = 0, \
|
||||||
@ -81,6 +121,8 @@ struct scrcpy_options {
|
|||||||
.mipmaps = true, \
|
.mipmaps = true, \
|
||||||
.stay_awake = false, \
|
.stay_awake = false, \
|
||||||
.force_adb_forward = false, \
|
.force_adb_forward = false, \
|
||||||
|
.disable_screensaver = false, \
|
||||||
|
.forward_key_repeat = true, \
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "icon.xpm"
|
#include "icon.xpm"
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/lock.h"
|
#include "util/lock.h"
|
||||||
@ -257,9 +258,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = window_x != WINDOW_POSITION_UNDEFINED
|
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
int y = window_y != WINDOW_POSITION_UNDEFINED
|
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
screen->window = SDL_CreateWindow(window_title, x, y,
|
screen->window = SDL_CreateWindow(window_title, x, y,
|
||||||
window_size.width, window_size.height,
|
window_size.width, window_size.height,
|
||||||
@ -579,14 +580,14 @@ screen_handle_window_event(struct screen *screen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct point
|
struct point
|
||||||
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
|
int32_t x, int32_t y) {
|
||||||
unsigned rotation = screen->rotation;
|
unsigned rotation = screen->rotation;
|
||||||
assert(rotation < 4);
|
assert(rotation < 4);
|
||||||
|
|
||||||
int32_t w = screen->content_size.width;
|
int32_t w = screen->content_size.width;
|
||||||
int32_t h = screen->content_size.height;
|
int32_t h = screen->content_size.height;
|
||||||
|
|
||||||
screen_hidpi_scale_coords(screen, &x, &y);
|
|
||||||
|
|
||||||
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
|
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
|
||||||
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||||
@ -615,6 +616,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct point
|
||||||
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
|
int32_t x, int32_t y) {
|
||||||
|
screen_hidpi_scale_coords(screen, &x, &y);
|
||||||
|
return screen_convert_drawable_to_frame_coords(screen, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
||||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "opengl.h"
|
#include "opengl.h"
|
||||||
|
|
||||||
#define WINDOW_POSITION_UNDEFINED (-0x8000)
|
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|
||||||
struct screen {
|
struct screen {
|
||||||
@ -76,7 +74,7 @@ void
|
|||||||
screen_init(struct screen *screen);
|
screen_init(struct screen *screen);
|
||||||
|
|
||||||
// initialize screen, create window, renderer and texture (window is hidden)
|
// initialize screen, create window, renderer and texture (window is hidden)
|
||||||
// window_x and window_y accept WINDOW_POSITION_UNDEFINED
|
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
|
||||||
bool
|
bool
|
||||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||||
struct size frame_size, bool always_on_top,
|
struct size frame_size, bool always_on_top,
|
||||||
@ -126,7 +124,14 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
|||||||
// convert point from window coordinates to frame coordinates
|
// convert point from window coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct point
|
struct point
|
||||||
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
|
// convert point from drawable coordinates to frame coordinates
|
||||||
|
// x and y are expressed in pixels
|
||||||
|
struct point
|
||||||
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
// Convert coordinates from window to drawable.
|
// Convert coordinates from window to drawable.
|
||||||
// Events are expressed in window coordinates, but content is expressed in
|
// Events are expressed in window coordinates, but content is expressed in
|
||||||
|
@ -143,7 +143,7 @@ listen_on_port(uint16_t port) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel_reverse_any_port(struct server *server,
|
enable_tunnel_reverse_any_port(struct server *server,
|
||||||
struct port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
if (!enable_tunnel_reverse(server->serial, port)) {
|
||||||
@ -189,7 +189,7 @@ enable_tunnel_reverse_any_port(struct server *server,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel_forward_any_port(struct server *server,
|
enable_tunnel_forward_any_port(struct server *server,
|
||||||
struct port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
server->tunnel_forward = true;
|
server->tunnel_forward = true;
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -217,7 +217,7 @@ enable_tunnel_forward_any_port(struct server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel_any_port(struct server *server, struct port_range port_range,
|
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
|
||||||
bool force_adb_forward) {
|
bool force_adb_forward) {
|
||||||
if (!force_adb_forward) {
|
if (!force_adb_forward) {
|
||||||
// Attempt to use "adb reverse"
|
// Attempt to use "adb reverse"
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ struct server {
|
|||||||
socket_t server_socket; // only used if !tunnel_forward
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
socket_t video_socket;
|
socket_t video_socket;
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
struct port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint16_t local_port; // selected from port_range
|
uint16_t local_port; // selected from port_range
|
||||||
bool tunnel_enabled;
|
bool tunnel_enabled;
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
@ -47,7 +48,7 @@ struct server_params {
|
|||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
struct port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
|
@ -3,13 +3,6 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
enum sc_log_level {
|
|
||||||
SC_LOG_LEVEL_DEBUG,
|
|
||||||
SC_LOG_LEVEL_INFO,
|
|
||||||
SC_LOG_LEVEL_WARN,
|
|
||||||
SC_LOG_LEVEL_ERROR,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
|
@ -65,7 +65,10 @@ static void test_buffer_read64be(void) {
|
|||||||
assert(val == 0xABCD1234567890EF);
|
assert(val == 0xABCD1234567890EF);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_buffer_write16be();
|
test_buffer_write16be();
|
||||||
test_buffer_write32be();
|
test_buffer_write32be();
|
||||||
test_buffer_write64be();
|
test_buffer_write64be();
|
||||||
|
@ -65,7 +65,10 @@ static void test_cbuf_push_take(void) {
|
|||||||
assert(item == 35);
|
assert(item == 35);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_cbuf_empty();
|
test_cbuf_empty();
|
||||||
test_cbuf_full();
|
test_cbuf_full();
|
||||||
test_cbuf_push_take();
|
test_cbuf_push_take();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
static void test_flag_version(void) {
|
static void test_flag_version(void) {
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
@ -73,7 +75,6 @@ static void test_options(void) {
|
|||||||
|
|
||||||
const struct scrcpy_options *opts = &args.opts;
|
const struct scrcpy_options *opts = &args.opts;
|
||||||
assert(opts->always_on_top);
|
assert(opts->always_on_top);
|
||||||
fprintf(stderr, "%d\n", (int) opts->bit_rate);
|
|
||||||
assert(opts->bit_rate == 5000000);
|
assert(opts->bit_rate == 5000000);
|
||||||
assert(!strcmp(opts->crop, "100:200:300:400"));
|
assert(!strcmp(opts->crop, "100:200:300:400"));
|
||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
@ -84,7 +85,7 @@ static void test_options(void) {
|
|||||||
assert(opts->port_range.last == 1236);
|
assert(opts->port_range.last == 1236);
|
||||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
assert(!strcmp(opts->record_filename, "file"));
|
assert(!strcmp(opts->record_filename, "file"));
|
||||||
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
|
||||||
assert(opts->render_expired_frames);
|
assert(opts->render_expired_frames);
|
||||||
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
||||||
assert(opts->show_touches);
|
assert(opts->show_touches);
|
||||||
@ -119,13 +120,54 @@ static void test_options2(void) {
|
|||||||
assert(!opts->control);
|
assert(!opts->control);
|
||||||
assert(!opts->display);
|
assert(!opts->display);
|
||||||
assert(!strcmp(opts->record_filename, "file.mp4"));
|
assert(!strcmp(opts->record_filename, "file.mp4"));
|
||||||
assert(opts->record_format == RECORDER_FORMAT_MP4);
|
assert(opts->record_format == SC_RECORD_FORMAT_MP4);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
static void test_parse_shortcut_mods(void) {
|
||||||
|
struct sc_shortcut_mods mods;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("lctrl", &mods);
|
||||||
|
assert(ok);
|
||||||
|
assert(mods.count == 1);
|
||||||
|
assert(mods.data[0] == SC_MOD_LCTRL);
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
|
||||||
|
assert(ok);
|
||||||
|
assert(mods.count == 1);
|
||||||
|
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT));
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
|
||||||
|
assert(ok);
|
||||||
|
assert(mods.count == 2);
|
||||||
|
assert(mods.data[0] == SC_MOD_RCTRL);
|
||||||
|
assert(mods.data[1] == SC_MOD_LALT);
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
|
||||||
|
assert(ok);
|
||||||
|
assert(mods.count == 3);
|
||||||
|
assert(mods.data[0] == SC_MOD_LSUPER);
|
||||||
|
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT));
|
||||||
|
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("", &mods);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("lctrl+", &mods);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
ok = sc_parse_shortcut_mods("lctrl,", &mods);
|
||||||
|
assert(!ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_flag_version();
|
test_flag_version();
|
||||||
test_flag_help();
|
test_flag_help();
|
||||||
test_options();
|
test_options();
|
||||||
test_options2();
|
test_options2();
|
||||||
|
test_parse_shortcut_mods();
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
@ -9,18 +9,20 @@ static void test_serialize_inject_keycode(void) {
|
|||||||
.inject_keycode = {
|
.inject_keycode = {
|
||||||
.action = AKEY_EVENT_ACTION_UP,
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
.keycode = AKEYCODE_ENTER,
|
.keycode = AKEYCODE_ENTER,
|
||||||
|
.repeat = 5,
|
||||||
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 10);
|
assert(size == 14);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
0x01, // AKEY_EVENT_ACTION_UP
|
0x01, // AKEY_EVENT_ACTION_UP
|
||||||
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
||||||
|
0x00, 0x00, 0x00, 0X05, // repeat
|
||||||
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -34,13 +36,13 @@ static void test_serialize_inject_text(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 16);
|
assert(size == 18);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_TEXT,
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
0x00, 0x0d, // text length
|
0x00, 0x00, 0x00, 0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -54,15 +56,17 @@ static void test_serialize_inject_text_long(void) {
|
|||||||
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
|
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
|
||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text;
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
||||||
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
expected[1] = 0x01;
|
expected[1] = 0x00;
|
||||||
expected[2] = 0x2c; // text length (16 bits)
|
expected[2] = 0x00;
|
||||||
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
expected[3] = 0x01;
|
||||||
|
expected[4] = 0x2c; // text length (32 bits)
|
||||||
|
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@ -88,7 +92,7 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 28);
|
assert(size == 28);
|
||||||
|
|
||||||
@ -123,7 +127,7 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 21);
|
assert(size == 21);
|
||||||
|
|
||||||
@ -142,7 +146,7 @@ static void test_serialize_back_or_screen_on(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -157,7 +161,7 @@ static void test_serialize_expand_notification_panel(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -172,7 +176,7 @@ static void test_serialize_collapse_notification_panel(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -187,7 +191,7 @@ static void test_serialize_get_clipboard(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -200,18 +204,20 @@ static void test_serialize_get_clipboard(void) {
|
|||||||
static void test_serialize_set_clipboard(void) {
|
static void test_serialize_set_clipboard(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
.inject_text = {
|
.set_clipboard = {
|
||||||
|
.paste = true,
|
||||||
.text = "hello, world!",
|
.text = "hello, world!",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 16);
|
assert(size == 19);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
0x00, 0x0d, // text length
|
1, // paste
|
||||||
|
0x00, 0x00, 0x00, 0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -225,7 +231,7 @@ static void test_serialize_set_screen_power_mode(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 2);
|
assert(size == 2);
|
||||||
|
|
||||||
@ -241,7 +247,7 @@ static void test_serialize_rotate_device(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@ -251,7 +257,10 @@ static void test_serialize_rotate_device(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_serialize_inject_keycode();
|
test_serialize_inject_keycode();
|
||||||
test_serialize_inject_text();
|
test_serialize_inject_text();
|
||||||
test_serialize_inject_text_long();
|
test_serialize_inject_text_long();
|
||||||
|
@ -4,16 +4,17 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
static void test_deserialize_clipboard(void) {
|
static void test_deserialize_clipboard(void) {
|
||||||
const unsigned char input[] = {
|
const unsigned char input[] = {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
0x00, 0x03, // text length
|
0x00, 0x00, 0x00, 0x03, // text length
|
||||||
0x41, 0x42, 0x43, // "ABC"
|
0x41, 0x42, 0x43, // "ABC"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||||
assert(r == 6);
|
assert(r == 8);
|
||||||
|
|
||||||
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
assert(msg.clipboard.text);
|
assert(msg.clipboard.text);
|
||||||
@ -22,7 +23,33 @@ static void test_deserialize_clipboard(void) {
|
|||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
static void test_deserialize_clipboard_big(void) {
|
||||||
|
unsigned char input[DEVICE_MSG_MAX_SIZE];
|
||||||
|
input[0] = DEVICE_MSG_TYPE_CLIPBOARD;
|
||||||
|
input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24;
|
||||||
|
input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16;
|
||||||
|
input[3] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x0000ff00u) >> 8;
|
||||||
|
input[4] = DEVICE_MSG_TEXT_MAX_LENGTH & 0x000000ffu;
|
||||||
|
|
||||||
|
memset(input + 5, 'a', DEVICE_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
|
struct device_msg msg;
|
||||||
|
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
||||||
|
assert(r == DEVICE_MSG_MAX_SIZE);
|
||||||
|
|
||||||
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
|
assert(msg.clipboard.text);
|
||||||
|
assert(strlen(msg.clipboard.text) == DEVICE_MSG_TEXT_MAX_LENGTH);
|
||||||
|
assert(msg.clipboard.text[0] == 'a');
|
||||||
|
|
||||||
|
device_msg_destroy(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_deserialize_clipboard();
|
test_deserialize_clipboard();
|
||||||
|
test_deserialize_clipboard_big();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,10 @@ static void test_queue(void) {
|
|||||||
assert(queue_is_empty(&queue));
|
assert(queue_is_empty(&queue));
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_queue();
|
test_queue();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,10 @@ static void test_parse_integer_with_suffix(void) {
|
|||||||
assert(!ok);
|
assert(!ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
test_xstrncpy_simple();
|
test_xstrncpy_simple();
|
||||||
test_xstrncpy_just_fit();
|
test_xstrncpy_just_fit();
|
||||||
test_xstrncpy_truncated();
|
test_xstrncpy_truncated();
|
||||||
|
@ -15,6 +15,6 @@ cpu = 'i686'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win32-shared'
|
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win32-dev'
|
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32'
|
||||||
|
@ -15,6 +15,6 @@ cpu = 'x86_64'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.2.2-win64-shared'
|
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.2.2-win64-dev'
|
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.13',
|
version: '1.15',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
@ -10,24 +10,24 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
|
|||||||
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
|
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
|
||||||
|
|
||||||
prepare-ffmpeg-shared-win32:
|
prepare-ffmpeg-shared-win32:
|
||||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.2.2-win32-shared.zip \
|
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.3.1-win32-shared.zip \
|
||||||
ab5d603aaa54de360db2c2ffe378c82376b9343ea1175421dd644639aa07ee31 \
|
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
|
||||||
ffmpeg-4.2.2-win32-shared
|
ffmpeg-4.3.1-win32-shared
|
||||||
|
|
||||||
prepare-ffmpeg-dev-win32:
|
prepare-ffmpeg-dev-win32:
|
||||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.2.2-win32-dev.zip \
|
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.3.1-win32-dev.zip \
|
||||||
8d224be567a2950cad4be86f4aabdd045bfa52ad758e87c72cedd278613bc6c8 \
|
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
|
||||||
ffmpeg-4.2.2-win32-dev
|
ffmpeg-4.3.1-win32-dev
|
||||||
|
|
||||||
prepare-ffmpeg-shared-win64:
|
prepare-ffmpeg-shared-win64:
|
||||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.2.2-win64-shared.zip \
|
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.3.1-win64-shared.zip \
|
||||||
5aedf268952b7d9f6541dbfcb47cd86a7e7881a3b7ba482fd3bc4ca33bda7bf5 \
|
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
|
||||||
ffmpeg-4.2.2-win64-shared
|
ffmpeg-4.3.1-win64-shared
|
||||||
|
|
||||||
prepare-ffmpeg-dev-win64:
|
prepare-ffmpeg-dev-win64:
|
||||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.2.2-win64-dev.zip \
|
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.3.1-win64-dev.zip \
|
||||||
f4885f859c5b0d6663c2a0a4c1cf035b1c60b146402790b796bd3ad84f4f3ca2 \
|
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
|
||||||
ffmpeg-4.2.2-win64-dev
|
ffmpeg-4.3.1-win64-dev
|
||||||
|
|
||||||
prepare-sdl2:
|
prepare-sdl2:
|
||||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \
|
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \
|
||||||
@ -35,6 +35,6 @@ prepare-sdl2:
|
|||||||
SDL2-2.0.12
|
SDL2-2.0.12
|
||||||
|
|
||||||
prepare-adb:
|
prepare-adb:
|
||||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
|
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.4-windows.zip \
|
||||||
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
|
413182fff6c5957911e231b9e97e6be4fc6a539035e3dfb580b5c54bd5950fee \
|
||||||
platform-tools
|
platform-tools
|
||||||
|
@ -6,8 +6,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 15
|
versionCode 17
|
||||||
versionName "1.13"
|
versionName "1.15"
|
||||||
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.13
|
SCRCPY_VERSION_NAME=1.15
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||||
@ -42,6 +42,8 @@ echo "Generating java from aidl..."
|
|||||||
cd "$SERVER_DIR/src/main/aidl"
|
cd "$SERVER_DIR/src/main/aidl"
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
||||||
android/view/IRotationWatcher.aidl
|
android/view/IRotationWatcher.aidl
|
||||||
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
||||||
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
cd ../java
|
cd ../java
|
||||||
@ -55,6 +57,7 @@ cd "$CLASSES_DIR"
|
|||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
||||||
--output "$BUILD_DIR/classes.dex" \
|
--output "$BUILD_DIR/classes.dex" \
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
|
android/content/*.class \
|
||||||
com/genymobile/scrcpy/*.class \
|
com/genymobile/scrcpy/*.class \
|
||||||
com/genymobile/scrcpy/wrappers/*.class
|
com/genymobile/scrcpy/wrappers/*.class
|
||||||
|
|
||||||
|
@ -19,18 +19,19 @@ public final class CleanUp {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException {
|
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
||||||
boolean needProcess = disableShowTouches || restoreStayOn != -1;
|
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
|
||||||
if (needProcess) {
|
if (needProcess) {
|
||||||
startProcess(disableShowTouches, restoreStayOn);
|
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
|
||||||
} else {
|
} else {
|
||||||
// There is no additional clean up to do when scrcpy dies
|
// There is no additional clean up to do when scrcpy dies
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException {
|
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
||||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)};
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
|
||||||
|
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
builder.environment().put("CLASSPATH", SERVER_PATH);
|
||||||
@ -59,6 +60,7 @@ public final class CleanUp {
|
|||||||
|
|
||||||
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
|
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
|
||||||
int restoreStayOn = Integer.parseInt(args[1]);
|
int restoreStayOn = Integer.parseInt(args[1]);
|
||||||
|
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
|
||||||
|
|
||||||
if (disableShowTouches || restoreStayOn != -1) {
|
if (disableShowTouches || restoreStayOn != -1) {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
ServiceManager serviceManager = new ServiceManager();
|
||||||
@ -73,5 +75,10 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (restoreNormalPowerMode) {
|
||||||
|
Ln.i("Restoring normal power mode");
|
||||||
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,15 +28,18 @@ public final class ControlMessage {
|
|||||||
private Position position;
|
private Position position;
|
||||||
private int hScroll;
|
private int hScroll;
|
||||||
private int vScroll;
|
private int vScroll;
|
||||||
|
private boolean paste;
|
||||||
|
private int repeat;
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
|
public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_KEYCODE;
|
msg.type = TYPE_INJECT_KEYCODE;
|
||||||
msg.action = action;
|
msg.action = action;
|
||||||
msg.keycode = keycode;
|
msg.keycode = keycode;
|
||||||
|
msg.repeat = repeat;
|
||||||
msg.metaState = metaState;
|
msg.metaState = metaState;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
@ -68,10 +71,11 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(String text) {
|
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
msg.text = text;
|
msg.text = text;
|
||||||
|
msg.paste = paste;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,4 +138,12 @@ public final class ControlMessage {
|
|||||||
public int getVScroll() {
|
public int getVScroll() {
|
||||||
return vScroll;
|
return vScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getPaste() {
|
||||||
|
return paste;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRepeat() {
|
||||||
|
return repeat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,19 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
public class ControlMessageReader {
|
public class ControlMessageReader {
|
||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
|
||||||
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||||
|
|
||||||
private static final int RAW_BUFFER_SIZE = 4096;
|
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
|
||||||
|
|
||||||
public ControlMessageReader() {
|
public ControlMessageReader() {
|
||||||
// invariant: the buffer is always in "get" mode
|
// invariant: the buffer is always in "get" mode
|
||||||
@ -98,20 +98,23 @@ public class ControlMessageReader {
|
|||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = toUnsigned(buffer.get());
|
||||||
int keycode = buffer.getInt();
|
int keycode = buffer.getInt();
|
||||||
|
int repeat = buffer.getInt();
|
||||||
int metaState = buffer.getInt();
|
int metaState = buffer.getInt();
|
||||||
return ControlMessage.createInjectKeycode(action, keycode, metaState);
|
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() {
|
private String parseString() {
|
||||||
if (buffer.remaining() < 2) {
|
if (buffer.remaining() < 4) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = toUnsigned(buffer.getShort());
|
int len = buffer.getInt();
|
||||||
if (buffer.remaining() < len) {
|
if (buffer.remaining() < len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
buffer.get(textBuffer, 0, len);
|
int position = buffer.position();
|
||||||
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
// Move the buffer position to consume the text
|
||||||
|
buffer.position(position + len);
|
||||||
|
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectText() {
|
private ControlMessage parseInjectText() {
|
||||||
@ -148,11 +151,15 @@ public class ControlMessageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() {
|
||||||
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean paste = buffer.get() != 0;
|
||||||
String text = parseString();
|
String text = parseString();
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ControlMessage.createSetClipboard(text);
|
return ControlMessage.createSetClipboard(text, paste);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
private ControlMessage parseSetScreenPowerMode() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
@ -7,11 +8,16 @@ import android.view.KeyEvent;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class Controller {
|
public class Controller {
|
||||||
|
|
||||||
private static final int DEVICE_ID_VIRTUAL = -1;
|
private static final int DEVICE_ID_VIRTUAL = -1;
|
||||||
|
|
||||||
|
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final DesktopConnection connection;
|
private final DesktopConnection connection;
|
||||||
private final DeviceMessageSender sender;
|
private final DeviceMessageSender sender;
|
||||||
@ -23,6 +29,8 @@ public class Controller {
|
|||||||
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||||
|
|
||||||
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
public Controller(Device device, DesktopConnection connection) {
|
public Controller(Device device, DesktopConnection connection) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
@ -47,10 +55,10 @@ public class Controller {
|
|||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (!device.isScreenOn()) {
|
if (!device.isScreenOn()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
device.injectKeycode(KeyEvent.KEYCODE_WAKEUP);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
// After POWER is injected, the device is powered on asynchronously.
|
// After the keycode is injected, the device is powered on asynchronously.
|
||||||
// To turn the device screen off while mirroring, the client will send a message that
|
// To turn the device screen off while mirroring, the client will send a message that
|
||||||
// would be handled before the device is actually powered on, so its effect would
|
// would be handled before the device is actually powered on, so its effect would
|
||||||
// be "canceled" once the device is turned back on.
|
// be "canceled" once the device is turned back on.
|
||||||
@ -73,7 +81,7 @@ public class Controller {
|
|||||||
switch (msg.getType()) {
|
switch (msg.getType()) {
|
||||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
|
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
@ -104,19 +112,19 @@ public class Controller {
|
|||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
String clipboardText = device.getClipboardText();
|
String clipboardText = device.getClipboardText();
|
||||||
|
if (clipboardText != null) {
|
||||||
sender.pushClipboardText(clipboardText);
|
sender.pushClipboardText(clipboardText);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
boolean setClipboardOk = device.setClipboardText(msg.getText());
|
setClipboard(msg.getText(), msg.getPaste());
|
||||||
if (setClipboardOk) {
|
|
||||||
Ln.i("Device clipboard set");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
int mode = msg.getAction();
|
int mode = msg.getAction();
|
||||||
boolean setPowerModeOk = device.setScreenPowerMode(mode);
|
boolean setPowerModeOk = Device.setScreenPowerMode(mode);
|
||||||
if (setPowerModeOk) {
|
if (setPowerModeOk) {
|
||||||
|
keepPowerModeOff = mode == Device.POWER_MODE_OFF;
|
||||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,8 +137,11 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
return device.injectKeyEvent(action, keycode, 0, metaState);
|
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
|
||||||
|
schedulePowerModeOff();
|
||||||
|
}
|
||||||
|
return device.injectKeyEvent(action, keycode, repeat, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectChar(char c) {
|
||||||
@ -165,7 +176,7 @@ public class Controller {
|
|||||||
|
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
// ignore event
|
Ln.w("Ignore touch event, it was generated for a different device size");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,8 +234,38 @@ public class Controller {
|
|||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a call to set power mode to off after a small delay.
|
||||||
|
*/
|
||||||
|
private static void schedulePowerModeOff() {
|
||||||
|
EXECUTOR.schedule(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Ln.i("Forcing screen off");
|
||||||
|
Device.setScreenPowerMode(Device.POWER_MODE_OFF);
|
||||||
|
}
|
||||||
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn() {
|
private boolean pressBackOrTurnScreenOn() {
|
||||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP;
|
||||||
|
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_WAKEUP) {
|
||||||
|
schedulePowerModeOff();
|
||||||
|
}
|
||||||
return device.injectKeycode(keycode);
|
return device.injectKeycode(keycode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setClipboard(String text, boolean paste) {
|
||||||
|
boolean ok = device.setClipboardText(text);
|
||||||
|
if (ok) {
|
||||||
|
Ln.i("Device clipboard set");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Android >= 7, also press the PASTE key if requested
|
||||||
|
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||||
|
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||||
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
@ -80,7 +81,9 @@ public final class Device {
|
|||||||
|
|
||||||
if (options.getControl()) {
|
if (options.getControl()) {
|
||||||
// If control is enabled, synchronize Android clipboard to the computer automatically
|
// If control is enabled, synchronize Android clipboard to the computer automatically
|
||||||
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager != null) {
|
||||||
|
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void dispatchPrimaryClipChanged() {
|
public void dispatchPrimaryClipChanged() {
|
||||||
if (isSettingClipboard.get()) {
|
if (isSettingClipboard.get()) {
|
||||||
@ -97,6 +100,9 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Ln.w("No clipboard manager, copy-paste between device and computer will not work");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
|
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
|
||||||
@ -199,7 +205,11 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getClipboardText() {
|
public String getClipboardText() {
|
||||||
CharSequence s = serviceManager.getClipboardManager().getText();
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CharSequence s = clipboardManager.getText();
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -207,16 +217,30 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean setClipboardText(String text) {
|
public boolean setClipboardText(String text) {
|
||||||
|
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
|
||||||
|
if (clipboardManager == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentClipboard = getClipboardText();
|
||||||
|
if (currentClipboard == null || currentClipboard.equals(text)) {
|
||||||
|
// The clipboard already contains the requested text.
|
||||||
|
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||||
|
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||||
|
// problem, do not explicitly set the clipboard text if it already contains the expected content.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
isSettingClipboard.set(true);
|
isSettingClipboard.set(true);
|
||||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
boolean ok = clipboardManager.setText(text);
|
||||||
isSettingClipboard.set(false);
|
isSettingClipboard.set(false);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
* @param mode one of the {@code POWER_MODE_*} constants
|
||||||
*/
|
*/
|
||||||
public boolean setScreenPowerMode(int mode) {
|
public static boolean setScreenPowerMode(int mode) {
|
||||||
IBinder d = SurfaceControl.getBuiltInDisplay();
|
IBinder d = SurfaceControl.getBuiltInDisplay();
|
||||||
if (d == null) {
|
if (d == null) {
|
||||||
Ln.e("Could not get built-in display");
|
Ln.e("Could not get built-in display");
|
||||||
|
@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
public class DeviceMessageWriter {
|
public class DeviceMessageWriter {
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
|
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
|
||||||
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
||||||
@ -21,7 +21,7 @@ public class DeviceMessageWriter {
|
|||||||
String text = msg.getText();
|
String text = msg.getText();
|
||||||
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
buffer.putShort((short) len);
|
buffer.putInt(len);
|
||||||
buffer.put(raw, 0, len);
|
buffer.put(raw, 0, len);
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
output.write(rawBuffer, 0, buffer.position());
|
||||||
break;
|
break;
|
||||||
|
@ -15,7 +15,7 @@ public final class Ln {
|
|||||||
DEBUG, INFO, WARN, ERROR
|
DEBUG, INFO, WARN, ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Level threshold;
|
private static Level threshold = Level.INFO;
|
||||||
|
|
||||||
private Ln() {
|
private Ln() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
|
@ -49,7 +49,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn);
|
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
|
||||||
|
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
|
||||||
|
@ -77,7 +77,14 @@ public final class ServiceManager {
|
|||||||
|
|
||||||
public ClipboardManager getClipboardManager() {
|
public ClipboardManager getClipboardManager() {
|
||||||
if (clipboardManager == null) {
|
if (clipboardManager == null) {
|
||||||
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
|
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
|
||||||
|
if (clipboard == null) {
|
||||||
|
// Some devices have no clipboard manager
|
||||||
|
// <https://github.com/Genymobile/scrcpy/issues/1440>
|
||||||
|
// <https://github.com/Genymobile/scrcpy/issues/1556>
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
clipboardManager = new ClipboardManager(clipboard);
|
||||||
}
|
}
|
||||||
return clipboardManager;
|
return clipboardManager;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(5); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(5, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ public class ControlMessageReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
|
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
|
||||||
Arrays.fill(text, (byte) 'a');
|
Arrays.fill(text, (byte) 'a');
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -216,8 +218,9 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
|
dos.writeByte(1); // paste
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeShort(text.length);
|
dos.writeInt(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -227,6 +230,7 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
|
Assert.assertTrue(event.getPaste());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -238,10 +242,11 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
|
|
||||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
||||||
|
dos.writeByte(1); // paste
|
||||||
Arrays.fill(rawText, (byte) 'a');
|
Arrays.fill(rawText, (byte) 'a');
|
||||||
String text = new String(rawText, 0, rawText.length);
|
String text = new String(rawText, 0, rawText.length);
|
||||||
|
|
||||||
dos.writeShort(rawText.length);
|
dos.writeInt(rawText.length);
|
||||||
dos.write(rawText);
|
dos.write(rawText);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -251,6 +256,7 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(text, event.getText());
|
Assert.assertEquals(text, event.getText());
|
||||||
|
Assert.assertTrue(event.getPaste());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -300,11 +306,13 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(0); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(1); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -314,12 +322,14 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(0, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
event = reader.next();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(1, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +343,7 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(4); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
@ -345,6 +356,7 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(4, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
event = reader.next();
|
||||||
@ -352,6 +364,7 @@ public class ControlMessageReaderTest {
|
|||||||
|
|
||||||
bos.reset();
|
bos.reset();
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(5); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
packet = bos.toByteArray();
|
packet = bos.toByteArray();
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
@ -361,6 +374,7 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(5, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public class DeviceMessageWriterTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
dos.writeByte(DeviceMessage.TYPE_CLIPBOARD);
|
||||||
dos.writeShort(data.length);
|
dos.writeInt(data.length);
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
Reference in New Issue
Block a user