Compare commits

...

36 Commits

Author SHA1 Message Date
Romain Vimont
a01e9c2812 Avoid additional copy on Java text parsing
Directly pass the buffer range to the String constructor.
2020-07-09 12:28:42 +02:00
Romain Vimont
80f5a7c43d Update copy-paste section in README
Update documentation regarding the recent copy-paste changes.
2020-07-09 12:28:42 +02:00
Romain Vimont
ddb36e3436 Forward copy-cut shortcuts
For convenience, forward RCtrl+c (or Cmd+c) and and RCtrl+x (or Cmd+x)
as LCtrl+c and LCtrl+x to the device.

This allows to use the "natural" keys for copy-paste (especially on
macOS).
2020-07-09 12:28:42 +02:00
Romain Vimont
cac1765091 Change "resize to fit" shortcut to RCtrl+w
For convenience, RCtrl+x (and Cmd+x) will be used for "cut text to
clipboard", in addition to LCtrl+x.
2020-07-09 12:28:42 +02:00
Romain Vimont
f5a14b285b Accept Cmd for shortcuts on macOS
For convenience (and to keep the existing behavior), also accept
shortcuts using Cmd instead of RCtrl.
2020-07-09 12:28:42 +02:00
Romain Vimont
65edae0ca6 Reformulate RCtrl+v description
RCtrl+v is the only scrcpy shortcut related to copy-paste. Since it's
not a real "paste", reformulate to indicate that it injects the
clipboard content as text events.
2020-07-09 12:28:42 +02:00
Romain Vimont
e4a0fada10 Remove RCtrl+c copy shortcut
Now that Ctrl+c is forwarded to the device, and that every device
clipboard change is automatically synchronized to the computer, RCtrl+c
is useless.
2020-07-09 12:28:42 +02:00
Romain Vimont
8a037e3d9b Remove RCtrl+Shift+v paste shortcut
Now that LCtrl+v synchronize the computer clipboard to the device
clipboard, RCtrl+Shift+v is not needed anymore.

Note: RCtrl+v is kept to send the computer clipboard content as a
sequence of keys.
2020-07-09 12:28:42 +02:00
Romain Vimont
b3aa88c751 Set device clipboard only if necessary
Do not explicitly set the clipboard text if it already contains the
expected content. This avoids possible copy-paste loops between the
computer and the device.
2020-07-09 12:28:37 +02:00
Romain Vimont
b9602e56d9 Synchronize clipboard on Ctrl+v
Pressing Ctrl+v on the device will typically paste the clipboard
content.

Before sending the key event, synchronize the computer clipboard to the
device clipboard to allow seamless copy-paste.
2020-06-02 18:27:24 +02:00
Romain Vimont
0c01ac34b4 Simplify PASTE option for "set clipboard"
When the client requests to set the clipboard, it may request to press
the PASTE key in addition. To be a bit generic, it was stored as a flag
in ControlMessage.java.

But flags suggest that it represents a bitwise union, which could become
confusing when we add a COPY option for getting the clipboard.
2020-06-02 18:27:24 +02:00
Romain Vimont
82295ef4a7 Forward Shift to the device
This allows to select text using Shift+(arrow keys).

Fixes #942 <https://github.com/Genymobile/scrcpy/issues/942>
2020-06-02 18:27:24 +02:00
Romain Vimont
e62aca59fe Forward LCtrl to the device
Now that only RCtrl is used for scrcpy shortcuts, LCtrl can be forwarded
to the device.

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-06-02 18:27:24 +02:00
Romain Vimont
fbd2d0bf3e Only use RCtrl for scrcpy shortcuts
This paves the way to forward LCtrl to the device.
2020-06-02 18:27:24 +02:00
Romain Vimont
6e1069a822 Configure log level for application only
Do not expose internal SDL logs to users.

Fixes #1441 <https://github.com/Genymobile/scrcpy/issues/1441>
2020-06-02 18:27:23 +02:00
Romain Vimont
c4323df976 Fix incorrect log return value
The function must return a SDL_LogPriority, but returned an enum
sc_log_level.

