Compare commits
1 Commits
clipboard
...
seamless_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ae688764c |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
/dist/
|
/dist/
|
||||||
/build-*/
|
|
||||||
/build_*/
|
|
||||||
/release-*/
|
|
||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
/x/
|
/x/
|
||||||
|
|||||||
6
BUILD.md
6
BUILD.md
@@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_.
|
|||||||
|
|
||||||
## Prebuilt server
|
## Prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
|
||||||
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
|
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
110
README.md
110
README.md
@@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.14)
|
# scrcpy (v1.13)
|
||||||
|
|
||||||
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.
|
||||||
@@ -69,10 +69,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.14.zip`][direct-win64]
|
- [`scrcpy-win64-v1.13.zip`][direct-win64]
|
||||||
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
|
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
|
||||||
|
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip
|
||||||
|
|
||||||
It is also available in [Chocolatey]:
|
It is also available in [Chocolatey]:
|
||||||
|
|
||||||
@@ -289,22 +289,6 @@ From another terminal:
|
|||||||
scrcpy
|
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:
|
Like for wireless connections, it may be useful to reduce quality:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -354,7 +338,7 @@ scrcpy --fullscreen
|
|||||||
scrcpy -f # short version
|
scrcpy -f # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
|
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
||||||
|
|
||||||
#### Rotation
|
#### Rotation
|
||||||
|
|
||||||
@@ -370,18 +354,18 @@ 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 `RCtrl`+`←` _(left)_ and
|
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
|
||||||
`RCtrl`+`→` _(right)_.
|
`Ctrl`+`→` _(right)_.
|
||||||
|
|
||||||
Note that _scrcpy_ manages 3 different rotations:
|
Note that _scrcpy_ manages 3 different rotations:
|
||||||
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
|
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
|
||||||
current running app may refuse, if it does support the requested
|
current running app may refuse, if it does support the requested
|
||||||
orientation).
|
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 `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
|
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
||||||
This affects only the display, not the recording.
|
affects only the display, not the recording.
|
||||||
|
|
||||||
|
|
||||||
### Other mirroring options
|
### Other mirroring options
|
||||||
@@ -417,7 +401,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 when the device is plugged in:
|
To prevent the device to sleep after some delay:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --stay-awake
|
scrcpy --stay-awake
|
||||||
@@ -437,9 +421,9 @@ scrcpy --turn-screen-off
|
|||||||
scrcpy -S
|
scrcpy -S
|
||||||
```
|
```
|
||||||
|
|
||||||
Or by pressing `RCtrl`+`o` at any time.
|
Or by pressing `Ctrl`+`o` at any time.
|
||||||
|
|
||||||
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
|
To turn it back on, press `POWER` (or `Ctrl`+`p`).
|
||||||
|
|
||||||
It can be useful to also prevent the device to sleep:
|
It can be useful to also prevent the device to sleep:
|
||||||
|
|
||||||
@@ -483,22 +467,23 @@ Note that it only shows _physical_ touches (with the finger on the device).
|
|||||||
|
|
||||||
#### Rotate device screen
|
#### Rotate device screen
|
||||||
|
|
||||||
Press `RCtrl`+`r` to switch between portrait and landscape modes.
|
Press `Ctrl`+`r` 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
|
||||||
|
|
||||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
It is possible to synchronize clipboards between the computer and the device, in
|
||||||
computer clipboard.
|
both directions:
|
||||||
|
|
||||||
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
|
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
||||||
|
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
|
||||||
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
|
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||||
sequence of text event. Even if it can break non-ASCII content, this is
|
breaks non-ASCII characters).
|
||||||
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
|
#### Text injection preference
|
||||||
|
|
||||||
@@ -558,34 +543,29 @@ Also see [issue #14].
|
|||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
| Action | Shortcut | Shortcut (macOS)
|
||||||
device).
|
| -------------------------------------- |:----------------------------- |:-----------------------------
|
||||||
|
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
|
||||||
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
|
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
|
||||||
system).
|
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
|
||||||
|
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
|
||||||
| Action | Shortcut
|
| 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_
|
||||||
| Switch fullscreen mode | `RCtrl`+`f`
|
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
|
||||||
| Rotate display left | `RCtrl`+`←`
|
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
|
||||||
| Rotate display right | `RCtrl`+`→`
|
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
|
||||||
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_
|
||||||
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
|
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
|
||||||
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
|
||||||
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
| Power on | _Right-click²_ | _Right-click²_
|
||||||
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
|
||||||
| Click on `MENU` | `RCtrl`+`m`
|
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
|
||||||
| Click on `VOLUME_UP` | `RCtrl`+`↑` _(up)_
|
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
|
||||||
| Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
|
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
|
||||||
| Click on `POWER` | `RCtrl`+`p`
|
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
|
||||||
| Power on | _Right-click²_
|
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
|
||||||
| Turn device screen off (keep mirroring) | `RCtrl`+`o`
|
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
|
||||||
| Turn device screen on | `RCtrl`+`Shift`+`o`
|
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
|
||||||
| 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._
|
_¹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._
|
||||||
|
|||||||
@@ -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_msg_serialize', [
|
['test_control_event_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_msg_deserialize', [
|
['test_device_event_deserialize', [
|
||||||
'tests/test_device_msg_deserialize.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
]],
|
]],
|
||||||
|
|||||||
77
app/scrcpy.1
77
app/scrcpy.1
@@ -25,16 +25,6 @@ Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are
|
|||||||
|
|
||||||
Default is 8000000.
|
Default is 8000000.
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-codec\-options " key[:type]=value[,...]
|
|
||||||
Set a list of comma-separated key:type=value options for the device encoder.
|
|
||||||
|
|
||||||
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
|
|
||||||
|
|
||||||
The list of possible codec options is available in the Android documentation
|
|
||||||
.UR https://d.android.com/reference/android/media/MediaFormat
|
|
||||||
.UE .
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
Crop the device screen on the server.
|
Crop the device screen on the server.
|
||||||
@@ -52,10 +42,6 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
|
|||||||
|
|
||||||
Default is 0.
|
Default is 0.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-force\-adb\-forward
|
|
||||||
Do not attempt to use "adb reverse" to connect to the device.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-f, \-\-fullscreen
|
.B \-f, \-\-fullscreen
|
||||||
Start in fullscreen.
|
Start in fullscreen.
|
||||||
@@ -129,7 +115,6 @@ 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
|
||||||
|
|
||||||
@@ -151,7 +136,7 @@ Turn the device screen off immediately.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-t, \-\-show\-touches
|
.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).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
@@ -159,15 +144,9 @@ It only shows physical touches (not clicks from scrcpy).
|
|||||||
.B \-v, \-\-version
|
.B \-v, \-\-version
|
||||||
Print the version of scrcpy.
|
Print the version of scrcpy.
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-V, \-\-verbosity " value
|
|
||||||
Set the log level ("debug", "info", "warn" or "error").
|
|
||||||
|
|
||||||
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, when the device is plugged in.
|
Keep the device on while scrcpy is running.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-window\-borderless
|
.B \-\-window\-borderless
|
||||||
@@ -203,54 +182,52 @@ Default is 0 (automatic).\n
|
|||||||
|
|
||||||
.SH SHORTCUTS
|
.SH SHORTCUTS
|
||||||
|
|
||||||
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+f
|
.B Ctrl+f
|
||||||
Switch fullscreen mode
|
Switch fullscreen mode
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Left
|
.B Ctrl+Left
|
||||||
Rotate display left
|
Rotate display left
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Right
|
.B Ctrl+Right
|
||||||
Rotate display right
|
Rotate display right
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+g
|
.B Ctrl+g
|
||||||
Resize window to 1:1 (pixel\-perfect)
|
Resize window to 1:1 (pixel\-perfect)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+w, Double\-click on black borders
|
.B Ctrl+x, Double\-click on black borders
|
||||||
Resize window to remove black borders
|
Resize window to remove black borders
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+h, Home, Middle\-click
|
.B Ctrl+h, Home, Middle\-click
|
||||||
Click on HOME
|
Click on HOME
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
|
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
|
||||||
Click on BACK
|
Click on BACK
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+s
|
.B Ctrl+s
|
||||||
Click on APP_SWITCH
|
Click on APP_SWITCH
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+m
|
.B Ctrl+m
|
||||||
Click on MENU
|
Click on MENU
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Up
|
.B Ctrl+Up
|
||||||
Click on VOLUME_UP
|
Click on VOLUME_UP
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Down
|
.B Ctrl+Down
|
||||||
Click on VOLUME_DOWN
|
Click on VOLUME_DOWN
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+p
|
.B Ctrl+p
|
||||||
Click on POWER (turn screen on/off)
|
Click on POWER (turn screen on/off)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -258,31 +235,35 @@ Click on POWER (turn screen on/off)
|
|||||||
Turn screen on
|
Turn screen on
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+o
|
.B Ctrl+o
|
||||||
Turn device screen off (keep mirroring)
|
Turn device screen off (keep mirroring)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Shift+o
|
.B Ctrl+r
|
||||||
Turn device screen on
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B RCtrl+r
|
|
||||||
Rotate device screen
|
Rotate device screen
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+n
|
.B Ctrl+n
|
||||||
Expand notification panel
|
Expand notification panel
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+Shift+n
|
.B Ctrl+Shift+n
|
||||||
Collapse notification panel
|
Collapse notification panel
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+v
|
.B Ctrl+c
|
||||||
Inject computer clipboard text
|
Copy device clipboard to computer
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B RCtrl+i
|
.B Ctrl+v
|
||||||
|
Paste computer clipboard to device
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+Shift+v
|
||||||
|
Copy computer clipboard to device
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Ctrl+i
|
||||||
Enable/disable FPS counter (print frames/second in logs)
|
Enable/disable FPS counter (print frames/second in logs)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
128
app/src/cli.c
128
app/src/cli.c
@@ -12,6 +12,11 @@
|
|||||||
|
|
||||||
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"
|
||||||
@@ -25,15 +30,6 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
" Default is %d.\n"
|
" Default is %d.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --codec-options key[:type]=value[,...]\n"
|
|
||||||
" Set a list of comma-separated key:type=value options for the\n"
|
|
||||||
" device encoder.\n"
|
|
||||||
" The possible values for 'type' are 'int' (default), 'long',\n"
|
|
||||||
" 'float' and 'string'.\n"
|
|
||||||
" The list of possible codec options is available in the\n"
|
|
||||||
" Android documentation:\n"
|
|
||||||
" <https://d.android.com/reference/android/media/MediaFormat>\n"
|
|
||||||
"\n"
|
|
||||||
" --crop width:height:x:y\n"
|
" --crop width:height:x:y\n"
|
||||||
" Crop the device screen on the server.\n"
|
" Crop the device screen on the server.\n"
|
||||||
" The values are expressed in the device natural orientation\n"
|
" The values are expressed in the device natural orientation\n"
|
||||||
@@ -49,10 +45,6 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" Default is 0.\n"
|
" Default is 0.\n"
|
||||||
"\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"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -144,18 +136,9 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" -v, --version\n"
|
" -v, --version\n"
|
||||||
" Print the version of scrcpy.\n"
|
" Print the version of scrcpy.\n"
|
||||||
"\n"
|
|
||||||
" -V, --verbosity value\n"
|
|
||||||
" Set the log level (debug, info, warn or error).\n"
|
|
||||||
#ifndef NDEBUG
|
|
||||||
" Default is debug.\n"
|
|
||||||
#else
|
|
||||||
" Default is info.\n"
|
|
||||||
#endif
|
|
||||||
"\n"
|
"\n"
|
||||||
" -w, --stay-awake\n"
|
" -w, --stay-awake\n"
|
||||||
" Keep the device on while scrcpy is running, when the device\n"
|
" Keep the device on while scrcpy is running.\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"
|
||||||
@@ -181,71 +164,71 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
"Shortcuts:\n"
|
"Shortcuts:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
|
" " CTRL_OR_CMD "+f\n"
|
||||||
" the device.\n"
|
|
||||||
"\n"
|
|
||||||
" RCtrl+f\n"
|
|
||||||
" Switch fullscreen mode\n"
|
" Switch fullscreen mode\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Left\n"
|
" " CTRL_OR_CMD "+Left\n"
|
||||||
" Rotate display left\n"
|
" Rotate display left\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Right\n"
|
" " CTRL_OR_CMD "+Right\n"
|
||||||
" Rotate display right\n"
|
" Rotate display right\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+g\n"
|
" " CTRL_OR_CMD "+g\n"
|
||||||
" Resize window to 1:1 (pixel-perfect)\n"
|
" Resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+w\n"
|
" " CTRL_OR_CMD "+x\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"
|
||||||
" RCtrl+h\n"
|
" Ctrl+h\n"
|
||||||
" Middle-click\n"
|
" Middle-click\n"
|
||||||
" Click on HOME\n"
|
" Click on HOME\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+b\n"
|
" " CTRL_OR_CMD "+b\n"
|
||||||
" RCtrl+Backspace\n"
|
" " CTRL_OR_CMD "+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"
|
||||||
" RCtrl+s\n"
|
" " CTRL_OR_CMD "+s\n"
|
||||||
" Click on APP_SWITCH\n"
|
" Click on APP_SWITCH\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+m\n"
|
" Ctrl+m\n"
|
||||||
" Click on MENU\n"
|
" Click on MENU\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Up\n"
|
" " CTRL_OR_CMD "+Up\n"
|
||||||
" Click on VOLUME_UP\n"
|
" Click on VOLUME_UP\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Down\n"
|
" " CTRL_OR_CMD "+Down\n"
|
||||||
" Click on VOLUME_DOWN\n"
|
" Click on VOLUME_DOWN\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+p\n"
|
" " CTRL_OR_CMD "+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"
|
||||||
" RCtrl+o\n"
|
" " CTRL_OR_CMD "+o\n"
|
||||||
" Turn device screen off (keep mirroring)\n"
|
" Turn device screen off (keep mirroring)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Shift+o\n"
|
" " CTRL_OR_CMD "+r\n"
|
||||||
" Turn device screen on\n"
|
|
||||||
"\n"
|
|
||||||
" RCtrl+r\n"
|
|
||||||
" Rotate device screen\n"
|
" Rotate device screen\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+n\n"
|
" " CTRL_OR_CMD "+n\n"
|
||||||
" Expand notification panel\n"
|
" Expand notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+Shift+n\n"
|
" " CTRL_OR_CMD "+Shift+n\n"
|
||||||
" Collapse notification panel\n"
|
" Collapse notification panel\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+v\n"
|
" " CTRL_OR_CMD "+c\n"
|
||||||
" Inject computer clipboard text\n"
|
" Copy device clipboard to computer\n"
|
||||||
"\n"
|
"\n"
|
||||||
" RCtrl+i\n"
|
" " CTRL_OR_CMD "+v\n"
|
||||||
|
" Paste computer clipboard to device\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Shift+v\n"
|
||||||
|
" Copy computer clipboard to device\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+i\n"
|
||||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
@@ -441,32 +424,6 @@ parse_display_id(const char *s, uint16_t *display_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_log_level(const char *s, enum sc_log_level *log_level) {
|
|
||||||
if (!strcmp(s, "debug")) {
|
|
||||||
*log_level = SC_LOG_LEVEL_DEBUG;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "info")) {
|
|
||||||
*log_level = SC_LOG_LEVEL_INFO;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "warn")) {
|
|
||||||
*log_level = SC_LOG_LEVEL_WARN;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "error")) {
|
|
||||||
*log_level = SC_LOG_LEVEL_ERROR;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGE("Could not parse log level: %s", s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_record_format(const char *optarg, enum recorder_format *format) {
|
parse_record_format(const char *optarg, enum recorder_format *format) {
|
||||||
if (!strcmp(optarg, "mp4")) {
|
if (!strcmp(optarg, "mp4")) {
|
||||||
@@ -515,19 +472,14 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_ROTATION 1015
|
#define OPT_ROTATION 1015
|
||||||
#define OPT_RENDER_DRIVER 1016
|
#define OPT_RENDER_DRIVER 1016
|
||||||
#define OPT_NO_MIPMAPS 1017
|
#define OPT_NO_MIPMAPS 1017
|
||||||
#define OPT_CODEC_OPTIONS 1018
|
|
||||||
#define OPT_FORCE_ADB_FORWARD 1019
|
|
||||||
|
|
||||||
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[]) {
|
||||||
static const struct option long_options[] = {
|
static const struct option long_options[] = {
|
||||||
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
||||||
{"bit-rate", required_argument, NULL, 'b'},
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
|
|
||||||
{"crop", required_argument, NULL, OPT_CROP},
|
{"crop", required_argument, NULL, OPT_CROP},
|
||||||
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
||||||
{"force-adb-forward", no_argument, NULL,
|
|
||||||
OPT_FORCE_ADB_FORWARD},
|
|
||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"lock-video-orientation", required_argument, NULL,
|
{"lock-video-orientation", required_argument, NULL,
|
||||||
@@ -550,7 +502,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"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'},
|
||||||
{"verbosity", required_argument, NULL, 'V'},
|
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||||
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
||||||
@@ -567,8 +518,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
optind = 0; // reset to start from the first argument in tests
|
optind = 0; // reset to start from the first argument in tests
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w",
|
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvw", long_options,
|
||||||
long_options, NULL)) != -1) {
|
NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
if (!parse_bit_rate(optarg, &opts->bit_rate)) {
|
||||||
@@ -647,11 +598,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case 'v':
|
case 'v':
|
||||||
args->version = true;
|
args->version = true;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
|
||||||
if (!parse_log_level(optarg, &opts->log_level)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'w':
|
case 'w':
|
||||||
opts->stay_awake = true;
|
opts->stay_awake = true;
|
||||||
break;
|
break;
|
||||||
@@ -701,12 +647,6 @@ 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_CODEC_OPTIONS:
|
|
||||||
opts->codec_options = optarg;
|
|
||||||
break;
|
|
||||||
case OPT_FORCE_ADB_FORWARD:
|
|
||||||
opts->force_adb_forward = true;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -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_write32be(buf, len);
|
buffer_write16be(buf, (uint16_t) len);
|
||||||
memcpy(&buf[4], utf8, len);
|
memcpy(&buf[2], utf8, len);
|
||||||
return 4 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
static uint16_t
|
||||||
@@ -67,7 +67,7 @@ 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->set_clipboard.text,
|
size_t len = write_string(msg->inject_text.text,
|
||||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
&buf[1]);
|
&buf[1]);
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
|
|||||||
@@ -10,11 +10,10 @@
|
|||||||
#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
|
||||||
// type: 1 byte; length: 4 bytes
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 5)
|
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||||
|
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ struct control_msg {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
// buf size must be at least CONTROL_MSG_SERIALIZED_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) {
|
||||||
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
unsigned char serialized_msg[CONTROL_MSG_SERIALIZED_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 < 5) {
|
if (len < 3) {
|
||||||
// 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: {
|
||||||
size_t clipboard_len = buffer_read32be(&buf[1]);
|
uint16_t clipboard_len = buffer_read16be(&buf[1]);
|
||||||
if (clipboard_len > len - 5) {
|
if (clipboard_len > len - 3) {
|
||||||
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[5], clipboard_len);
|
memcpy(text, &buf[3], clipboard_len);
|
||||||
}
|
}
|
||||||
text[clipboard_len] = '\0';
|
text[clipboard_len] = '\0';
|
||||||
|
|
||||||
msg->clipboard.text = text;
|
msg->clipboard.text = text;
|
||||||
return 5 + clipboard_len;
|
return 3 + clipboard_len;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
|
|||||||
@@ -7,9 +7,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
||||||
// type: 1 byte; length: 4 bytes
|
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
||||||
#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,9 +92,6 @@ 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_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
|
||||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
||||||
@@ -114,7 +111,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer_text && !(mod & KMOD_LCTRL)) {
|
if (prefer_text) {
|
||||||
// do not forward alpha and space key events
|
// do not forward alpha and space key events
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,16 @@ 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
|
static void
|
||||||
set_device_clipboard(struct controller *controller) {
|
set_device_clipboard(struct controller *controller) {
|
||||||
char *text = SDL_GetClipboardText();
|
char *text = SDL_GetClipboardText();
|
||||||
@@ -241,25 +251,6 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
return true;
|
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
|
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,
|
||||||
@@ -267,127 +258,134 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
// 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
|
// ctrl: the Ctrl key
|
||||||
|
|
||||||
bool lctrl = event->keysym.mod & KMOD_LCTRL;
|
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||||
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
|
||||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
||||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
||||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
|
|
||||||
bool shortcut_key = rctrl;
|
// use Cmd on macOS, Ctrl on other platforms
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
shortcut_key |= meta;
|
bool cmd = !ctrl && meta;
|
||||||
#else
|
#else
|
||||||
if (meta) {
|
if (meta) {
|
||||||
// No shortcut involve Meta, and it is not forwarded to the device
|
// no shortcuts involve Meta on platforms other than macOS, and it must
|
||||||
|
// not be forwarded to the device
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
bool cmd = ctrl; // && !meta, already guaranteed
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (alt) {
|
if (alt) {
|
||||||
// No shortcuts involve Alt, and it is not forwarded to the device
|
// no shortcuts involve Alt, and it must not be forwarded to the device
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
// capture all Ctrl events
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
if (ctrl || cmd) {
|
||||||
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
// Capture all RCtrl events
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
if (shortcut_key) {
|
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
bool repeat = event->repeat;
|
bool repeat = event->repeat;
|
||||||
|
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||||
// the system on macOS to hide the window
|
// the system on macOS to hide the window
|
||||||
if (control && !shift && !repeat) {
|
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 && !shift && !repeat) {
|
if (control && cmd && !shift && !repeat) {
|
||||||
action_back(controller, action);
|
action_back(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_s:
|
case SDLK_s:
|
||||||
if (control && !shift && !repeat) {
|
if (control && cmd && !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
|
// Ctrl+m on all platform, since Cmd+m is already captured by
|
||||||
// the system on macOS to minimize the window
|
// the system on macOS to minimize the window
|
||||||
if (control && !shift && !repeat) {
|
if (control && ctrl && !meta && !shift && !repeat) {
|
||||||
action_menu(controller, action);
|
action_menu(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (control && !shift && !repeat) {
|
if (control && cmd && !shift && !repeat) {
|
||||||
action_power(controller, action);
|
action_power(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
if (control && !repeat && down) {
|
if (control && cmd && !shift && down) {
|
||||||
enum screen_power_mode mode = shift
|
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
|
||||||
? SCREEN_POWER_MODE_NORMAL
|
|
||||||
: SCREEN_POWER_MODE_OFF;
|
|
||||||
set_screen_power_mode(controller, mode);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
if (control && !shift) {
|
if (control && cmd && !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 && !shift) {
|
if (control && cmd && !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 (!shift && !repeat && down) {
|
if (cmd && !shift && down) {
|
||||||
rotate_client_left(im->screen);
|
rotate_client_left(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_RIGHT:
|
case SDLK_RIGHT:
|
||||||
if (!shift && !repeat && down) {
|
if (cmd && !shift && down) {
|
||||||
rotate_client_right(im->screen);
|
rotate_client_right(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case SDLK_c:
|
||||||
|
if (control && cmd && !shift && !repeat && down) {
|
||||||
|
request_device_clipboard(controller);
|
||||||
|
}
|
||||||
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && !shift && !repeat && down) {
|
if (control && cmd && !repeat && down) {
|
||||||
// Inject the text as input events
|
if (shift) {
|
||||||
clipboard_paste(controller);
|
// store the text in the device clipboard
|
||||||
|
set_device_clipboard(controller);
|
||||||
|
} else {
|
||||||
|
// inject the text as input events
|
||||||
|
clipboard_paste(controller);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
screen_switch_fullscreen(im->screen);
|
screen_switch_fullscreen(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_w:
|
case SDLK_x:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && cmd && !repeat && down) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_g:
|
case SDLK_g:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && cmd && !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 && !repeat && down) {
|
if (!shift && cmd && !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 && !repeat && down) {
|
if (control && cmd && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_notification_panel(controller);
|
collapse_notification_panel(controller);
|
||||||
} else {
|
} else {
|
||||||
@@ -396,19 +394,10 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && !shift && !repeat && down) {
|
if (control && cmd && !shift && !repeat && down) {
|
||||||
rotate_device(controller);
|
rotate_device(controller);
|
||||||
}
|
}
|
||||||
return;
|
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;
|
return;
|
||||||
@@ -418,12 +407,6 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
|||||||
@@ -29,24 +29,6 @@ print_version(void) {
|
|||||||
LIBAVUTIL_VERSION_MICRO);
|
LIBAVUTIL_VERSION_MICRO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_LogPriority
|
|
||||||
convert_log_level_to_sdl(enum sc_log_level level) {
|
|
||||||
switch (level) {
|
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
|
||||||
return SDL_LOG_PRIORITY_DEBUG;
|
|
||||||
case SC_LOG_LEVEL_INFO:
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
case SC_LOG_LEVEL_WARN:
|
|
||||||
return SDL_LOG_PRIORITY_WARN;
|
|
||||||
case SC_LOG_LEVEL_ERROR:
|
|
||||||
return SDL_LOG_PRIORITY_ERROR;
|
|
||||||
default:
|
|
||||||
assert(!"unexpected log level");
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@@ -56,23 +38,20 @@ main(int argc, char *argv[]) {
|
|||||||
setbuf(stderr, NULL);
|
setbuf(stderr, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||||
|
#endif
|
||||||
|
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
args.opts.log_level = SC_LOG_LEVEL_DEBUG;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!scrcpy_parse_args(&args, argc, argv)) {
|
if (!scrcpy_parse_args(&args, argc, argv)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
scrcpy_print_usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -60,29 +60,28 @@ static int
|
|||||||
run_receiver(void *data) {
|
run_receiver(void *data) {
|
||||||
struct receiver *receiver = data;
|
struct receiver *receiver = data;
|
||||||
|
|
||||||
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
unsigned char buf[DEVICE_MSG_SERIALIZED_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
assert(head < DEVICE_MSG_MAX_SIZE);
|
assert(head < DEVICE_MSG_SERIALIZED_MAX_SIZE);
|
||||||
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
ssize_t r = net_recv(receiver->control_socket, buf,
|
||||||
DEVICE_MSG_MAX_SIZE - head);
|
DEVICE_MSG_SERIALIZED_MAX_SIZE - head);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
LOGD("Receiver stopped");
|
LOGD("Receiver stopped");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
head += r;
|
ssize_t consumed = process_msgs(buf, 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], head);
|
memmove(buf, &buf[consumed], r - consumed);
|
||||||
|
head = r - consumed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -293,7 +293,6 @@ bool
|
|||||||
scrcpy(const struct scrcpy_options *options) {
|
scrcpy(const struct scrcpy_options *options) {
|
||||||
bool record = !!options->record_filename;
|
bool record = !!options->record_filename;
|
||||||
struct server_params params = {
|
struct server_params params = {
|
||||||
.log_level = options->log_level,
|
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
@@ -304,8 +303,6 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
.show_touches = options->show_touches,
|
.show_touches = options->show_touches,
|
||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.codec_options = options->codec_options,
|
|
||||||
.force_adb_forward = options->force_adb_forward,
|
|
||||||
};
|
};
|
||||||
if (!server_start(&server, options->serial, ¶ms)) {
|
if (!server_start(&server, options->serial, ¶ms)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
struct scrcpy_options {
|
struct scrcpy_options {
|
||||||
const char *serial;
|
const char *serial;
|
||||||
@@ -17,8 +16,6 @@ struct scrcpy_options {
|
|||||||
const char *window_title;
|
const char *window_title;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
|
||||||
enum sc_log_level log_level;
|
|
||||||
enum recorder_format record_format;
|
enum recorder_format record_format;
|
||||||
struct port_range port_range;
|
struct port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
@@ -42,7 +39,6 @@ struct scrcpy_options {
|
|||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
@@ -52,8 +48,6 @@ struct scrcpy_options {
|
|||||||
.window_title = NULL, \
|
.window_title = NULL, \
|
||||||
.push_target = NULL, \
|
.push_target = NULL, \
|
||||||
.render_driver = NULL, \
|
.render_driver = NULL, \
|
||||||
.codec_options = NULL, \
|
|
||||||
.log_level = SC_LOG_LEVEL_INFO, \
|
|
||||||
.record_format = RECORDER_FORMAT_AUTO, \
|
.record_format = RECORDER_FORMAT_AUTO, \
|
||||||
.port_range = { \
|
.port_range = { \
|
||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
||||||
@@ -80,7 +74,6 @@ struct scrcpy_options {
|
|||||||
.window_borderless = false, \
|
.window_borderless = false, \
|
||||||
.mipmaps = true, \
|
.mipmaps = true, \
|
||||||
.stay_awake = false, \
|
.stay_awake = false, \
|
||||||
.force_adb_forward = false, \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -217,46 +217,24 @@ 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 port_range port_range) {
|
||||||
bool force_adb_forward) {
|
if (enable_tunnel_reverse_any_port(server, port_range)) {
|
||||||
if (!force_adb_forward) {
|
return true;
|
||||||
// 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);
|
return enable_tunnel_forward_any_port(server, port_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
|
||||||
log_level_to_server_string(enum sc_log_level level) {
|
|
||||||
switch (level) {
|
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
|
||||||
return "debug";
|
|
||||||
case SC_LOG_LEVEL_INFO:
|
|
||||||
return "info";
|
|
||||||
case SC_LOG_LEVEL_WARN:
|
|
||||||
return "warn";
|
|
||||||
case SC_LOG_LEVEL_ERROR:
|
|
||||||
return "error";
|
|
||||||
default:
|
|
||||||
assert(!"unexpected log level");
|
|
||||||
return "(unknown)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static process_t
|
static process_t
|
||||||
execute_server(struct server *server, const struct server_params *params) {
|
execute_server(struct server *server, const struct server_params *params) {
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
char max_fps_string[6];
|
char max_fps_string[6];
|
||||||
char lock_video_orientation_string[5];
|
char lock_video_orientation_string[3];
|
||||||
char display_id_string[6];
|
char display_id_string[6];
|
||||||
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
||||||
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
||||||
@@ -281,7 +259,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
"/", // unused
|
"/", // unused
|
||||||
"com.genymobile.scrcpy.Server",
|
"com.genymobile.scrcpy.Server",
|
||||||
SCRCPY_VERSION,
|
SCRCPY_VERSION,
|
||||||
log_level_to_server_string(params->log_level),
|
|
||||||
max_size_string,
|
max_size_string,
|
||||||
bit_rate_string,
|
bit_rate_string,
|
||||||
max_fps_string,
|
max_fps_string,
|
||||||
@@ -293,7 +270,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
display_id_string,
|
display_id_string,
|
||||||
params->show_touches ? "true" : "false",
|
params->show_touches ? "true" : "false",
|
||||||
params->stay_awake ? "true" : "false",
|
params->stay_awake ? "true" : "false",
|
||||||
params->codec_options ? params->codec_options : "-",
|
|
||||||
};
|
};
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger waiting for a client on device port "
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
@@ -389,8 +365,7 @@ server_start(struct server *server, const char *serial,
|
|||||||
goto error1;
|
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;
|
goto error1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
@@ -44,9 +43,7 @@ struct server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct server_params {
|
struct server_params {
|
||||||
enum sc_log_level log_level;
|
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
|
||||||
struct port_range port_range;
|
struct port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
@@ -56,7 +53,6 @@ struct server_params {
|
|||||||
uint16_t display_id;
|
uint16_t display_id;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// init default values
|
// init default values
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ static void test_serialize_inject_keycode(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 10);
|
assert(size == 10);
|
||||||
|
|
||||||
@@ -34,13 +34,13 @@ static void test_serialize_inject_text(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 18);
|
assert(size == 16);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_TEXT,
|
CONTROL_MSG_TYPE_INJECT_TEXT,
|
||||||
0x00, 0x00, 0x00, 0x0d, // text length
|
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,17 +54,15 @@ 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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
||||||
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
expected[1] = 0x00;
|
expected[1] = 0x01;
|
||||||
expected[2] = 0x00;
|
expected[2] = 0x2c; // text length (16 bits)
|
||||||
expected[3] = 0x01;
|
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
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)));
|
||||||
}
|
}
|
||||||
@@ -90,7 +88,7 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 28);
|
assert(size == 28);
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 21);
|
assert(size == 21);
|
||||||
|
|
||||||
@@ -144,7 +142,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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@@ -159,7 +157,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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@@ -174,7 +172,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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@@ -189,7 +187,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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
@@ -202,18 +200,18 @@ 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,
|
||||||
.set_clipboard = {
|
.inject_text = {
|
||||||
.text = "hello, world!",
|
.text = "hello, world!",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 18);
|
assert(size == 16);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
0x00, 0x00, 0x00, 0x0d, // text length
|
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)));
|
||||||
@@ -227,7 +225,7 @@ static void test_serialize_set_screen_power_mode(void) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 2);
|
assert(size == 2);
|
||||||
|
|
||||||
@@ -243,7 +241,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_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,16 @@
|
|||||||
#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, 0x00, 0x00, 0x03, // text length
|
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 == 8);
|
assert(r == 6);
|
||||||
|
|
||||||
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
assert(msg.type == DEVICE_MSG_TYPE_CLIPBOARD);
|
||||||
assert(msg.clipboard.text);
|
assert(msg.clipboard.text);
|
||||||
@@ -23,30 +22,7 @@ static void test_deserialize_clipboard(void) {
|
|||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(void) {
|
int main(void) {
|
||||||
test_deserialize_clipboard();
|
test_deserialize_clipboard();
|
||||||
test_deserialize_clipboard_big();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.14',
|
version: '1.13',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
|||||||
@@ -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_r30.0.0-windows.zip \
|
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
|
||||||
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
|
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
|
||||||
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 16
|
versionCode 15
|
||||||
versionName "1.14"
|
versionName "1.13"
|
||||||
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.14
|
SCRCPY_VERSION_NAME=1.13
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
PLATFORM=${ANDROID_PLATFORM:-29}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CodecOption {
|
|
||||||
private String key;
|
|
||||||
private Object value;
|
|
||||||
|
|
||||||
public CodecOption(String key, Object value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<CodecOption> parse(String codecOptions) {
|
|
||||||
if ("-".equals(codecOptions)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CodecOption> result = new ArrayList<>();
|
|
||||||
|
|
||||||
boolean escape = false;
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
|
|
||||||
for (char c : codecOptions.toCharArray()) {
|
|
||||||
switch (c) {
|
|
||||||
case '\\':
|
|
||||||
if (escape) {
|
|
||||||
buf.append('\\');
|
|
||||||
escape = false;
|
|
||||||
} else {
|
|
||||||
escape = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ',':
|
|
||||||
if (escape) {
|
|
||||||
buf.append(',');
|
|
||||||
escape = false;
|
|
||||||
} else {
|
|
||||||
// This comma is a separator between codec options
|
|
||||||
String codecOption = buf.toString();
|
|
||||||
result.add(parseOption(codecOption));
|
|
||||||
// Clear buf
|
|
||||||
buf.setLength(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
buf.append(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.length() > 0) {
|
|
||||||
String codecOption = buf.toString();
|
|
||||||
result.add(parseOption(codecOption));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CodecOption parseOption(String option) {
|
|
||||||
int equalSignIndex = option.indexOf('=');
|
|
||||||
if (equalSignIndex == -1) {
|
|
||||||
throw new IllegalArgumentException("'=' expected");
|
|
||||||
}
|
|
||||||
String keyAndType = option.substring(0, equalSignIndex);
|
|
||||||
if (keyAndType.length() == 0) {
|
|
||||||
throw new IllegalArgumentException("Key may not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
String key;
|
|
||||||
String type;
|
|
||||||
|
|
||||||
int colonIndex = keyAndType.indexOf(':');
|
|
||||||
if (colonIndex != -1) {
|
|
||||||
key = keyAndType.substring(0, colonIndex);
|
|
||||||
type = keyAndType.substring(colonIndex + 1);
|
|
||||||
} else {
|
|
||||||
key = keyAndType;
|
|
||||||
type = "int"; // assume int by default
|
|
||||||
}
|
|
||||||
|
|
||||||
Object value;
|
|
||||||
String valueString = option.substring(equalSignIndex + 1);
|
|
||||||
switch (type) {
|
|
||||||
case "int":
|
|
||||||
value = Integer.parseInt(valueString);
|
|
||||||
break;
|
|
||||||
case "long":
|
|
||||||
value = Long.parseLong(valueString);
|
|
||||||
break;
|
|
||||||
case "float":
|
|
||||||
value = Float.parseFloat(valueString);
|
|
||||||
break;
|
|
||||||
case "string":
|
|
||||||
value = valueString;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid codec option type (int, long, float, str): " + type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CodecOption(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,13 +13,13 @@ public class ControlMessageReader {
|
|||||||
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;
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 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 = 1024;
|
||||||
|
|
||||||
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
|
||||||
@@ -102,17 +102,15 @@ public class ControlMessageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() {
|
private String parseString() {
|
||||||
if (buffer.remaining() < 4) {
|
if (buffer.remaining() < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = buffer.getInt();
|
int len = toUnsigned(buffer.getShort());
|
||||||
if (buffer.remaining() < len) {
|
if (buffer.remaining() < len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int position = buffer.position();
|
buffer.get(textBuffer, 0, len);
|
||||||
// Move the buffer position to consume the text
|
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||||
buffer.position(position + len);
|
|
||||||
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseInjectText() {
|
private ControlMessage parseInjectText() {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
|
import android.view.InputEvent;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -47,7 +50,7 @@ public class Controller {
|
|||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (!device.isScreenOn()) {
|
if (!device.isScreenOn()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
// After POWER is injected, the device is powered on asynchronously.
|
// After POWER is injected, the device is powered on asynchronously.
|
||||||
@@ -104,20 +107,14 @@ 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:
|
||||||
setClipboard(msg.getText());
|
device.setClipboardText(msg.getText());
|
||||||
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();
|
device.setScreenPowerMode(msg.getAction());
|
||||||
boolean setPowerModeOk = device.setScreenPowerMode(mode);
|
|
||||||
if (setPowerModeOk) {
|
|
||||||
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
@@ -129,7 +126,7 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
private boolean injectKeycode(int action, int keycode, int metaState) {
|
||||||
return device.injectKeyEvent(action, keycode, 0, metaState);
|
return injectKeyEvent(action, keycode, 0, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectChar(char c) {
|
||||||
@@ -140,7 +137,7 @@ public class Controller {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (KeyEvent event : events) {
|
for (KeyEvent event : events) {
|
||||||
if (!device.injectEvent(event)) {
|
if (!injectEvent(event)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +193,7 @@ public class Controller {
|
|||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
return device.injectEvent(event);
|
return injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
||||||
@@ -219,21 +216,26 @@ public class Controller {
|
|||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
return device.injectEvent(event);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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_POWER;
|
||||||
return device.injectKeycode(keycode);
|
return injectKeycode(keycode);
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setClipboard(String text) {
|
|
||||||
boolean ok = device.setClipboardText(text);
|
|
||||||
if (ok) {
|
|
||||||
Ln.i("Device clipboard set");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,8 @@ import android.content.IOnPrimaryClipChangedListener;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.view.IRotationWatcher;
|
import android.view.IRotationWatcher;
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
import android.view.KeyCharacterMap;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public final class Device {
|
public final class Device {
|
||||||
|
|
||||||
@@ -37,7 +31,6 @@ public final class Device {
|
|||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
private ClipboardListener clipboardListener;
|
private ClipboardListener clipboardListener;
|
||||||
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical display identifier
|
* Logical display identifier
|
||||||
@@ -83,10 +76,6 @@ public final class Device {
|
|||||||
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void dispatchPrimaryClipChanged() {
|
public void dispatchPrimaryClipChanged() {
|
||||||
if (isSettingClipboard.get()) {
|
|
||||||
// This is a notification for the change we are currently applying, ignore it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
synchronized (Device.this) {
|
synchronized (Device.this) {
|
||||||
if (clipboardListener != null) {
|
if (clipboardListener != null) {
|
||||||
String text = getClipboardText();
|
String text = getClipboardText();
|
||||||
@@ -151,7 +140,7 @@ public final class Device {
|
|||||||
return supportsInputEvents;
|
return supportsInputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||||
if (!supportsInputEvents()) {
|
if (!supportsInputEvents()) {
|
||||||
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||||
}
|
}
|
||||||
@@ -163,21 +152,6 @@ public final class Device {
|
|||||||
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
|
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() {
|
public boolean isScreenOn() {
|
||||||
return serviceManager.getPowerManager().isScreenOn();
|
return serviceManager.getPowerManager().isScreenOn();
|
||||||
}
|
}
|
||||||
@@ -206,31 +180,26 @@ public final class Device {
|
|||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setClipboardText(String text) {
|
public void 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);
|
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||||
isSettingClipboard.set(false);
|
if (ok) {
|
||||||
return ok;
|
Ln.i("Device clipboard set");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
|
||||||
*/
|
*/
|
||||||
public boolean setScreenPowerMode(int mode) {
|
public void 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");
|
||||||
return false;
|
return;
|
||||||
|
}
|
||||||
|
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode);
|
||||||
|
if (ok) {
|
||||||
|
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||||
}
|
}
|
||||||
return SurfaceControl.setDisplayPowerMode(d, mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
public class DeviceMessageWriter {
|
public class DeviceMessageWriter {
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
private static final int MAX_EVENT_SIZE = CLIPBOARD_TEXT_MAX_LENGTH + 3;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
private final byte[] rawBuffer = new byte[MAX_EVENT_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.putInt(len);
|
buffer.putShort((short) 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,25 +15,14 @@ public final class Ln {
|
|||||||
DEBUG, INFO, WARN, ERROR
|
DEBUG, INFO, WARN, ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Level threshold = Level.INFO;
|
private static final Level THRESHOLD = BuildConfig.DEBUG ? Level.DEBUG : Level.INFO;
|
||||||
|
|
||||||
private Ln() {
|
private Ln() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the log level.
|
|
||||||
* <p>
|
|
||||||
* Must be called before starting any new thread.
|
|
||||||
*
|
|
||||||
* @param level the log level
|
|
||||||
*/
|
|
||||||
public static void initLogLevel(Level level) {
|
|
||||||
threshold = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isEnabled(Level level) {
|
public static boolean isEnabled(Level level) {
|
||||||
return level.ordinal() >= threshold.ordinal();
|
return level.ordinal() >= THRESHOLD.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void d(String message) {
|
public static void d(String message) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
public class Options {
|
public class Options {
|
||||||
private Ln.Level logLevel;
|
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
@@ -15,15 +14,6 @@ public class Options {
|
|||||||
private int displayId;
|
private int displayId;
|
||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private String codecOptions;
|
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
|
||||||
return logLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLogLevel(Ln.Level logLevel) {
|
|
||||||
this.logLevel = logLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxSize() {
|
public int getMaxSize() {
|
||||||
return maxSize;
|
return maxSize;
|
||||||
@@ -112,12 +102,4 @@ public class Options {
|
|||||||
public void setStayAwake(boolean stayAwake) {
|
public void setStayAwake(boolean stayAwake) {
|
||||||
this.stayAwake = stayAwake;
|
this.stayAwake = stayAwake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCodecOptions() {
|
|
||||||
return codecOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCodecOptions(String codecOptions) {
|
|
||||||
this.codecOptions = codecOptions;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import android.view.Surface;
|
|||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class ScreenEncoder implements Device.RotationListener {
|
public class ScreenEncoder implements Device.RotationListener {
|
||||||
@@ -26,17 +25,15 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
||||||
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
||||||
|
|
||||||
private List<CodecOption> codecOptions;
|
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
private boolean sendFrameMeta;
|
private boolean sendFrameMeta;
|
||||||
private long ptsOrigin;
|
private long ptsOrigin;
|
||||||
|
|
||||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
this.maxFps = maxFps;
|
this.maxFps = maxFps;
|
||||||
this.codecOptions = codecOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,7 +61,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {
|
private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||||
MediaFormat format = createFormat(bitRate, maxFps, codecOptions);
|
MediaFormat format = createFormat(bitRate, maxFps);
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
boolean alive;
|
boolean alive;
|
||||||
try {
|
try {
|
||||||
@@ -154,24 +151,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
|
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
private static MediaFormat createFormat(int bitRate, int maxFps) {
|
||||||
String key = codecOption.getKey();
|
|
||||||
Object value = codecOption.getValue();
|
|
||||||
|
|
||||||
if (value instanceof Integer) {
|
|
||||||
format.setInteger(key, (Integer) value);
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
format.setLong(key, (Long) value);
|
|
||||||
} else if (value instanceof Float) {
|
|
||||||
format.setFloat(key, (Float) value);
|
|
||||||
} else if (value instanceof String) {
|
|
||||||
format.setString(key, (String) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ln.d("Codec option set: " + key + " (" + value.getClass().getSimpleName() + ") = " + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaFormat createFormat(int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
|
||||||
MediaFormat format = new MediaFormat();
|
MediaFormat format = new MediaFormat();
|
||||||
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
|
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
|
||||||
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
||||||
@@ -187,13 +167,6 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
// <https://github.com/Genymobile/scrcpy/issues/488#issuecomment-567321437>
|
// <https://github.com/Genymobile/scrcpy/issues/488#issuecomment-567321437>
|
||||||
format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps);
|
format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecOptions != null) {
|
|
||||||
for (CodecOption option : codecOptions) {
|
|
||||||
setCodecOption(format, option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import android.os.BatteryManager;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public final class Server {
|
public final class Server {
|
||||||
|
|
||||||
@@ -21,7 +19,6 @@ public final class Server {
|
|||||||
private static void scrcpy(Options options) throws IOException {
|
private static void scrcpy(Options options) throws IOException {
|
||||||
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
||||||
final Device device = new Device(options);
|
final Device device = new Device(options);
|
||||||
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
|
|
||||||
|
|
||||||
boolean mustDisableShowTouchesOnCleanUp = false;
|
boolean mustDisableShowTouchesOnCleanUp = false;
|
||||||
int restoreStayOn = -1;
|
int restoreStayOn = -1;
|
||||||
@@ -52,9 +49,8 @@ public final class Server {
|
|||||||
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn);
|
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn);
|
||||||
|
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions);
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
|
||||||
|
|
||||||
if (options.getControl()) {
|
if (options.getControl()) {
|
||||||
final Controller controller = new Controller(device, connection);
|
final Controller controller = new Controller(device, connection);
|
||||||
@@ -120,53 +116,47 @@ public final class Server {
|
|||||||
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
|
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
final int expectedParameters = 14;
|
final int expectedParameters = 12;
|
||||||
if (args.length != expectedParameters) {
|
if (args.length != expectedParameters) {
|
||||||
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
|
|
||||||
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH));
|
int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8
|
||||||
options.setLogLevel(level);
|
|
||||||
|
|
||||||
int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8
|
|
||||||
options.setMaxSize(maxSize);
|
options.setMaxSize(maxSize);
|
||||||
|
|
||||||
int bitRate = Integer.parseInt(args[3]);
|
int bitRate = Integer.parseInt(args[2]);
|
||||||
options.setBitRate(bitRate);
|
options.setBitRate(bitRate);
|
||||||
|
|
||||||
int maxFps = Integer.parseInt(args[4]);
|
int maxFps = Integer.parseInt(args[3]);
|
||||||
options.setMaxFps(maxFps);
|
options.setMaxFps(maxFps);
|
||||||
|
|
||||||
int lockedVideoOrientation = Integer.parseInt(args[5]);
|
int lockedVideoOrientation = Integer.parseInt(args[4]);
|
||||||
options.setLockedVideoOrientation(lockedVideoOrientation);
|
options.setLockedVideoOrientation(lockedVideoOrientation);
|
||||||
|
|
||||||
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
||||||
boolean tunnelForward = Boolean.parseBoolean(args[6]);
|
boolean tunnelForward = Boolean.parseBoolean(args[5]);
|
||||||
options.setTunnelForward(tunnelForward);
|
options.setTunnelForward(tunnelForward);
|
||||||
|
|
||||||
Rect crop = parseCrop(args[7]);
|
Rect crop = parseCrop(args[6]);
|
||||||
options.setCrop(crop);
|
options.setCrop(crop);
|
||||||
|
|
||||||
boolean sendFrameMeta = Boolean.parseBoolean(args[8]);
|
boolean sendFrameMeta = Boolean.parseBoolean(args[7]);
|
||||||
options.setSendFrameMeta(sendFrameMeta);
|
options.setSendFrameMeta(sendFrameMeta);
|
||||||
|
|
||||||
boolean control = Boolean.parseBoolean(args[9]);
|
boolean control = Boolean.parseBoolean(args[8]);
|
||||||
options.setControl(control);
|
options.setControl(control);
|
||||||
|
|
||||||
int displayId = Integer.parseInt(args[10]);
|
int displayId = Integer.parseInt(args[9]);
|
||||||
options.setDisplayId(displayId);
|
options.setDisplayId(displayId);
|
||||||
|
|
||||||
boolean showTouches = Boolean.parseBoolean(args[11]);
|
boolean showTouches = Boolean.parseBoolean(args[10]);
|
||||||
options.setShowTouches(showTouches);
|
options.setShowTouches(showTouches);
|
||||||
|
|
||||||
boolean stayAwake = Boolean.parseBoolean(args[12]);
|
boolean stayAwake = Boolean.parseBoolean(args[11]);
|
||||||
options.setStayAwake(stayAwake);
|
options.setStayAwake(stayAwake);
|
||||||
|
|
||||||
String codecOptions = args[13];
|
|
||||||
options.setCodecOptions(codecOptions);
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,9 +209,6 @@ public final class Server {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Options options = createOptions(args);
|
Options options = createOptions(args);
|
||||||
|
|
||||||
Ln.initLogLevel(options.getLogLevel());
|
|
||||||
|
|
||||||
scrcpy(options);
|
scrcpy(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CodecOptionsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIntegerImplicit() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key=5");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertEquals(5, option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInteger() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key:int=5");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Integer);
|
|
||||||
Assert.assertEquals(5, option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLong() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key:long=5");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Long);
|
|
||||||
Assert.assertEquals(5L, option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFloat() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key:float=4.5");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Float);
|
|
||||||
Assert.assertEquals(4.5f, option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testString() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=some_value");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof String);
|
|
||||||
Assert.assertEquals("some_value", option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStringEscaped() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("some_key:string=warning\\,this_is_not=a_new_key");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("some_key", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof String);
|
|
||||||
Assert.assertEquals("warning,this_is_not=a_new_key", option.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testList() {
|
|
||||||
List<CodecOption> codecOptions = CodecOption.parse("a=1,b:int=2,c:long=3,d:float=4.5,e:string=a\\,b=c");
|
|
||||||
|
|
||||||
Assert.assertEquals(5, codecOptions.size());
|
|
||||||
|
|
||||||
CodecOption option;
|
|
||||||
|
|
||||||
option = codecOptions.get(0);
|
|
||||||
Assert.assertEquals("a", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Integer);
|
|
||||||
Assert.assertEquals(1, option.getValue());
|
|
||||||
|
|
||||||
option = codecOptions.get(1);
|
|
||||||
Assert.assertEquals("b", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Integer);
|
|
||||||
Assert.assertEquals(2, option.getValue());
|
|
||||||
|
|
||||||
option = codecOptions.get(2);
|
|
||||||
Assert.assertEquals("c", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Long);
|
|
||||||
Assert.assertEquals(3L, option.getValue());
|
|
||||||
|
|
||||||
option = codecOptions.get(3);
|
|
||||||
Assert.assertEquals("d", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof Float);
|
|
||||||
Assert.assertEquals(4.5f, option.getValue());
|
|
||||||
|
|
||||||
option = codecOptions.get(4);
|
|
||||||
Assert.assertEquals("e", option.getKey());
|
|
||||||
Assert.assertTrue(option.getValue() instanceof String);
|
|
||||||
Assert.assertEquals("a,b=c", option.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,7 +48,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.writeInt(text.length);
|
dos.writeShort(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@@ -68,7 +68,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.writeInt(text.length);
|
dos.writeShort(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ public class ControlMessageReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeInt(text.length);
|
dos.writeShort(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@@ -229,30 +229,6 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseBigSetClipboardEvent() throws IOException {
|
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
|
||||||
|
|
||||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
|
||||||
Arrays.fill(rawText, (byte) 'a');
|
|
||||||
String text = new String(rawText, 0, rawText.length);
|
|
||||||
|
|
||||||
dos.writeInt(rawText.length);
|
|
||||||
dos.write(rawText);
|
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlMessage event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
|
||||||
Assert.assertEquals(text, event.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseSetScreenPowerMode() throws IOException {
|
public void testParseSetScreenPowerMode() throws IOException {
|
||||||
ControlMessageReader reader = new ControlMessageReader();
|
ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
|||||||
@@ -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.writeInt(data.length);
|
dos.writeShort(data.length);
|
||||||
dos.write(data);
|
dos.write(data);
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
byte[] expected = bos.toByteArray();
|
||||||
|
|||||||
Reference in New Issue
Block a user