Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01e9c2812 | ||
|
|
80f5a7c43d | ||
|
|
ddb36e3436 | ||
|
|
cac1765091 | ||
|
|
f5a14b285b | ||
|
|
65edae0ca6 | ||
|
|
e4a0fada10 | ||
|
|
8a037e3d9b | ||
|
|
b3aa88c751 | ||
|
|
b9602e56d9 | ||
|
|
0c01ac34b4 | ||
|
|
82295ef4a7 | ||
|
|
e62aca59fe | ||
|
|
fbd2d0bf3e | ||
|
|
6e1069a822 | ||
|
|
c4323df976 | ||
|
|
8ff07e0c88 | ||
|
|
e4efd75766 | ||
|
|
0e4a6f462b | ||
|
|
8b73c90427 | ||
|
|
ef91ab2841 | ||
|
|
44fa4a090e | ||
|
|
dcde578a50 | ||
|
|
93a5c5149d | ||
|
|
2ca8318b9d | ||
|
|
d499ee53c9 | ||
|
|
8f619f337b | ||
|
|
fc1dec0270 | ||
|
|
274b591d18 | ||
|
|
4bbabfb4ef | ||
|
|
ffc57512b3 | ||
|
|
c7a33fac36 | ||
|
|
81573d81a0 | ||
|
|
5c2cf88a1d | ||
|
|
8f46e18426 | ||
|
|
ee3882f8be |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,8 @@
|
||||
build/
|
||||
/dist/
|
||||
/build-*/
|
||||
/build_*/
|
||||
/release-*/
|
||||
.idea/
|
||||
.gradle/
|
||||
/x/
|
||||
|
||||
6
BUILD.md
6
BUILD.md
@@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
|
||||
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
|
||||
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
|
||||
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
|
||||
|
||||
[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.14/scrcpy-server-v1.14
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
110
README.md
110
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.13)
|
||||
# scrcpy (v1.14)
|
||||
|
||||
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.
|
||||
@@ -69,10 +69,10 @@ hard).
|
||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
||||
(including `adb`) is available:
|
||||
|
||||
- [`scrcpy-win64-v1.13.zip`][direct-win64]
|
||||
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
|
||||
- [`scrcpy-win64-v1.14.zip`][direct-win64]
|
||||
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
|
||||
|
||||
[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.14/scrcpy-win64-v1.14.zip
|
||||
|
||||
It is also available in [Chocolatey]:
|
||||
|
||||
@@ -289,6 +289,22 @@ From another terminal:
|
||||
scrcpy
|
||||
```
|
||||
|
||||
To avoid enabling remote port forwarding, you could force a forward connection
|
||||
instead (notice the `-L` instead of `-R`):
|
||||
|
||||
```bash
|
||||
adb kill-server # kill the local adb server on 5037
|
||||
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||
# keep this open
|
||||
```
|
||||
|
||||
From another terminal:
|
||||
|
||||
```bash
|
||||
scrcpy --force-adb-forwrad
|
||||
```
|
||||
|
||||
|
||||
Like for wireless connections, it may be useful to reduce quality:
|
||||
|
||||
```
|
||||
@@ -338,7 +354,7 @@ scrcpy --fullscreen
|
||||
scrcpy -f # short version
|
||||
```
|
||||
|
||||
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
||||
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
|
||||
|
||||
#### Rotation
|
||||
|
||||
@@ -354,18 +370,18 @@ Possibles values are:
|
||||
- `2`: 180 degrees
|
||||
- `3`: 90 degrees clockwise
|
||||
|
||||
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
|
||||
`Ctrl`+`→` _(right)_.
|
||||
The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
|
||||
`RCtrl`+`→` _(right)_.
|
||||
|
||||
Note that _scrcpy_ manages 3 different rotations:
|
||||
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
current running app may refuse, if it does support the requested
|
||||
orientation).
|
||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||
of the video sent from the device to the computer). This affects the
|
||||
recording.
|
||||
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
||||
affects only the display, not the recording.
|
||||
- `--rotation` (or `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
|
||||
This affects only the display, not the recording.
|
||||
|
||||
|
||||
### Other mirroring options
|
||||
@@ -401,7 +417,7 @@ The secondary display may only be controlled if the device runs at least Android
|
||||
|
||||
#### 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
|
||||
scrcpy --stay-awake
|
||||
@@ -421,9 +437,9 @@ scrcpy --turn-screen-off
|
||||
scrcpy -S
|
||||
```
|
||||
|
||||
Or by pressing `Ctrl`+`o` at any time.
|
||||
Or by pressing `RCtrl`+`o` at any time.
|
||||
|
||||
To turn it back on, press `POWER` (or `Ctrl`+`p`).
|
||||
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
|
||||
|
||||
It can be useful to also prevent the device to sleep:
|
||||
|
||||
@@ -467,23 +483,22 @@ Note that it only shows _physical_ touches (with the finger on the device).
|
||||
|
||||
#### Rotate device screen
|
||||
|
||||
Press `Ctrl`+`r` to switch between portrait and landscape modes.
|
||||
Press `RCtrl`+`r` to switch between portrait and landscape modes.
|
||||
|
||||
Note that it rotates only if the application in foreground supports the
|
||||
requested orientation.
|
||||
|
||||
#### Copy-paste
|
||||
|
||||
It is possible to synchronize clipboards between the computer and the device, in
|
||||
both directions:
|
||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||
computer clipboard.
|
||||
|
||||
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
|
||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||
breaks non-ASCII characters).
|
||||
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
|
||||
|
||||
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
|
||||
sequence of text event. Even if it can break non-ASCII content, this is
|
||||
sometimes necessary when pasting directly is not possible.
|
||||
|
||||
Moreover, any time the Android clipboard changes, it is automatically
|
||||
synchronized to the computer clipboard.
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
@@ -543,29 +558,34 @@ Also see [issue #14].
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Action | Shortcut | Shortcut (macOS)
|
||||
| -------------------------------------- |:----------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
||||
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
||||
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
||||
| 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¹_
|
||||
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
||||
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
||||
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
||||
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||
| Power on | _Right-click²_ | _Right-click²_
|
||||
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
||||
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
||||
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
||||
device).
|
||||
|
||||
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
|
||||
system).
|
||||
|
||||
| Action | Shortcut
|
||||
| ------------------------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `RCtrl`+`f`
|
||||
| Rotate display left | `RCtrl`+`←`
|
||||
| Rotate display right | `RCtrl`+`→`
|
||||
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
||||
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
|
||||
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
||||
| Click on `MENU` | `RCtrl`+`m`
|
||||
| Click on `VOLUME_UP` | `RCtrl`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
|
||||
| Click on `POWER` | `RCtrl`+`p`
|
||||
| Power on | _Right-click²_
|
||||
| Turn device screen off (keep mirroring) | `RCtrl`+`o`
|
||||
| Turn device screen on | `RCtrl`+`Shift`+`o`
|
||||
| Rotate device screen | `RCtrl`+`r`
|
||||
| Expand notification panel | `RCtrl`+`n`
|
||||
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
||||
| Inject computer clipboard text | `RCtrl`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
61
app/scrcpy.1
61
app/scrcpy.1
@@ -52,6 +52,10 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-f, \-\-fullscreen
|
||||
Start in fullscreen.
|
||||
@@ -125,6 +129,7 @@ Force recording format (either mp4 or mkv).
|
||||
Request SDL to use the given render driver (this is just a hint).
|
||||
|
||||
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
|
||||
|
||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||
.UE
|
||||
|
||||
@@ -146,7 +151,7 @@ Turn the device screen off immediately.
|
||||
|
||||
.TP
|
||||
.B \-t, \-\-show\-touches
|
||||
Enable "show touches" on start, restore the initial value on exit..
|
||||
Enable "show touches" on start, restore the initial value on exit.
|
||||
|
||||
It only shows physical touches (not clicks from scrcpy).
|
||||
|
||||
@@ -162,7 +167,7 @@ Default is "info" for release builds, "debug" for debug builds.
|
||||
|
||||
.TP
|
||||
.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
|
||||
.B \-\-window\-borderless
|
||||
@@ -198,52 +203,54 @@ Default is 0 (automatic).\n
|
||||
|
||||
.SH SHORTCUTS
|
||||
|
||||
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
|
||||
|
||||
.TP
|
||||
.B Ctrl+f
|
||||
.B RCtrl+f
|
||||
Switch fullscreen mode
|
||||
|
||||
.TP
|
||||
.B Ctrl+Left
|
||||
.B RCtrl+Left
|
||||
Rotate display left
|
||||
|
||||
.TP
|
||||
.B Ctrl+Right
|
||||
.B RCtrl+Right
|
||||
Rotate display right
|
||||
|
||||
.TP
|
||||
.B Ctrl+g
|
||||
.B RCtrl+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
||||
.TP
|
||||
.B Ctrl+x, Double\-click on black borders
|
||||
.B RCtrl+w, Double\-click on black borders
|
||||
Resize window to remove black borders
|
||||
|
||||
.TP
|
||||
.B Ctrl+h, Home, Middle\-click
|
||||
.B RCtrl+h, Home, Middle\-click
|
||||
Click on HOME
|
||||
|
||||
.TP
|
||||
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
|
||||
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
|
||||
Click on BACK
|
||||
|
||||
.TP
|
||||
.B Ctrl+s
|
||||
.B RCtrl+s
|
||||
Click on APP_SWITCH
|
||||
|
||||
.TP
|
||||
.B Ctrl+m
|
||||
.B RCtrl+m
|
||||
Click on MENU
|
||||
|
||||
.TP
|
||||
.B Ctrl+Up
|
||||
.B RCtrl+Up
|
||||
Click on VOLUME_UP
|
||||
|
||||
.TP
|
||||
.B Ctrl+Down
|
||||
.B RCtrl+Down
|
||||
Click on VOLUME_DOWN
|
||||
|
||||
.TP
|
||||
.B Ctrl+p
|
||||
.B RCtrl+p
|
||||
Click on POWER (turn screen on/off)
|
||||
|
||||
.TP
|
||||
@@ -251,35 +258,31 @@ Click on POWER (turn screen on/off)
|
||||
Turn screen on
|
||||
|
||||
.TP
|
||||
.B Ctrl+o
|
||||
.B RCtrl+o
|
||||
Turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B Ctrl+r
|
||||
.B RCtrl+Shift+o
|
||||
Turn device screen on
|
||||
|
||||
.TP
|
||||
.B RCtrl+r
|
||||
Rotate device screen
|
||||
|
||||
.TP
|
||||
.B Ctrl+n
|
||||
.B RCtrl+n
|
||||
Expand notification panel
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+n
|
||||
.B RCtrl+Shift+n
|
||||
Collapse notification panel
|
||||
|
||||
.TP
|
||||
.B Ctrl+c
|
||||
Copy device clipboard to computer
|
||||
.B RCtrl+v
|
||||
Inject computer clipboard text
|
||||
|
||||
.TP
|
||||
.B Ctrl+v
|
||||
Paste computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+v
|
||||
Copy computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B Ctrl+i
|
||||
.B RCtrl+i
|
||||
Enable/disable FPS counter (print frames/second in logs)
|
||||
|
||||
.TP
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
|
||||
void
|
||||
scrcpy_print_usage(const char *arg0) {
|
||||
#ifdef __APPLE__
|
||||
# define CTRL_OR_CMD "Cmd"
|
||||
#else
|
||||
# define CTRL_OR_CMD "Ctrl"
|
||||
#endif
|
||||
fprintf(stderr,
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
@@ -54,6 +49,10 @@ scrcpy_print_usage(const char *arg0) {
|
||||
"\n"
|
||||
" Default is 0.\n"
|
||||
"\n"
|
||||
" --force-adb-forward\n"
|
||||
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
||||
" the device.\n"
|
||||
"\n"
|
||||
" -f, --fullscreen\n"
|
||||
" Start in fullscreen.\n"
|
||||
"\n"
|
||||
@@ -155,7 +154,8 @@ scrcpy_print_usage(const char *arg0) {
|
||||
#endif
|
||||
"\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"
|
||||
" --window-borderless\n"
|
||||
" Disable window decorations (display borderless window).\n"
|
||||
@@ -181,71 +181,71 @@ scrcpy_print_usage(const char *arg0) {
|
||||
"\n"
|
||||
"Shortcuts:\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+f\n"
|
||||
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
|
||||
" the device.\n"
|
||||
"\n"
|
||||
" RCtrl+f\n"
|
||||
" Switch fullscreen mode\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Left\n"
|
||||
" RCtrl+Left\n"
|
||||
" Rotate display left\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Right\n"
|
||||
" RCtrl+Right\n"
|
||||
" Rotate display right\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+g\n"
|
||||
" RCtrl+g\n"
|
||||
" Resize window to 1:1 (pixel-perfect)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+x\n"
|
||||
" RCtrl+w\n"
|
||||
" Double-click on black borders\n"
|
||||
" Resize window to remove black borders\n"
|
||||
"\n"
|
||||
" Ctrl+h\n"
|
||||
" RCtrl+h\n"
|
||||
" Middle-click\n"
|
||||
" Click on HOME\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+b\n"
|
||||
" " CTRL_OR_CMD "+Backspace\n"
|
||||
" RCtrl+b\n"
|
||||
" RCtrl+Backspace\n"
|
||||
" Right-click (when screen is on)\n"
|
||||
" Click on BACK\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+s\n"
|
||||
" RCtrl+s\n"
|
||||
" Click on APP_SWITCH\n"
|
||||
"\n"
|
||||
" Ctrl+m\n"
|
||||
" RCtrl+m\n"
|
||||
" Click on MENU\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Up\n"
|
||||
" RCtrl+Up\n"
|
||||
" Click on VOLUME_UP\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Down\n"
|
||||
" RCtrl+Down\n"
|
||||
" Click on VOLUME_DOWN\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+p\n"
|
||||
" RCtrl+p\n"
|
||||
" Click on POWER (turn screen on/off)\n"
|
||||
"\n"
|
||||
" Right-click (when screen is off)\n"
|
||||
" Power on\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+o\n"
|
||||
" RCtrl+o\n"
|
||||
" Turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+r\n"
|
||||
" RCtrl+Shift+o\n"
|
||||
" Turn device screen on\n"
|
||||
"\n"
|
||||
" RCtrl+r\n"
|
||||
" Rotate device screen\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+n\n"
|
||||
" RCtrl+n\n"
|
||||
" Expand notification panel\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Shift+n\n"
|
||||
" RCtrl+Shift+n\n"
|
||||
" Collapse notification panel\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+c\n"
|
||||
" Copy device clipboard to computer\n"
|
||||
" RCtrl+v\n"
|
||||
" Inject computer clipboard text\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+v\n"
|
||||
" Paste computer clipboard to device\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+Shift+v\n"
|
||||
" Copy computer clipboard to device\n"
|
||||
"\n"
|
||||
" " CTRL_OR_CMD "+i\n"
|
||||
" RCtrl+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
"\n"
|
||||
" Drag & drop APK file\n"
|
||||
@@ -516,6 +516,7 @@ guess_record_format(const char *filename) {
|
||||
#define OPT_RENDER_DRIVER 1016
|
||||
#define OPT_NO_MIPMAPS 1017
|
||||
#define OPT_CODEC_OPTIONS 1018
|
||||
#define OPT_FORCE_ADB_FORWARD 1019
|
||||
|
||||
bool
|
||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
@@ -525,6 +526,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
|
||||
{"crop", required_argument, NULL, OPT_CROP},
|
||||
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
||||
{"force-adb-forward", no_argument, NULL,
|
||||
OPT_FORCE_ADB_FORWARD},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"lock-video-orientation", required_argument, NULL,
|
||||
@@ -701,6 +704,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
case OPT_CODEC_OPTIONS:
|
||||
opts->codec_options = optarg;
|
||||
break;
|
||||
case OPT_FORCE_ADB_FORWARD:
|
||||
opts->force_adb_forward = true;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
||||
@@ -67,10 +67,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||
return 21;
|
||||
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,
|
||||
&buf[1]);
|
||||
return 1 + len;
|
||||
&buf[2]);
|
||||
return 2 + len;
|
||||
}
|
||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include "common.h"
|
||||
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
|
||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||
|
||||
@@ -62,6 +62,7 @@ struct control_msg {
|
||||
} inject_scroll_event;
|
||||
struct {
|
||||
char *text; // owned, to be freed by SDL_free()
|
||||
bool paste;
|
||||
} set_clipboard;
|
||||
struct {
|
||||
enum screen_power_mode mode;
|
||||
|
||||
@@ -92,6 +92,9 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||
}
|
||||
|
||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||
@@ -111,7 +114,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
}
|
||||
}
|
||||
|
||||
if (prefer_text) {
|
||||
if (prefer_text && !(mod & KMOD_LCTRL)) {
|
||||
// do not forward alpha and space key events
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -102,17 +102,7 @@ collapse_notification_panel(struct controller *controller) {
|
||||
}
|
||||
|
||||
static void
|
||||
request_device_clipboard(struct controller *controller) {
|
||||
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) {
|
||||
set_device_clipboard(struct controller *controller, bool paste) {
|
||||
char *text = SDL_GetClipboardText();
|
||||
if (!text) {
|
||||
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
||||
@@ -127,6 +117,7 @@ set_device_clipboard(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||
msg.set_clipboard.text = text;
|
||||
msg.set_clipboard.paste = paste;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
SDL_free(text);
|
||||
@@ -251,6 +242,25 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
inject_as_ctrl(struct input_manager *im, const SDL_KeyboardEvent *event) {
|
||||
struct control_msg msg;
|
||||
|
||||
if (!convert_input_key(event, &msg, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable RCtrl and Meta
|
||||
msg.inject_keycode.metastate &=
|
||||
~(AMETA_CTRL_RIGHT_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
|
||||
// Enable LCtrl
|
||||
msg.inject_keycode.metastate |= AMETA_CTRL_LEFT_ON;
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_key(struct input_manager *im,
|
||||
const SDL_KeyboardEvent *event,
|
||||
@@ -258,134 +268,127 @@ input_manager_process_key(struct input_manager *im,
|
||||
// control: indicates the state of the command-line option --no-control
|
||||
// ctrl: the Ctrl key
|
||||
|
||||
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||
bool lctrl = event->keysym.mod & KMOD_LCTRL;
|
||||
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
|
||||
// use Cmd on macOS, Ctrl on other platforms
|
||||
bool shortcut_key = rctrl;
|
||||
#ifdef __APPLE__
|
||||
bool cmd = !ctrl && meta;
|
||||
shortcut_key |= meta;
|
||||
#else
|
||||
if (meta) {
|
||||
// no shortcuts involve Meta on platforms other than macOS, and it must
|
||||
// not be forwarded to the device
|
||||
// No shortcut involve Meta, and it is not 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
|
||||
// No shortcuts involve Alt, and it is not forwarded to the device
|
||||
return;
|
||||
}
|
||||
|
||||
struct controller *controller = im->controller;
|
||||
|
||||
// capture all Ctrl events
|
||||
if (ctrl || cmd) {
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
|
||||
// Capture all RCtrl events
|
||||
if (shortcut_key) {
|
||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||
bool repeat = event->repeat;
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||
// the system on macOS to hide the window
|
||||
if (control && ctrl && !meta && !shift && !repeat) {
|
||||
if (control && !shift && !repeat) {
|
||||
action_home(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
if (control && !shift && !repeat) {
|
||||
action_back(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
if (control && !shift && !repeat) {
|
||||
action_app_switch(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
// Ctrl+m on all platform, since Cmd+m is already captured by
|
||||
// the system on macOS to minimize the window
|
||||
if (control && ctrl && !meta && !shift && !repeat) {
|
||||
if (control && !shift && !repeat) {
|
||||
action_menu(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (control && cmd && !shift && !repeat) {
|
||||
if (control && !shift && !repeat) {
|
||||
action_power(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && cmd && !shift && down) {
|
||||
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
|
||||
if (control && !repeat && down) {
|
||||
enum screen_power_mode mode = shift
|
||||
? SCREEN_POWER_MODE_NORMAL
|
||||
: SCREEN_POWER_MODE_OFF;
|
||||
set_screen_power_mode(controller, mode);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (control && cmd && !shift) {
|
||||
if (control && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_down(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (control && cmd && !shift) {
|
||||
if (control && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_up(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_LEFT:
|
||||
if (cmd && !shift && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
rotate_client_left(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_RIGHT:
|
||||
if (cmd && !shift && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
rotate_client_right(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
request_device_clipboard(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (control && cmd && !repeat && down) {
|
||||
if (shift) {
|
||||
// store the text in the device clipboard
|
||||
set_device_clipboard(controller);
|
||||
} else {
|
||||
// inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
}
|
||||
if (control && !shift && !repeat && down) {
|
||||
// Inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_f:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
screen_switch_fullscreen(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
case SDLK_w:
|
||||
if (!shift && !repeat && down) {
|
||||
screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
screen_resize_to_pixel_perfect(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_i:
|
||||
if (!shift && cmd && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
struct fps_counter *fps_counter =
|
||||
im->video_buffer->fps_counter;
|
||||
switch_fps_counter_state(fps_counter);
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
if (control && cmd && !repeat && down) {
|
||||
if (control && !repeat && down) {
|
||||
if (shift) {
|
||||
collapse_notification_panel(controller);
|
||||
} else {
|
||||
@@ -394,10 +397,19 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && cmd && !shift && !repeat && down) {
|
||||
if (control && !shift && !repeat && down) {
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
case SDLK_x:
|
||||
if (control && !shift) {
|
||||
// For convenience, forward shortcut_key+c and
|
||||
// shortcut_key+x as Ctrl+c and Ctrl+x (typically "copy" and
|
||||
// "cut", but not always) to the device
|
||||
inject_as_ctrl(im, event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -407,6 +419,12 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (lctrl && !shift && keycode == SDLK_v && down) {
|
||||
// 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;
|
||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
|
||||
@@ -42,7 +42,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
|
||||
return SDL_LOG_PRIORITY_ERROR;
|
||||
default:
|
||||
assert(!"unexpected log level");
|
||||
return SC_LOG_LEVEL_INFO;
|
||||
return SDL_LOG_PRIORITY_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
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) {
|
||||
scrcpy_print_usage(argv[0]);
|
||||
|
||||
@@ -305,6 +305,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
.show_touches = options->show_touches,
|
||||
.stay_awake = options->stay_awake,
|
||||
.codec_options = options->codec_options,
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
};
|
||||
if (!server_start(&server, options->serial, ¶ms)) {
|
||||
return false;
|
||||
|
||||
@@ -42,6 +42,7 @@ struct scrcpy_options {
|
||||
bool window_borderless;
|
||||
bool mipmaps;
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
};
|
||||
|
||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
@@ -79,6 +80,7 @@ struct scrcpy_options {
|
||||
.window_borderless = false, \
|
||||
.mipmaps = true, \
|
||||
.stay_awake = false, \
|
||||
.force_adb_forward = false, \
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -217,15 +217,20 @@ enable_tunnel_forward_any_port(struct server *server,
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_any_port(struct server *server, struct port_range port_range) {
|
||||
if (enable_tunnel_reverse_any_port(server, port_range)) {
|
||||
return true;
|
||||
enable_tunnel_any_port(struct server *server, struct port_range port_range,
|
||||
bool force_adb_forward) {
|
||||
if (!force_adb_forward) {
|
||||
// Attempt to use "adb reverse"
|
||||
if (enable_tunnel_reverse_any_port(server, port_range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it
|
||||
// fallbacks to "adb forward", so the app socket is the client
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
||||
// "adb forward", so the app socket is the client
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
return enable_tunnel_forward_any_port(server, port_range);
|
||||
}
|
||||
|
||||
@@ -384,7 +389,8 @@ server_start(struct server *server, const char *serial,
|
||||
goto error1;
|
||||
}
|
||||
|
||||
if (!enable_tunnel_any_port(server, params->port_range)) {
|
||||
if (!enable_tunnel_any_port(server, params->port_range,
|
||||
params->force_adb_forward)) {
|
||||
goto error1;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ struct server_params {
|
||||
uint16_t display_id;
|
||||
bool show_touches;
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
};
|
||||
|
||||
// init default values
|
||||
|
||||
@@ -200,17 +200,19 @@ static void test_serialize_get_clipboard(void) {
|
||||
static void test_serialize_set_clipboard(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
.inject_text = {
|
||||
.set_clipboard = {
|
||||
.paste = true,
|
||||
.text = "hello, world!",
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 16);
|
||||
assert(size == 17);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
1, // paste
|
||||
0x00, 0x0d, // text length
|
||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '1.13',
|
||||
version: '1.14',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -35,6 +35,6 @@ prepare-sdl2:
|
||||
SDL2-2.0.12
|
||||
|
||||
prepare-adb:
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
|
||||
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \
|
||||
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
|
||||
platform-tools
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 15
|
||||
versionName "1.13"
|
||||
versionCode 16
|
||||
versionName "1.14"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=1.13
|
||||
SCRCPY_VERSION_NAME=1.14
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||
|
||||
@@ -28,6 +28,7 @@ public final class ControlMessage {
|
||||
private Position position;
|
||||
private int hScroll;
|
||||
private int vScroll;
|
||||
private boolean paste;
|
||||
|
||||
private ControlMessage() {
|
||||
}
|
||||
@@ -68,10 +69,11 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createSetClipboard(String text) {
|
||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
msg.text = text;
|
||||
msg.paste = paste;
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -134,4 +136,8 @@ public final class ControlMessage {
|
||||
public int getVScroll() {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public boolean getPaste() {
|
||||
return paste;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ public class ControlMessageReader {
|
||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
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;
|
||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
|
||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||
|
||||
private static final int RAW_BUFFER_SIZE = 4096;
|
||||
|
||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
||||
|
||||
public ControlMessageReader() {
|
||||
// invariant: the buffer is always in "get" mode
|
||||
@@ -110,8 +110,10 @@ public class ControlMessageReader {
|
||||
if (buffer.remaining() < len) {
|
||||
return null;
|
||||
}
|
||||
buffer.get(textBuffer, 0, len);
|
||||
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||
int position = buffer.position();
|
||||
// Move the buffer position to consume the text
|
||||
buffer.position(position + len);
|
||||
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private ControlMessage parseInjectText() {
|
||||
@@ -148,11 +150,15 @@ public class ControlMessageReader {
|
||||
}
|
||||
|
||||
private ControlMessage parseSetClipboard() {
|
||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
boolean parse = buffer.get() != 0;
|
||||
String text = parseString();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
return ControlMessage.createSetClipboard(text);
|
||||
return ControlMessage.createSetClipboard(text, parse);
|
||||
}
|
||||
|
||||
private ControlMessage parseSetScreenPowerMode() {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
@@ -50,7 +48,7 @@ public class Controller {
|
||||
public void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (!device.isScreenOn()) {
|
||||
injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||
|
||||
// dirty hack
|
||||
// After POWER is injected, the device is powered on asynchronously.
|
||||
@@ -107,14 +105,20 @@ public class Controller {
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
String clipboardText = device.getClipboardText();
|
||||
sender.pushClipboardText(clipboardText);
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
device.setClipboardText(msg.getText());
|
||||
setClipboard(msg.getText(), msg.getPaste());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
device.setScreenPowerMode(msg.getAction());
|
||||
int mode = msg.getAction();
|
||||
boolean setPowerModeOk = device.setScreenPowerMode(mode);
|
||||
if (setPowerModeOk) {
|
||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
@@ -126,7 +130,7 @@ public class Controller {
|
||||
}
|
||||
|
||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
||||
return injectKeyEvent(action, keycode, 0, metaState);
|
||||
return device.injectKeyEvent(action, keycode, 0, metaState);
|
||||
}
|
||||
|
||||
private boolean injectChar(char c) {
|
||||
@@ -137,7 +141,7 @@ public class Controller {
|
||||
return false;
|
||||
}
|
||||
for (KeyEvent event : events) {
|
||||
if (!injectEvent(event)) {
|
||||
if (!device.injectEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -193,7 +197,7 @@ public class Controller {
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
return injectEvent(event);
|
||||
return device.injectEvent(event);
|
||||
}
|
||||
|
||||
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
||||
@@ -216,26 +220,25 @@ public class Controller {
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
private boolean injectKeycode(int keyCode) {
|
||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
||||
}
|
||||
|
||||
private boolean injectEvent(InputEvent event) {
|
||||
return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
return device.injectEvent(event);
|
||||
}
|
||||
|
||||
private boolean pressBackOrTurnScreenOn() {
|
||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,14 @@ import android.content.IOnPrimaryClipChangedListener;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.view.IRotationWatcher;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class Device {
|
||||
|
||||
@@ -31,6 +37,7 @@ public final class Device {
|
||||
private ScreenInfo screenInfo;
|
||||
private RotationListener rotationListener;
|
||||
private ClipboardListener clipboardListener;
|
||||
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Logical display identifier
|
||||
@@ -76,6 +83,10 @@ public final class Device {
|
||||
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||
@Override
|
||||
public void dispatchPrimaryClipChanged() {
|
||||
if (isSettingClipboard.get()) {
|
||||
// This is a notification for the change we are currently applying, ignore it
|
||||
return;
|
||||
}
|
||||
synchronized (Device.this) {
|
||||
if (clipboardListener != null) {
|
||||
String text = getClipboardText();
|
||||
@@ -140,7 +151,7 @@ public final class Device {
|
||||
return supportsInputEvents;
|
||||
}
|
||||
|
||||
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
||||
if (!supportsInputEvents()) {
|
||||
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||
}
|
||||
@@ -152,6 +163,21 @@ public final class Device {
|
||||
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
|
||||
}
|
||||
|
||||
public boolean injectEvent(InputEvent event) {
|
||||
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
return injectEvent(event);
|
||||
}
|
||||
|
||||
public boolean injectKeycode(int keyCode) {
|
||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
||||
}
|
||||
|
||||
public boolean isScreenOn() {
|
||||
return serviceManager.getPowerManager().isScreenOn();
|
||||
}
|
||||
@@ -180,26 +206,31 @@ public final class Device {
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public void setClipboardText(String text) {
|
||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||
if (ok) {
|
||||
Ln.i("Device clipboard set");
|
||||
public boolean setClipboardText(String text) {
|
||||
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);
|
||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||
isSettingClipboard.set(false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
||||
*/
|
||||
public void setScreenPowerMode(int mode) {
|
||||
public boolean setScreenPowerMode(int mode) {
|
||||
IBinder d = SurfaceControl.getBuiltInDisplay();
|
||||
if (d == null) {
|
||||
Ln.e("Could not get built-in display");
|
||||
return;
|
||||
}
|
||||
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode);
|
||||
if (ok) {
|
||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||
return false;
|
||||
}
|
||||
return SurfaceControl.setDisplayPowerMode(d, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@ public final class Ln {
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static Level THRESHOLD;
|
||||
private static Level threshold = Level.INFO;
|
||||
|
||||
private Ln() {
|
||||
// not instantiable
|
||||
@@ -29,11 +29,11 @@ public final class Ln {
|
||||
* @param level the log level
|
||||
*/
|
||||
public static void initLogLevel(Level level) {
|
||||
THRESHOLD = level;
|
||||
threshold = level;
|
||||
}
|
||||
|
||||
public static boolean isEnabled(Level level) {
|
||||
return level.ordinal() >= THRESHOLD.ordinal();
|
||||
return level.ordinal() >= threshold.ordinal();
|
||||
}
|
||||
|
||||
public static void d(String message) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Build;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class Server {
|
||||
|
||||
@@ -126,7 +127,7 @@ public final class Server {
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase());
|
||||
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH));
|
||||
options.setLogLevel(level);
|
||||
|
||||
int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8
|
||||
|
||||
@@ -216,6 +216,7 @@ public class ControlMessageReaderTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||
dos.writeByte(1); // paste
|
||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||
dos.writeShort(text.length);
|
||||
dos.write(text);
|
||||
@@ -227,6 +228,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals("testé", event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -238,6 +240,7 @@ public class ControlMessageReaderTest {
|
||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||
|
||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
||||
dos.writeByte(1); // paste
|
||||
Arrays.fill(rawText, (byte) 'a');
|
||||
String text = new String(rawText, 0, rawText.length);
|
||||
|
||||
@@ -251,6 +254,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(text, event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user