(It was harmless because this specific return should never happen, as
asserted.)
2020-06-02 18:27:14 +02:00
Romain Vimont
8ff07e0c88 Git-ignore release directories 2020-06-02 18:25:22 +02:00
Romain Vimont
e4efd75766 Avoid repetition for some shortcuts
Keeping the key pressed generate "repeat" events. It does not make sense
to repeat the event for rotation or turn screen off.
2020-05-29 22:02:41 +02:00
Romain Vimont
0e4a6f462b Mention stay awake limitation
The "stay awake" feature only works when the device is plugged in.

Refs #1445 <https://github.com/Genymobile/scrcpy/issues/1445>
2020-05-28 23:07:28 +02:00
Romain Vimont
8b73c90427 Mention how to turn the screen on in README
Now that Ctrl+Shift+o has been reactivated, mention it in the "turn
screen off" section.
2020-05-27 19:32:02 +02:00
Romain Vimont
ef91ab2841 Update links to v1.14 in README and BUILD 2020-05-27 19:31:12 +02:00
Romain Vimont
44fa4a090e Bump version to 1.14 2020-05-27 18:26:46 +02:00
Romain Vimont
dcde578a50 Reactivate "turn device screen on" feature
This reverts commit 8c8649cfcd.

I cannot reproduce the issue with Ctrl+Shift+o on any device, so in
practice it works, it's too bad to remove the feature for a random bug
on some Android versions on some devices.
2020-05-27 18:26:46 +02:00
Romain Vimont
93a5c5149d Push clipboard text only if not null
In practice, it does not change anything (it just avoids a spurious
wake-up), but semantically, it makes no sense to call
pushClipboardText() with a null value.
2020-05-27 18:26:39 +02:00
Romain Vimont
2ca8318b9d Improve manpage formatting
Add a new line to avoid unwanted text justification
2020-05-27 12:39:42 +02:00
Romain Vimont
d499ee53c9 Initialize a default log level
Clean up has been broken by 3df63c579d.

The verbosity was correctly initialized for the Server process, but not
for the CleanUp process.

To avoid the problem, initialize a default log level.
2020-05-27 12:05:29 +02:00
Romain Vimont
8f619f337b Upgrade platform-tools (30.0.0) for Windows
Include the latest version of adb in Windows releases.
2020-05-26 19:21:05 +02:00
Romain Vimont
fc1dec0270 Paste on "set clipboard" if possible
Ctrl+Shift+v synchronizes the computer clipboard to the Android device
clipboard. This feature had been added to provide a way to copy UTF-8
text from the computer to the device.

To make such a paste more straightforward, if the device runs at least
Android 7, also send a PASTE keycode to paste immediately.

<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_PASTE>

Fixes #786 <https://github.com/Genymobile/scrcpy/issues/786>
2020-05-25 20:59:21 +02:00
Romain Vimont
274b591d18 Fix union typo
The "set clipboard" event used the wrong union type to store its text.

In practice, it worked because both are at the same offset.
2020-05-25 18:41:05 +02:00
Romain Vimont
4bbabfb4ef Move injection methods to Device
Only the main injection method was exposed on Device, the convenience
methods were implemented in Controller.

For consistency, move them all to the Device class.
2020-05-25 03:28:33 +02:00
Romain Vimont
ffc57512b3 Avoid clipboard synchronization loop
The Android device listens for clipboard changes to synchronize with the
computer clipboard.

However, if the change comes from scrcpy (for example via Ctrl+Shift+v),
do not notify the change.
2020-05-25 03:28:33 +02:00
Romain Vimont
c7a33fac36 Log actions on the caller side
Some actions are exposed by the Device class, but logging success should
be done by the caller.
2020-05-25 03:28:33 +02:00
Romain Vimont
81573d81a0 Pass a Locale to toUpperCase()
Make lint happy.
2020-05-25 03:28:15 +02:00
Romain Vimont
5c2cf88a1d Rename THRESHOLD to threshold
Since the field is not final anymore, lint expects the name not to be
capitalized.
2020-05-25 03:22:07 +02:00
Romain Vimont
8f46e18426 Add --force-adb-forward
Add a command-line option to force "adb forward", without attempting
"adb reverse" first.

This is especially useful for using SSH tunnels without enabling remote
port forwarding.
2020-05-24 23:28:31 +02:00
Romain Vimont
ee3882f8be Fix typo in manpage 2020-05-24 23:14:30 +02:00
26 changed files with 353 additions and 235 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,8 @@
build/
/dist/
/build-*/
/build_*/
/release-*/
.idea/
.gradle/
/x/

View File

@@ -249,10 +249,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.13`][direct-scrcpy-server]
_(SHA-256: 5fee64ca1ccdc2f38550f31f5353c66de3de30c2e929a964e30fa2d005d5f885)_
- [`scrcpy-server-v1.14`][direct-scrcpy-server]
_(SHA-256: 1d1b18a2b80e956771fd63b99b414d2d028713a8f12ddfa5a369709ad4295620)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-server-v1.13
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-server-v1.14
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

110
README.md
View File

@@ -1,4 +1,4 @@
# scrcpy (v1.13)
# scrcpy (v1.14)
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@@ -69,10 +69,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.13.zip`][direct-win64]
_(SHA-256: 806aafc00d4db01513193addaa24f47858893ba5efe75770bfef6ae1ea987d27)_
- [`scrcpy-win64-v1.14.zip`][direct-win64]
_(SHA-256: 2be9139e46e29cf2f5f695848bb2b75a543b8f38be1133257dc5068252abc25f)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.13/scrcpy-win64-v1.13.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.14/scrcpy-win64-v1.14.zip
It is also available in [Chocolatey]:
@@ -289,6 +289,22 @@ From another terminal:
scrcpy
```
To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`):
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal:
```bash
scrcpy --force-adb-forwrad
```
Like for wireless connections, it may be useful to reduce quality:
```
@@ -338,7 +354,7 @@ scrcpy --fullscreen
scrcpy -f # short version
```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
#### Rotation
@@ -354,18 +370,18 @@ Possibles values are:
- `2`: 180 degrees
- `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
`Ctrl`+`→` _(right)_.
The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
`RCtrl`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations:
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
current running app may refuse, if it does support the requested
orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the
recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
affects only the display, not the recording.
- `--rotation` (or `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
This affects only the display, not the recording.
### Other mirroring options
@@ -401,7 +417,7 @@ The secondary display may only be controlled if the device runs at least Android
#### Stay awake
To prevent the device to sleep after some delay:
To prevent the device to sleep after some delay when the device is plugged in:
```bash
scrcpy --stay-awake
@@ -421,9 +437,9 @@ scrcpy --turn-screen-off
scrcpy -S
```
Or by pressing `Ctrl`+`o` at any time.
Or by pressing `RCtrl`+`o` at any time.
To turn it back on, press `POWER` (or `Ctrl`+`p`).
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
It can be useful to also prevent the device to sleep:
@@ -467,23 +483,22 @@ Note that it only shows _physical_ touches (with the finger on the device).
#### Rotate device screen
Press `Ctrl`+`r` to switch between portrait and landscape modes.
Press `RCtrl`+`r` to switch between portrait and landscape modes.
Note that it rotates only if the application in foreground supports the
requested orientation.
#### Copy-paste
It is possible to synchronize clipboards between the computer and the device, in
both directions:
Any time the Android clipboard changes, it is automatically synchronized to the
computer clipboard.
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard;
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
sequence of text event. Even if it can break non-ASCII content, this is
sometimes necessary when pasting directly is not possible.
Moreover, any time the Android clipboard changes, it is automatically
synchronized to the computer clipboard.
#### Text injection preference
@@ -543,29 +558,34 @@ Also see [issue #14].
## Shortcuts
| Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s`
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`` _(up)_
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p`
| Power on | _Right-click²_ | _Right-click²_
| Turn device screen off (keep mirroring)| `Ctrl`+`o` | `Cmd`+`o`
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r`
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n`
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n`
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c`
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v`
| Copy computer clipboard to device | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
device).
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
system).
| Action | Shortcut
| ------------------------------------------- |:-----------------------------
| Switch fullscreen mode | `RCtrl`+`f`
| Rotate display left | `RCtrl`+``
| Rotate display right | `RCtrl`+``
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `RCtrl`+`s`
| Click on `MENU` | `RCtrl`+`m`
| Click on `VOLUME_UP` | `RCtrl`+`` _(up)_
| Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
| Click on `POWER` | `RCtrl`+`p`
| Power on | _Right-click²_
| Turn device screen off (keep mirroring) | `RCtrl`+`o`
| Turn device screen on | `RCtrl`+`Shift`+`o`
| Rotate device screen | `RCtrl`+`r`
| Expand notification panel | `RCtrl`+`n`
| Collapse notification panel | `RCtrl`+`Shift`+`n`
| Inject computer clipboard text | `RCtrl`+`v`
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

@@ -52,6 +52,10 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0.
.TP
.B \-\-force\-adb\-forward
Do not attempt to use "adb reverse" to connect to the device.
.TP
.B \-f, \-\-fullscreen
Start in fullscreen.
@@ -125,6 +129,7 @@ Force recording format (either mp4 or mkv).
Request SDL to use the given render driver (this is just a hint).
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE
@@ -146,7 +151,7 @@ Turn the device screen off immediately.
.TP
.B \-t, \-\-show\-touches
Enable "show touches" on start, restore the initial value on exit..
Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
@@ -162,7 +167,7 @@ Default is "info" for release builds, "debug" for debug builds.
.TP
.B \-w, \-\-stay-awake
Keep the device on while scrcpy is running.
Keep the device on while scrcpy is running, when the device is plugged in.
.TP
.B \-\-window\-borderless
@@ -198,52 +203,54 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
.TP
.B Ctrl+f
.B RCtrl+f
Switch fullscreen mode
.TP
.B Ctrl+Left
.B RCtrl+Left
Rotate display left
.TP
.B Ctrl+Right
.B RCtrl+Right
Rotate display right
.TP
.B Ctrl+g
.B RCtrl+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
.B RCtrl+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
.B RCtrl+h, Home, Middle\-click
Click on HOME
.TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B Ctrl+s
.B RCtrl+s
Click on APP_SWITCH
.TP
.B Ctrl+m
.B RCtrl+m
Click on MENU
.TP
.B Ctrl+Up
.B RCtrl+Up
Click on VOLUME_UP
.TP
.B Ctrl+Down
.B RCtrl+Down
Click on VOLUME_DOWN
.TP
.B Ctrl+p
.B RCtrl+p
Click on POWER (turn screen on/off)
.TP
@@ -251,35 +258,31 @@ Click on POWER (turn screen on/off)
Turn screen on
.TP
.B Ctrl+o
.B RCtrl+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+r
.B RCtrl+Shift+o
Turn device screen on
.TP
.B RCtrl+r
Rotate device screen
.TP
.B Ctrl+n
.B RCtrl+n
Expand notification panel
.TP
.B Ctrl+Shift+n
.B RCtrl+Shift+n
Collapse notification panel
.TP
.B Ctrl+c
Copy device clipboard to computer
.B RCtrl+v
Inject computer clipboard text
.TP
.B Ctrl+v
Paste computer clipboard to device
.TP
.B Ctrl+Shift+v
Copy computer clipboard to device
.TP
.B Ctrl+i
.B RCtrl+i
Enable/disable FPS counter (print frames/second in logs)
.TP

View File

@@ -12,11 +12,6 @@
void
scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
#endif
fprintf(stderr,
"Usage: %s [options]\n"
"\n"
@@ -54,6 +49,10 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" Default is 0.\n"
"\n"
" --force-adb-forward\n"
" Do not attempt to use \"adb reverse\" to connect to the\n"
" the device.\n"
"\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
@@ -155,7 +154,8 @@ scrcpy_print_usage(const char *arg0) {
#endif
"\n"
" -w, --stay-awake\n"
" Keep the device on while scrcpy is running.\n"
" Keep the device on while scrcpy is running, when the device\n"
" is plugged in.\n"
"\n"
" --window-borderless\n"
" Disable window decorations (display borderless window).\n"
@@ -181,71 +181,71 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\n"
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
" the device.\n"
"\n"
" RCtrl+f\n"
" Switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+Left\n"
" RCtrl+Left\n"
" Rotate display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" RCtrl+Right\n"
" Rotate display right\n"
"\n"
" " CTRL_OR_CMD "+g\n"
" RCtrl+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" " CTRL_OR_CMD "+x\n"
" RCtrl+w\n"
" Double-click on black borders\n"
" Resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" RCtrl+h\n"
" Middle-click\n"
" Click on HOME\n"
"\n"
" " CTRL_OR_CMD "+b\n"
" " CTRL_OR_CMD "+Backspace\n"
" RCtrl+b\n"
" RCtrl+Backspace\n"
" Right-click (when screen is on)\n"
" Click on BACK\n"
"\n"
" " CTRL_OR_CMD "+s\n"
" RCtrl+s\n"
" Click on APP_SWITCH\n"
"\n"
" Ctrl+m\n"
" RCtrl+m\n"
" Click on MENU\n"
"\n"
" " CTRL_OR_CMD "+Up\n"
" RCtrl+Up\n"
" Click on VOLUME_UP\n"
"\n"
" " CTRL_OR_CMD "+Down\n"
" RCtrl+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" " CTRL_OR_CMD "+p\n"
" RCtrl+p\n"
" Click on POWER (turn screen on/off)\n"
"\n"
" Right-click (when screen is off)\n"
" Power on\n"
"\n"
" " CTRL_OR_CMD "+o\n"
" RCtrl+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" RCtrl+Shift+o\n"
" Turn device screen on\n"
"\n"
" RCtrl+r\n"
" Rotate device screen\n"
"\n"
" " CTRL_OR_CMD "+n\n"
" RCtrl+n\n"
" Expand notification panel\n"
"\n"
" " CTRL_OR_CMD "+Shift+n\n"
" RCtrl+Shift+n\n"
" Collapse notification panel\n"
"\n"
" " CTRL_OR_CMD "+c\n"
" Copy device clipboard to computer\n"
" RCtrl+v\n"
" Inject computer clipboard text\n"
"\n"
" " CTRL_OR_CMD "+v\n"
" Paste computer clipboard to device\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device\n"
"\n"
" " CTRL_OR_CMD "+i\n"
" RCtrl+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
@@ -516,6 +516,7 @@ guess_record_format(const char *filename) {
#define OPT_RENDER_DRIVER 1016
#define OPT_NO_MIPMAPS 1017
#define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -525,6 +526,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS},
{"crop", required_argument, NULL, OPT_CROP},
{"display", required_argument, NULL, OPT_DISPLAY_ID},
{"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"lock-video-orientation", required_argument, NULL,
@@ -701,6 +704,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_CODEC_OPTIONS:
opts->codec_options = optarg;
break;
case OPT_FORCE_ADB_FORWARD:
opts->force_adb_forward = true;
break;
default:
// getopt prints the error message on stderr
return false;

View File

@@ -67,10 +67,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
size_t len = write_string(msg->inject_text.text,
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[1]);
return 1 + len;
&buf[2]);
return 2 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;

View File

@@ -11,9 +11,9 @@
#include "common.h"
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
(4 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1);
@@ -62,6 +62,7 @@ struct control_msg {
} inject_scroll_event;
struct {
char *text; // owned, to be freed by SDL_free()
bool paste;
} set_clipboard;
struct {
enum screen_power_mode mode;

View File

@@ -92,6 +92,9 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
@@ -111,7 +114,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
if (prefer_text) {
if (prefer_text && !(mod & KMOD_LCTRL)) {
// do not forward alpha and space key events
return false;
}

View File

@@ -102,17 +102,7 @@ collapse_notification_panel(struct controller *controller) {
}
static void
request_device_clipboard(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request device clipboard");
}
}
static void
set_device_clipboard(struct controller *controller) {
set_device_clipboard(struct controller *controller, bool paste) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -127,6 +117,7 @@ set_device_clipboard(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
@@ -251,6 +242,25 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return true;
}
static void
inject_as_ctrl(struct input_manager *im, const SDL_KeyboardEvent *event) {
struct control_msg msg;
if (!convert_input_key(event, &msg, false)) {
return;
}
// Disable RCtrl and Meta
msg.inject_keycode.metastate &=
~(AMETA_CTRL_RIGHT_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
// Enable LCtrl
msg.inject_keycode.metastate |= AMETA_CTRL_LEFT_ON;
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event,
@@ -258,134 +268,127 @@ input_manager_process_key(struct input_manager *im,
// control: indicates the state of the command-line option --no-control
// ctrl: the Ctrl key
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool lctrl = event->keysym.mod & KMOD_LCTRL;
bool rctrl = event->keysym.mod & KMOD_RCTRL;
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
// use Cmd on macOS, Ctrl on other platforms
bool shortcut_key = rctrl;
#ifdef __APPLE__
bool cmd = !ctrl && meta;
shortcut_key |= meta;
#else
if (meta) {
// no shortcuts involve Meta on platforms other than macOS, and it must
// not be forwarded to the device
// No shortcut involve Meta, and it is not forwarded to the device
return;
}
bool cmd = ctrl; // && !meta, already guaranteed
#endif
if (alt) {
// no shortcuts involve Alt, and it must not be forwarded to the device
// No shortcuts involve Alt, and it is not forwarded to the device
return;
}
struct controller *controller = im->controller;
// capture all Ctrl events
if (ctrl || cmd) {
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
// Capture all RCtrl events
if (shortcut_key) {
int action = down ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
switch (keycode) {
case SDLK_h:
// Ctrl+h on all platform, since Cmd+h is already captured by
// the system on macOS to hide the window
if (control && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_home(controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_back(controller, action);
}
return;
case SDLK_s:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_app_switch(controller, action);
}
return;
case SDLK_m:
// Ctrl+m on all platform, since Cmd+m is already captured by
// the system on macOS to minimize the window
if (control && ctrl && !meta && !shift && !repeat) {
if (control && !shift && !repeat) {
action_menu(controller, action);
}
return;
case SDLK_p:
if (control && cmd && !shift && !repeat) {
if (control && !shift && !repeat) {
action_power(controller, action);
}
return;
case SDLK_o:
if (control && cmd && !shift && down) {
set_screen_power_mode(controller, SCREEN_POWER_MODE_OFF);
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
}
return;
case SDLK_DOWN:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (control && cmd && !shift) {
if (control && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
return;
case SDLK_LEFT:
if (cmd && !shift && down) {
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && down) {
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c:
if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller);
}
return;
case SDLK_v:
if (control && cmd && !repeat && down) {
if (shift) {
// store the text in the device clipboard
set_device_clipboard(controller);
} else {
// inject the text as input events
clipboard_paste(controller);
}
if (control && !shift && !repeat && down) {
// Inject the text as input events
clipboard_paste(controller);
}
return;
case SDLK_f:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen);
}
return;
case SDLK_x:
if (!shift && cmd && !repeat && down) {
case SDLK_w:
if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
if (!shift && cmd && !repeat && down) {
if (!shift && !repeat && down) {
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_notification_panel(controller);
} else {
@@ -394,10 +397,19 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_r:
if (control && cmd && !shift && !repeat && down) {
if (control && !shift && !repeat && down) {
rotate_device(controller);
}
return;
case SDLK_c:
case SDLK_x:
if (control && !shift) {
// For convenience, forward shortcut_key+c and
// shortcut_key+x as Ctrl+c and Ctrl+x (typically "copy" and
// "cut", but not always) to the device
inject_as_ctrl(im, event);
}
return;
}
return;
@@ -407,6 +419,12 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (lctrl && !shift && keycode == SDLK_v && down) {
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+V, to allow seamless copy-paste.
set_device_clipboard(controller, false);
}
struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text)) {
if (!controller_push_msg(controller, &msg)) {

View File

@@ -42,7 +42,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SC_LOG_LEVEL_INFO;
return SDL_LOG_PRIORITY_INFO;
}
}
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetAllPriority(sdl_log);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);

View File

@@ -305,6 +305,7 @@ scrcpy(const struct scrcpy_options *options) {
.show_touches = options->show_touches,
.stay_awake = options->stay_awake,
.codec_options = options->codec_options,
.force_adb_forward = options->force_adb_forward,
};
if (!server_start(&server, options->serial, &params)) {
return false;

View File

@@ -42,6 +42,7 @@ struct scrcpy_options {
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
};
#define SCRCPY_OPTIONS_DEFAULT { \
@@ -79,6 +80,7 @@ struct scrcpy_options {
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
}
bool

View File

@@ -217,15 +217,20 @@ enable_tunnel_forward_any_port(struct server *server,
}
static bool
enable_tunnel_any_port(struct server *server, struct port_range port_range) {
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
enable_tunnel_any_port(struct server *server, struct port_range port_range,
bool force_adb_forward) {
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
// "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
return enable_tunnel_forward_any_port(server, port_range);
}
@@ -384,7 +389,8 @@ server_start(struct server *server, const char *serial,
goto error1;
}
if (!enable_tunnel_any_port(server, params->port_range)) {
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
goto error1;
}

View File

@@ -56,6 +56,7 @@ struct server_params {
uint16_t display_id;
bool show_touches;
bool stay_awake;
bool force_adb_forward;
};
// init default values

View File

@@ -200,17 +200,19 @@ static void test_serialize_get_clipboard(void) {
static void test_serialize_set_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
.inject_text = {
.set_clipboard = {
.paste = true,
.text = "hello, world!",
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 16);
assert(size == 17);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
1, // paste
0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '1.13',
version: '1.14',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@@ -35,6 +35,6 @@ prepare-sdl2:
SDL2-2.0.12
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.5-windows.zip \
2df06160056ec9a84c7334af2a1e42740befbb1a2e34370e7af544a2cc78152c \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.0-windows.zip \
854305f9a702f5ea2c3de73edde402bd26afa0ee944c9b0c4380420f5a862e0d \
platform-tools

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 29
versionCode 15
versionName "1.13"
versionCode 16
versionName "1.14"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.13
SCRCPY_VERSION_NAME=1.14
PLATFORM=${ANDROID_PLATFORM:-29}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}

View File

@@ -28,6 +28,7 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private boolean paste;
private ControlMessage() {
}
@@ -68,10 +69,11 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createSetClipboard(String text) {
public static ControlMessage createSetClipboard(String text, boolean paste) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
msg.paste = paste;
return msg;
}
@@ -134,4 +136,8 @@ public final class ControlMessage {
public int getVScroll() {
return vScroll;
}
public boolean getPaste() {
return paste;
}
}

View File

@@ -12,15 +12,15 @@ public class ControlMessageReader {
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 4096;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
public ControlMessageReader() {
// invariant: the buffer is always in "get" mode
@@ -110,8 +110,10 @@ public class ControlMessageReader {
if (buffer.remaining() < len) {
return null;
}
buffer.get(textBuffer, 0, len);
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
int position = buffer.position();
// Move the buffer position to consume the text
buffer.position(position + len);
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
}
private ControlMessage parseInjectText() {
@@ -148,11 +150,15 @@ public class ControlMessageReader {
}
private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;
}
boolean parse = buffer.get() != 0;
String text = parseString();
if (text == null) {
return null;
}
return ControlMessage.createSetClipboard(text);
return ControlMessage.createSetClipboard(text, parse);
}
private ControlMessage parseSetScreenPowerMode() {

View File

@@ -1,10 +1,8 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager;
import android.os.Build;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -50,7 +48,7 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!device.isScreenOn()) {
injectKeycode(KeyEvent.KEYCODE_POWER);
device.injectKeycode(KeyEvent.KEYCODE_POWER);
// dirty hack
// After POWER is injected, the device is powered on asynchronously.
@@ -107,14 +105,20 @@ public class Controller {
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
sender.pushClipboardText(clipboardText);
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
device.setClipboardText(msg.getText());
setClipboard(msg.getText(), msg.getPaste());
break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
device.setScreenPowerMode(msg.getAction());
int mode = msg.getAction();
boolean setPowerModeOk = device.setScreenPowerMode(mode);
if (setPowerModeOk) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
}
}
break;
case ControlMessage.TYPE_ROTATE_DEVICE:
@@ -126,7 +130,7 @@ public class Controller {
}
private boolean injectKeycode(int action, int keycode, int metaState) {
return injectKeyEvent(action, keycode, 0, metaState);
return device.injectKeyEvent(action, keycode, 0, metaState);
}
private boolean injectChar(char c) {
@@ -137,7 +141,7 @@ public class Controller {
return false;
}
for (KeyEvent event : events) {
if (!injectEvent(event)) {
if (!device.injectEvent(event)) {
return false;
}
}
@@ -193,7 +197,7 @@ public class Controller {
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event);
return device.injectEvent(event);
}
private boolean injectScroll(Position position, int hScroll, int vScroll) {
@@ -216,26 +220,25 @@ public class Controller {
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return injectEvent(event);
}
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
}
private boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
private boolean injectEvent(InputEvent event) {
return device.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
return device.injectEvent(event);
}
private boolean pressBackOrTurnScreenOn() {
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
return injectKeycode(keycode);
return device.injectKeycode(keycode);
}
private boolean setClipboard(String text, boolean paste) {
boolean ok = device.setClipboardText(text);
if (ok) {
Ln.i("Device clipboard set");
}
// On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
}
return ok;
}
}

View File

@@ -10,8 +10,14 @@ import android.content.IOnPrimaryClipChangedListener;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.view.IRotationWatcher;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.util.concurrent.atomic.AtomicBoolean;
public final class Device {
@@ -31,6 +37,7 @@ public final class Device {
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
/**
* Logical display identifier
@@ -76,6 +83,10 @@ public final class Device {
serviceManager.getClipboardManager().addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override
public void dispatchPrimaryClipChanged() {
if (isSettingClipboard.get()) {
// This is a notification for the change we are currently applying, ignore it
return;
}
synchronized (Device.this) {
if (clipboardListener != null) {
String text = getClipboardText();
@@ -140,7 +151,7 @@ public final class Device {
return supportsInputEvents;
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
@@ -152,6 +163,21 @@ public final class Device {
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
}
public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
}
public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
}
public boolean isScreenOn() {
return serviceManager.getPowerManager().isScreenOn();
}
@@ -180,26 +206,31 @@ public final class Device {
return s.toString();
}
public void setClipboardText(String text) {
boolean ok = serviceManager.getClipboardManager().setText(text);
if (ok) {
Ln.i("Device clipboard set");
public boolean setClipboardText(String text) {
String currentClipboard = getClipboardText();
if (currentClipboard == null || currentClipboard.equals(text)) {
// The clipboard already contains the requested text.
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
// problem, do not explicitly set the clipboard text if it already contains the expected content.
return false;
}
isSettingClipboard.set(true);
boolean ok = serviceManager.getClipboardManager().setText(text);
isSettingClipboard.set(false);
return ok;
}
/**
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
*/
public void setScreenPowerMode(int mode) {
public boolean setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay();
if (d == null) {
Ln.e("Could not get built-in display");
return;
}
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode);
if (ok) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
return false;
}
return SurfaceControl.setDisplayPowerMode(d, mode);
}
/**

View File

@@ -15,7 +15,7 @@ public final class Ln {
DEBUG, INFO, WARN, ERROR
}
private static Level THRESHOLD;
private static Level threshold = Level.INFO;
private Ln() {
// not instantiable
@@ -29,11 +29,11 @@ public final class Ln {
* @param level the log level
*/
public static void initLogLevel(Level level) {
THRESHOLD = level;
threshold = level;
}
public static boolean isEnabled(Level level) {
return level.ordinal() >= THRESHOLD.ordinal();
return level.ordinal() >= threshold.ordinal();
}
public static void d(String message) {

View File

@@ -9,6 +9,7 @@ import android.os.Build;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
public final class Server {
@@ -126,7 +127,7 @@ public final class Server {
Options options = new Options();
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase());
Ln.Level level = Ln.Level.valueOf(args[1].toUpperCase(Locale.ENGLISH));
options.setLogLevel(level);
int maxSize = Integer.parseInt(args[2]) & ~7; // multiple of 8

View File

@@ -216,6 +216,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
dos.writeByte(1); // paste
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeShort(text.length);
dos.write(text);
@@ -227,6 +228,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText());
Assert.assertTrue(event.getPaste());
}
@Test
@@ -238,6 +240,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
dos.writeByte(1); // paste
Arrays.fill(rawText, (byte) 'a');
String text = new String(rawText, 0, rawText.length);
@@ -251,6 +254,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
Assert.assertTrue(event.getPaste());
}
@Test