Compare commits

...

29 Commits

Author SHA1 Message Date
9d10426207 Mention in README that Ctrl is forwarded 2020-07-17 17:24:00 +02:00
0a98b4d4a6 Update copy-paste section in README
Update documentation regarding the recent copy-paste changes.
2020-07-17 17:24:00 +02:00
ae2b26fc47 Swap paste shortcuts
For consistency with MOD+c and MOD+x, use MOD+v to inject PASTE.

Use Mod+Shift+v to inject clipboard content as a sequence of text
events.
2020-07-17 17:24:00 +02:00
405f5f2704 Add shortcuts for COPY and CUT
Send COPY and CUT on MOD+c and MOD+x (only supported for Android >= 7).

The shortcuts Ctrl+c and Ctrl+x should generally also work (even before
Android 7), but the active Android app may use them for other actions
instead.
2020-07-17 17:24:00 +02:00
9ce0a475bf Change "resize to fit" shortcut to MOD+w
For convenience, MOD+x will be used for injecting the CUT keycode.
2020-07-17 17:24:00 +02:00
0987cd324e Remove "get clipboard" call
Now that every device clipboard change is automatically synchronized to
the computer, the "get clipboard" request (bound to MOD+c) is useless.
2020-07-17 17:24:00 +02:00
ecbbf5ff1c Set computer clipboard only if necessary
Do not explicitly set the clipboard text if it already contains the
expected content.

Even if copy-paste loops are avoided by the previous commit, this avoids
to trigger a clipboard change on the computer-side.

Refs #1580 <https://github.com/Genymobile/scrcpy/issues/1580>
2020-07-17 17:24:00 +02:00
25b80a85f1 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-17 17:24:00 +02:00
98d63fb24a 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-07-17 17:24:00 +02:00
59d30cb094 Forward Shift to the device
This allows to select text using Shift+(arrow keys).

Fixes #942 <https://github.com/Genymobile/scrcpy/issues/942>
2020-07-17 17:24:00 +02:00
ff0711fbff Forward Ctrl to the device
Now that the scrcpy shortcut modifier is Alt by default (and can be
configured), forward Ctrl to the device.

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-07-17 17:24:00 +02:00
7bfff9704e Ignore text events for shortcuts
Pressing Alt+c generates a text event containing "c", so "c" was sent to
the device when --prefer-text was enabled.

Ignore text events when the mod state matches a shortcut modifier.
2020-07-17 17:24:00 +02:00
060d91e4ce Accept Cmd as shortcut modifier
In addition to (left) Alt, also accept (left) Cmd. This is especially
convenient for macOS users.
2020-07-17 17:23:59 +02:00
a90b08ceb3 Customize shortcut modifier
Add --shortcut-mod, and use Alt as default modifier.

This paves a way to forward the Ctrl key to the device.
2020-07-17 17:23:00 +02:00
cfa2844121 Use Ctrl for all shortcuts
Remove the Cmd modifier on macOS, which was possible only for some
shortcuts but not all.

This paves the way to make the shortcut modifier customizable.
2020-07-17 00:00:42 +02:00
9d9dd1f143 Make expression order consistent
The condition "cmd" was always before "shift" in all expressions except
4.
2020-07-17 00:00:42 +02:00
eabaf6f7bd 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. Use a simple
boolean instead.
2020-07-17 00:00:42 +02:00
199c74f62f Declare main() with argc/argv params in tests
Declaring the main method as "int main(void)" causes issues with SDL.

Fixes #1209 <https://github.com/Genymobile/scrcpy/issues/1209>
2020-07-15 12:17:04 +02:00
322f1512ea Inject WAKEUP instead of POWER
To power the device on, inject KEYCODE_WAKEUP to avoid a possible
race condition (the device might become off between the test
isScreenOn() and the POWER keycode injection).
2020-07-15 12:03:44 +02:00
30714aba34 Restore power mode to normal on cleanup
This avoids to let the device screen turned off (as enabled by Ctrl+o or
--turn-screen-off).

PR #1576 <https://github.com/Genymobile/scrcpy/pull/1576>
Fixes #1572 <https://github.com/Genymobile/scrcpy/issues/1572>
2020-07-09 22:33:11 +02:00
a973757fd1 Warn on ignored touch event
In theory, this was expected to only happen when a touch event is sent
just before the device is rotated, but some devices do not respect the
encoding size, causing an unexpected mismatch.

Refs #1518 <https://github.com/Genymobile/scrcpy/issues/1518>
2020-07-09 22:32:14 +02:00
deea29f52a Send touch event without pressure on button up
Refs #1518 <https://github.com/Genymobile/scrcpy/issues/1518>
2020-07-09 22:31:47 +02:00
5fa46ad0c7 Fix constants name in comment 2020-07-07 15:47:25 +02:00
334964c380 Make setScreenPowerMode() method static
Its implementation does not use the instance at all.
2020-07-07 15:34:34 +02:00
f7d4b6d0db Do not crash on missing clipboard manager
Some devices have no clipboard manager.

In that case, do not try to enable clipboard synchronization to avoid
a crash.

Fixes #1440 <https://github.com/Genymobile/scrcpy/issues/1440>
Fixes #1556 <https://github.com/Genymobile/scrcpy/issues/1556>
2020-07-03 08:53:51 +02:00
e8a565f9ea Fix touch events HiDPI-scaling
Touch events were HiDPI-scaled twice:
 - once because the position (provided as floats between 0 and 1) were
   converted in pixels using the drawable size (not the window size)
 - once due to screen_convert_to_frame_coords()

One possible fix could be to compute the position in pixels from the
window size instead, but this would unnecessarily round the event
position to the nearest window coordinates (instead of drawable
coordinates).

Instead, expose two separate functions to convert to frame coordinates
from either window or drawable coordinates.

Fixes #1536 <https://github.com/Genymobile/scrcpy/issues/1536>
Refs #15 <https://github.com/Genymobile/scrcpy/issues/15>
Refs e40532a376
2020-06-27 15:45:28 +02:00
3c1ed5d86c Handle repeating keycodes
Initialize "repeat" count on key events.

PR #1519 <https://github.com/Genymobile/scrcpy/pull/1519>
Refs #1013 <https://github.com/Genymobile/scrcpy/pull/1013>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-06-19 22:30:17 +02:00
0ba74fbd9a Make scrcpy.h independant of other headers
The header scrcpy.h is intended to be the "public" API. It should not
depend on other internal headers.

Therefore, declare all required structs in this header and adapt
internal code.
2020-06-19 22:30:02 +02:00
29e5af76d4 Remove fprintf() call in tests
It should never have been committed.
2020-06-19 21:54:46 +02:00
36 changed files with 728 additions and 299 deletions

119
README.md
View File

@ -354,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 `MOD`+`f`.
#### Rotation
@ -370,17 +370,17 @@ 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 `MOD`+`←` _(left)_ and
`MOD`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations:
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
- `MOD`+`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
- `--rotation` (or `MOD`+`←`/`MOD`+`→`) rotates only the window content. This
affects only the display, not the recording.
@ -437,9 +437,9 @@ scrcpy --turn-screen-off
scrcpy -S
```
Or by pressing `Ctrl`+`o` at any time.
Or by pressing `MOD`+`o` at any time.
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
To turn it back on, press `MOD`+`Shift`+`o` (or `POWER`, `MOD`+`p`).
It can be useful to also prevent the device to sleep:
@ -494,24 +494,42 @@ scrcpy --disable-screensaver
#### Rotate device screen
Press `Ctrl`+`r` to switch between portrait and landscape modes.
Press `MOD`+`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 (and
pastes if the device runs Android >= 7);
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
breaks non-ASCII characters).
Any `Ctrl` shortcut is forwarded to the device. In particular:
- `Ctrl`+`c` typically copies
- `Ctrl`+`x` typically cuts
- `Ctrl`+`v` typically pastes (after computer-to-device clipboard
synchronization)
Moreover, any time the Android clipboard changes, it is automatically
synchronized to the computer clipboard.
This typically works as you expect.
The actual behavior depends on the active application though. For example,
_Termux_ sends SIGINT on `Ctrl`+`c` instead, and _K-9 Mail_ composes a new
message.
To copy, cut and paste in all cases (but only supported on Android >= 7):
- `MOD`+`c` injects `COPY`
- `MOD`+`x` injects `CUT`
- `MOD`+`v` injects `PASTE` (after computer-to-device clipboard
synchronization)
In addition, `MOD`+`Shift`+`v` allows to inject the computer clipboard text as a
sequence of key events. This is useful when the component does not accept text
pasting (for example in _Termux_), but it can break non-ASCII content.
*WARNING:* Pasting the computer clipboard to the device (either via `Ctrl`+`v`
or `MOD`+`v`) copies the content in the device clipboard. As a consequence, any
Android application could read its content. You should avoid to paste sensitive
content (like passwords) that way.
#### Text injection preference
@ -571,33 +589,52 @@ 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`
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`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 and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i`
In the following list, `MOD` is the shortcut modifier. By default, it's (left)
`Alt` or (left) `Cmd`.
It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
`lalt`, `ralt`, `lcmd` and `rcmd`. For example:
```bash
# use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl
# use either LCtrl+LAlt or LCmd for shortcuts
scrcpy --shortcut-mod=lctrl+lalt,lcmd
```
| Action | Shortcut
| ------------------------------------------- |:-----------------------------
| Switch fullscreen mode | `MOD`+`f`
| Rotate display left | `MOD`+`` _(left)_
| Rotate display right | `MOD`+`` _(right)_
| Resize window to 1:1 (pixel-perfect) | `MOD`+`g`
| Resize window to remove black borders | `MOD`+`w` \| _Double-click¹_
| Click on `HOME` | `MOD`+`h` \| _Middle-click_
| Click on `BACK` | `MOD`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `MOD`+`s`
| Click on `MENU` | `MOD`+`m`
| Click on `VOLUME_UP` | `MOD`+`↑` _(up)_
| Click on `VOLUME_DOWN` | `MOD`+`↓` _(down)_
| Click on `POWER` | `MOD`+`p`
| Power on | _Right-click²_
| Turn device screen off (keep mirroring) | `MOD`+`o`
| Turn device screen on | `MOD`+`Shift`+`o`
| Rotate device screen | `MOD`+`r`
| Expand notification panel | `MOD`+`n`
| Collapse notification panel | `MOD`+`Shift`+`n`
| Copy to clipboard³ | `MOD`+`c`
| Cut to clipboard³ | `MOD`+`x`
| Synchronize clipboards and paste³ | `MOD`+`v`
| Inject computer clipboard text | `MOD`+`Shift`+`v`
| Enable/disable FPS counter (on stdout) | `MOD`+`i`
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_³Only on Android >= 7._
All `Ctrl`+_key_ shortcuts are forwarded to the device, so they are handled by
the active application.
## Custom paths

View File

@ -186,7 +186,7 @@ if get_option('buildtype') == 'debug'
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies,
c_args: ['-DSDL_MAIN_HANDLED'])
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
test(t[0], exe)
endforeach
endif

View File

@ -149,6 +149,16 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-shortcut\-mod " key[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lcmd" and "rcmd".
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
For example, to use either LCtrl+LAlt or LCmd for scrcpy shortcuts, pass "lctrl+lalt,lcmd".
Default is "lalt,lcmd" (left-Alt or left-Cmd).
.TP
.B \-S, \-\-turn\-screen\-off
Turn the device screen off immediately.
@ -207,52 +217,55 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Cmd, but it can be configured by \-\-shortcut-mod.
.TP
.B Ctrl+f
.B MOD+f
Switch fullscreen mode
.TP
.B Ctrl+Left
.B MOD+Left
Rotate display left
.TP
.B Ctrl+Right
.B MOD+Right
Rotate display right
.TP
.B Ctrl+g
.B MOD+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
.B MOD+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
.B MOD+h, Home, Middle\-click
Click on HOME
.TP
.B Ctrl+b, Ctrl+Backspace, Right\-click (when screen is on)
.B MOD+b, MOD+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B Ctrl+s
.B MOD+s
Click on APP_SWITCH
.TP
.B Ctrl+m
.B MOD+m
Click on MENU
.TP
.B Ctrl+Up
.B MOD+Up
Click on VOLUME_UP
.TP
.B Ctrl+Down
.B MOD+Down
Click on VOLUME_DOWN
.TP
.B Ctrl+p
.B MOD+p
Click on POWER (turn screen on/off)
.TP
@ -260,39 +273,43 @@ Click on POWER (turn screen on/off)
Turn screen on
.TP
.B Ctrl+o
.B MOD+o
Turn device screen off (keep mirroring)
.TP
.B Ctrl+Shift+o
.B MOD+Shift+o
Turn device screen on
.TP
.B Ctrl+r
.B MOD+r
Rotate device screen
.TP
.B Ctrl+n
.B MOD+n
Expand notification panel
.TP
.B Ctrl+Shift+n
.B MOD+Shift+n
Collapse notification panel
.TP
.B Ctrl+c
Copy device clipboard to computer
.B Mod+c
Copy to clipboard (inject COPY keycode, Android >= 7 only)
.TP
.B Ctrl+v
Paste computer clipboard to device
.B Mod+x
Cut to clipboard (inject CUT keycode, Android >= 7 only)
.TP
.B Ctrl+Shift+v
Copy computer clipboard to device (and paste if the device runs Android >= 7)
.B MOD+v
Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only)
.TP
.B Ctrl+i
.B MOD+Shift+v
Inject computer clipboard text as a sequence of key events
.TP
.B MOD+i
Enable/disable FPS counter (print frames/second in logs)
.TP

View File

@ -3,20 +3,16 @@
#include <assert.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include "config.h"
#include "recorder.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/str_util.h"
void
scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
#endif
fprintf(stderr,
"Usage: %s [options]\n"
"\n"
@ -142,6 +138,19 @@ scrcpy_print_usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" --shortcut-mod key[+...]][,...]\n"
" Specify the modifiers to use for scrcpy shortcuts. Possible\n"
" keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", \"lcmd\"\n"
" and \"rcmd\".\n"
"\n"
" A shortcut can consist in several keys, separated by '+'.\n"
" Several shortcuts can be specified, separated by ','.\n"
"\n"
" For example, to use either LCtrl+LAlt or LCmd for scrcpy\n"
" shortcuts, pass \"lctrl+lalt,lcmd\".\n"
"\n"
" Default is \"lalt,lcmd\" (left-Alt or left-Cmd).\n"
"\n"
" -S, --turn-screen-off\n"
" Turn the device screen off immediately.\n"
"\n"
@ -189,75 +198,82 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" " CTRL_OR_CMD "+f\n"
" In the following list, MOD is the shortcut modifier. By default,\n"
" it's (left) Alt or (left) Cmd, but it can be configured by\n"
" --shortcut-mod.\n"
"\n"
" MOD+f\n"
" Switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+Left\n"
" MOD+Left\n"
" Rotate display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" MOD+Right\n"
" Rotate display right\n"
"\n"
" " CTRL_OR_CMD "+g\n"
" MOD+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" " CTRL_OR_CMD "+x\n"
" MOD+w\n"
" Double-click on black borders\n"
" Resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" MOD+h\n"
" Middle-click\n"
" Click on HOME\n"
"\n"
" " CTRL_OR_CMD "+b\n"
" " CTRL_OR_CMD "+Backspace\n"
" MOD+b\n"
" MOD+Backspace\n"
" Right-click (when screen is on)\n"
" Click on BACK\n"
"\n"
" " CTRL_OR_CMD "+s\n"
" MOD+s\n"
" Click on APP_SWITCH\n"
"\n"
" Ctrl+m\n"
" MOD+m\n"
" Click on MENU\n"
"\n"
" " CTRL_OR_CMD "+Up\n"
" MOD+Up\n"
" Click on VOLUME_UP\n"
"\n"
" " CTRL_OR_CMD "+Down\n"
" MOD+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" " CTRL_OR_CMD "+p\n"
" MOD+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"
" MOD+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" " CTRL_OR_CMD "+Shift+o\n"
" MOD+Shift+o\n"
" Turn device screen on\n"
"\n"
" " CTRL_OR_CMD "+r\n"
" MOD+r\n"
" Rotate device screen\n"
"\n"
" " CTRL_OR_CMD "+n\n"
" MOD+n\n"
" Expand notification panel\n"
"\n"
" " CTRL_OR_CMD "+Shift+n\n"
" MOD+Shift+n\n"
" Collapse notification panel\n"
"\n"
" " CTRL_OR_CMD "+c\n"
" Copy device clipboard to computer\n"
" MOD+c\n"
" Copy to clipboard (inject COPY keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+v\n"
" Paste computer clipboard to device\n"
" MOD+x\n"
" Cut to clipboard (inject CUT keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device (and paste if the device\n"
" runs Android >= 7)\n"
" MOD+v\n"
" Copy computer clipboard to device, then paste (inject PASTE\n"
" keycode, Android >= 7 only)\n"
"\n"
" " CTRL_OR_CMD "+i\n"
" MOD+Shift+v\n"
" Inject computer clipboard text as a sequence of key events\n"
"\n"
" MOD+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
@ -382,10 +398,10 @@ parse_rotation(const char *s, uint8_t *rotation) {
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
static_assert(SC_WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value");
if (!strcmp(s, "auto")) {
*position = WINDOW_POSITION_UNDEFINED;
*position = SC_WINDOW_POSITION_UNDEFINED;
return true;
}
@ -414,7 +430,7 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
}
static bool
parse_port_range(const char *s, struct port_range *port_range) {
parse_port_range(const char *s, struct sc_port_range *port_range) {
long values[2];
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
if (!count) {
@ -479,21 +495,116 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
return false;
}
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
static unsigned
parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0;
for (;;) {
char *plus = strchr(item, '+');
// strchr() does not consider the "len" parameter, to it could find an
// occurrence too far in the string (there is no strnchr())
bool has_plus = plus && plus < item + len;
assert(!has_plus || plus > item);
size_t key_len = has_plus ? (size_t) (plus - item) : len;
#define STREQ(literal, s, len) \
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) {
mod |= SC_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT;
} else if (STREQ("lcmd", item, key_len)) {
mod |= SC_MOD_LCMD;
} else if (STREQ("rcmd", item, key_len)) {
mod |= SC_MOD_RCMD;
} else {
LOGW("Unknown modifier key: %.*s", (int) key_len, item);
return 0;
}
#undef STREQ
if (!has_plus) {
break;
}
item = plus + 1;
assert(len >= key_len + 1);
len -= key_len + 1;
}
return mod;
}
static bool
parse_record_format(const char *optarg, enum recorder_format *format) {
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
unsigned count = 0;
unsigned current = 0;
// LCtrl+LAlt or RCtrl or LCtrl+RCmd: "lctrl+lalt,rctrl,lctrl+rcmd"
for (;;) {
char *comma = strchr(s, ',');
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
assert(count < SC_MAX_SHORTCUT_MODS);
LOGW("Too many shortcut modifiers alternatives");
return false;
}
assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
unsigned mod = parse_shortcut_mods_item(s, limit);
if (!mod) {
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
return false;
}
mods->data[current++] = mod;
++count;
if (!comma) {
break;
}
s = comma + 1;
}
mods->count = count;
return true;
}
#ifdef SC_TEST
// expose the function to unit-tests
bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
return parse_shortcut_mods(s, mods);
}
#endif
static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4;
*format = SC_RECORD_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV;
*format = SC_RECORD_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum recorder_format
static enum sc_record_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
@ -501,10 +612,10 @@ guess_record_format(const char *filename) {
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4;
return SC_RECORD_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV;
return SC_RECORD_FORMAT_MKV;
}
return 0;
}
@ -530,6 +641,7 @@ guess_record_format(const char *filename) {
#define OPT_CODEC_OPTIONS 1018
#define OPT_FORCE_ADB_FORWARD 1019
#define OPT_DISABLE_SCREENSAVER 1020
#define OPT_SHORTCUT_MOD 1021
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -562,6 +674,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'},
{"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'},
@ -725,6 +838,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_DISABLE_SCREENSAVER:
opts->disable_screensaver = true;
break;
case OPT_SHORTCUT_MOD:
if (!parse_shortcut_mods(optarg, &opts->shortcut_mods)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;

View File

@ -18,4 +18,9 @@ scrcpy_print_usage(const char *arg0);
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
#ifdef SC_TEST
bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
#endif
#endif

View File

@ -42,8 +42,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
return 10;
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len =
write_string(msg->inject_text.text,

View File

@ -44,6 +44,7 @@ struct control_msg {
struct {
enum android_keyevent_action action;
enum android_keycode keycode;
uint32_t repeat;
enum android_metastate metastate;
} inject_keycode;
struct {

View File

@ -92,6 +92,10 @@ 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_RCTRL, AKEYCODE_CTRL_RIGHT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
@ -111,8 +115,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
if (prefer_text) {
// do not forward alpha and space key events
if (prefer_text && !(mod & KMOD_CTRL)) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}

View File

@ -1,6 +1,7 @@
#include "input_manager.h"
#include <assert.h>
#include <SDL2/SDL_keycode.h>
#include "config.h"
#include "event_converter.h"
@ -10,6 +11,64 @@
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
to_sdl_mod(unsigned mod) {
uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
}
if (mod & SC_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL;
}
if (mod & SC_MOD_LALT) {
sdl_mod |= KMOD_LALT;
}
if (mod & SC_MOD_RALT) {
sdl_mod |= KMOD_RALT;
}
if (mod & SC_MOD_LCMD) {
sdl_mod |= KMOD_LGUI;
}
if (mod & SC_MOD_RCMD) {
sdl_mod |= KMOD_RGUI;
}
return sdl_mod;
}
static bool
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
assert(im->sdl_shortcut_mods.count);
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
return true;
}
}
return false;
}
void
input_manager_init(struct input_manager *im, bool prefer_text,
const struct sc_shortcut_mods *shortcut_mods)
{
im->prefer_text = prefer_text;
assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
assert(sdl_mod);
im->sdl_shortcut_mods.data[i] = sdl_mod;
}
im->sdl_shortcut_mods.count = shortcut_mods->count;
}
static void
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
@ -70,6 +129,16 @@ action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
static inline void
action_copy(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_COPY, actions, "COPY");
}
static inline void
action_cut(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
}
// turn the screen on if it was off, press BACK otherwise
static void
press_back_or_turn_screen_on(struct controller *controller) {
@ -101,16 +170,6 @@ 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, bool paste) {
char *text = SDL_GetClipboardText();
@ -210,6 +269,10 @@ rotate_client_right(struct screen *screen) {
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events
return;
}
if (!im->prefer_text) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
@ -234,7 +297,7 @@ input_manager_process_text_input(struct input_manager *im,
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text) {
bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
@ -247,6 +310,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
@ -257,71 +321,49 @@ input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event,
bool control) {
// 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 alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
// use Cmd on macOS, Ctrl on other platforms
#ifdef __APPLE__
bool cmd = !ctrl && meta;
#else
if (meta) {
// no shortcuts involve Meta on platforms other than macOS, and it must
// not be forwarded to the device
return;
}
bool cmd = ctrl; // && !meta, already guaranteed
#endif
if (alt) {
// no shortcuts involve Alt, and it must not be forwarded to the device
return;
}
bool smod = is_shortcut_mod(im, event->keysym.mod);
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;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat;
// The shortcut modifier is pressed
if (smod) {
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 && !repeat && down) {
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
@ -329,67 +371,72 @@ input_manager_process_key(struct input_manager *im,
}
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 && !repeat && down) {
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && !repeat && down) {
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c:
if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller);
if (control && !shift && !repeat) {
action_copy(controller, action);
}
return;
case SDLK_x:
if (control && !shift && !repeat) {
action_cut(controller, action);
}
return;
case SDLK_v:
if (control && cmd && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
// store the text in the device clipboard and paste
set_device_clipboard(controller, true);
} else {
// inject the text as input events
clipboard_paste(controller);
} else {
// store the text in the device clipboard and paste
set_device_clipboard(controller, true);
}
}
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 {
@ -398,7 +445,7 @@ 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;
@ -411,8 +458,20 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (event->repeat) {
++im->repeat;
} else {
im->repeat = 0;
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
set_device_clipboard(controller, false);
}
struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text)) {
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
@ -427,7 +486,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y);
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@ -465,15 +524,15 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww;
int32_t y = from->y * wh;
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
@ -503,8 +562,9 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
@ -568,7 +628,8 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@ -3,20 +3,37 @@
#include <stdbool.h>
#include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "controller.h"
#include "fps_counter.h"
#include "video_buffer.h"
#include "scrcpy.h"
#include "screen.h"
#include "video_buffer.h"
struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool prefer_text;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
};
void
input_manager_init(struct input_manager *im, bool prefer_text,
const struct sc_shortcut_mods *shortcut_mods);
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);

View File

@ -1,5 +1,6 @@
#include "scrcpy.h"
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>

View File

@ -25,10 +25,19 @@ receiver_destroy(struct receiver *receiver) {
static void
process_msg(struct device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD:
case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText();
bool same = current && !strcmp(current, msg->clipboard.text);
SDL_free(current);
if (same) {
LOGD("Computer clipboard unchanged");
return;
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(msg->clipboard.text);
break;
}
}
}

View File

@ -63,7 +63,7 @@ recorder_queue_clear(struct recorder_queue *queue) {
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum recorder_format format,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
if (!recorder->filename) {
@ -105,10 +105,10 @@ recorder_destroy(struct recorder *recorder) {
}
static const char *
recorder_get_format_name(enum recorder_format format) {
recorder_get_format_name(enum sc_record_format format) {
switch (format) {
case RECORDER_FORMAT_MP4: return "mp4";
case RECORDER_FORMAT_MKV: return "matroska";
case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska";
default: return NULL;
}
}

View File

@ -8,14 +8,9 @@
#include "config.h"
#include "common.h"
#include "scrcpy.h"
#include "util/queue.h"
enum recorder_format {
RECORDER_FORMAT_AUTO,
RECORDER_FORMAT_MP4,
RECORDER_FORMAT_MKV,
};
struct record_packet {
AVPacket packet;
struct record_packet *next;
@ -25,7 +20,7 @@ struct recorder_queue QUEUE(struct record_packet);
struct recorder {
char *filename;
enum recorder_format format;
enum sc_record_format format;
AVFormatContext *ctx;
struct size declared_frame_size;
bool header_written;
@ -46,7 +41,7 @@ struct recorder {
bool
recorder_init(struct recorder *recorder, const char *filename,
enum recorder_format format, struct size declared_frame_size);
enum sc_record_format format, struct size declared_frame_size);
void
recorder_destroy(struct recorder *recorder);

View File

@ -8,6 +8,8 @@
#include <SDL2/SDL.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
#endif
@ -46,7 +48,14 @@ static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.screen = &screen,
.prefer_text = false, // initialized later
.repeat = 0,
// initialized later
.prefer_text = false,
.sdl_shortcut_mods = {
.data = {0},
.count = 0,
},
};
#ifdef _WIN32
@ -434,7 +443,8 @@ scrcpy(const struct scrcpy_options *options) {
}
}
input_manager.prefer_text = options->prefer_text;
input_manager_init(&input_manager, options->prefer_text,
&options->shortcut_mods);
ret = event_loop(options->display, options->control);
LOGD("quit...");

View File

@ -2,13 +2,46 @@
#define SCRCPY_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "config.h"
#include "common.h"
#include "input_manager.h"
#include "recorder.h"
#include "util/log.h"
enum sc_log_level {
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LCMD = 1 << 4,
SC_MOD_RCMD = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
@ -19,15 +52,16 @@ struct scrcpy_options {
const char *render_driver;
const char *codec_options;
enum sc_log_level log_level;
enum recorder_format record_format;
struct port_range port_range;
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint16_t display_id;
@ -55,18 +89,22 @@ struct scrcpy_options {
.render_driver = NULL, \
.codec_options = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = RECORDER_FORMAT_AUTO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LCMD}, \
.count = 2, \
}, \
.max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = WINDOW_POSITION_UNDEFINED, \
.window_y = WINDOW_POSITION_UNDEFINED, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \

View File

@ -8,6 +8,7 @@
#include "common.h"
#include "compat.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
@ -257,9 +258,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = window_x != WINDOW_POSITION_UNDEFINED
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != WINDOW_POSITION_UNDEFINED
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height,
@ -579,14 +580,14 @@ screen_handle_window_event(struct screen *screen,
}
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
screen_hidpi_scale_coords(screen, &x, &y);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
@ -615,6 +616,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
return result;
}
struct point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
}
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account

View File

@ -9,8 +9,6 @@
#include "common.h"
#include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
struct video_buffer;
struct screen {
@ -76,7 +74,7 @@ void
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept WINDOW_POSITION_UNDEFINED
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
@ -126,7 +124,14 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in

View File

@ -143,7 +143,7 @@ listen_on_port(uint16_t port) {
static bool
enable_tunnel_reverse_any_port(struct server *server,
struct port_range port_range) {
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
@ -189,7 +189,7 @@ enable_tunnel_reverse_any_port(struct server *server,
static bool
enable_tunnel_forward_any_port(struct server *server,
struct port_range port_range) {
struct sc_port_range port_range) {
server->tunnel_forward = true;
uint16_t port = port_range.first;
for (;;) {
@ -217,7 +217,7 @@ enable_tunnel_forward_any_port(struct server *server,
}
static bool
enable_tunnel_any_port(struct server *server, struct port_range port_range,
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
bool force_adb_forward) {
if (!force_adb_forward) {
// Attempt to use "adb reverse"

View File

@ -9,6 +9,7 @@
#include "config.h"
#include "command.h"
#include "common.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/net.h"
@ -20,7 +21,7 @@ struct server {
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct port_range port_range;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
@ -47,7 +48,7 @@ struct server_params {
enum sc_log_level log_level;
const char *crop;
const char *codec_options;
struct port_range port_range;
struct sc_port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;

View File

@ -3,13 +3,6 @@
#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 LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@ -65,7 +65,10 @@ static void test_buffer_read64be(void) {
assert(val == 0xABCD1234567890EF);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_buffer_write16be();
test_buffer_write32be();
test_buffer_write64be();

View File

@ -65,7 +65,10 @@ static void test_cbuf_push_take(void) {
assert(item == 35);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_cbuf_empty();
test_cbuf_full();
test_cbuf_push_take();

View File

@ -1,7 +1,9 @@
#include <assert.h>
#include <string.h>
#include "cli.h"
#include "common.h"
#include "scrcpy.h"
static void test_flag_version(void) {
struct scrcpy_cli_args args = {
@ -73,7 +75,6 @@ static void test_options(void) {
const struct scrcpy_options *opts = &args.opts;
assert(opts->always_on_top);
fprintf(stderr, "%d\n", (int) opts->bit_rate);
assert(opts->bit_rate == 5000000);
assert(!strcmp(opts->crop, "100:200:300:400"));
assert(opts->fullscreen);
@ -84,7 +85,7 @@ static void test_options(void) {
assert(opts->port_range.last == 1236);
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == RECORDER_FORMAT_MKV);
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames);
assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches);
@ -119,13 +120,54 @@ static void test_options2(void) {
assert(!opts->control);
assert(!opts->display);
assert(!strcmp(opts->record_filename, "file.mp4"));
assert(opts->record_format == RECORDER_FORMAT_MP4);
assert(opts->record_format == SC_RECORD_FORMAT_MP4);
}
int main(void) {
static void test_parse_shortcut_mods(void) {
struct sc_shortcut_mods mods;
bool ok;
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == SC_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok);
assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT);
ok = sc_parse_shortcut_mods("lcmd,rcmd+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LCMD);
assert(mods.data[1] == (SC_MOD_RCMD | SC_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl+", &mods);
assert(!ok);
ok = sc_parse_shortcut_mods("lctrl,", &mods);
assert(!ok);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_flag_version();
test_flag_help();
test_options();
test_options2();
test_parse_shortcut_mods();
return 0;
};

View File

@ -9,18 +9,20 @@ static void test_serialize_inject_keycode(void) {
.inject_keycode = {
.action = AKEY_EVENT_ACTION_UP,
.keycode = AKEYCODE_ENTER,
.repeat = 5,
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 10);
assert(size == 14);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
0x00, 0x00, 0x00, 0X05, // repeat
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -255,7 +257,10 @@ static void test_serialize_rotate_device(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_serialize_inject_keycode();
test_serialize_inject_text();
test_serialize_inject_text_long();

View File

@ -45,7 +45,10 @@ static void test_deserialize_clipboard_big(void) {
device_msg_destroy(&msg);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_deserialize_clipboard();
test_deserialize_clipboard_big();
return 0;

View File

@ -32,7 +32,10 @@ static void test_queue(void) {
assert(queue_is_empty(&queue));
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_queue();
return 0;
}

View File

@ -286,7 +286,10 @@ static void test_parse_integer_with_suffix(void) {
assert(!ok);
}
int main(void) {
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();

View File

@ -19,18 +19,19 @@ public final class CleanUp {
// not instantiable
}
public static void configure(boolean disableShowTouches, int restoreStayOn) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1;
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
if (needProcess) {
startProcess(disableShowTouches, restoreStayOn);
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
} else {
// There is no additional clean up to do when scrcpy dies
unlinkSelf();
}
}
private static void startProcess(boolean disableShowTouches, int restoreStayOn) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(restoreStayOn)};
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH);
@ -59,6 +60,7 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
@ -73,5 +75,10 @@ public final class CleanUp {
}
}
}
if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}
}

View File

@ -17,8 +17,6 @@ public final class ControlMessage {
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_ROTATE_DEVICE = 10;
public static final int FLAGS_PASTE = 1;
private int type;
private String text;
private int metaState; // KeyEvent.META_*
@ -30,16 +28,18 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private int flags;
private boolean paste;
private int repeat;
private ControlMessage() {
}
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_KEYCODE;
msg.action = action;
msg.keycode = keycode;
msg.repeat = repeat;
msg.metaState = metaState;
return msg;
}
@ -75,9 +75,7 @@ public final class ControlMessage {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
if (paste) {
msg.flags = FLAGS_PASTE;
}
msg.paste = paste;
return msg;
}
@ -141,7 +139,11 @@ public final class ControlMessage {
return vScroll;
}
public int getFlags() {
return flags;
public boolean getPaste() {
return paste;
}
public int getRepeat() {
return repeat;
}
}

View File

@ -8,7 +8,7 @@ import java.nio.charset.StandardCharsets;
public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
@ -98,8 +98,9 @@ public class ControlMessageReader {
}
int action = toUnsigned(buffer.get());
int keycode = buffer.getInt();
int repeat = buffer.getInt();
int metaState = buffer.getInt();
return ControlMessage.createInjectKeycode(action, keycode, metaState);
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
}
private String parseString() {
@ -153,12 +154,12 @@ public class ControlMessageReader {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;
}
boolean parse = buffer.get() != 0;
boolean paste = buffer.get() != 0;
String text = parseString();
if (text == null) {
return null;
}
return ControlMessage.createSetClipboard(text, parse);
return ControlMessage.createSetClipboard(text, paste);
}
private ControlMessage parseSetScreenPowerMode() {

View File

@ -48,10 +48,10 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!device.isScreenOn()) {
device.injectKeycode(KeyEvent.KEYCODE_POWER);
device.injectKeycode(KeyEvent.KEYCODE_WAKEUP);
// dirty hack
// After POWER is injected, the device is powered on asynchronously.
// After the keycode is injected, the device is powered on asynchronously.
// To turn the device screen off while mirroring, the client will send a message that
// would be handled before the device is actually powered on, so its effect would
// be "canceled" once the device is turned back on.
@ -74,7 +74,7 @@ public class Controller {
switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE:
if (device.supportsInputEvents()) {
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
}
break;
case ControlMessage.TYPE_INJECT_TEXT:
@ -110,13 +110,12 @@ public class Controller {
}
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
setClipboard(msg.getText(), paste);
setClipboard(msg.getText(), msg.getPaste());
break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
int mode = msg.getAction();
boolean setPowerModeOk = device.setScreenPowerMode(mode);
boolean setPowerModeOk = Device.setScreenPowerMode(mode);
if (setPowerModeOk) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
}
@ -130,8 +129,8 @@ public class Controller {
}
}
private boolean injectKeycode(int action, int keycode, int metaState) {
return device.injectKeyEvent(action, keycode, 0, metaState);
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
return device.injectKeyEvent(action, keycode, repeat, metaState);
}
private boolean injectChar(char c) {
@ -166,7 +165,7 @@ public class Controller {
Point point = device.getPhysicalPoint(position);
if (point == null) {
// ignore event
Ln.w("Ignore touch event, it was generated for a different device size");
return false;
}
@ -225,7 +224,7 @@ public class Controller {
}
private boolean pressBackOrTurnScreenOn() {
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_WAKEUP;
return device.injectKeycode(keycode);
}

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
@ -80,23 +81,28 @@ public final class Device {
if (options.getControl()) {
// If control is enabled, synchronize Android clipboard to the computer automatically
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();
if (text != null) {
clipboardListener.onClipboardTextChanged(text);
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager != null) {
clipboardManager.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();
if (text != null) {
clipboardListener.onClipboardTextChanged(text);
}
}
}
}
}
});
});
} else {
Ln.w("No clipboard manager, copy-paste between device and computer will not work");
}
}
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
@ -199,7 +205,11 @@ public final class Device {
}
public String getClipboardText() {
CharSequence s = serviceManager.getClipboardManager().getText();
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return null;
}
CharSequence s = clipboardManager.getText();
if (s == null) {
return null;
}
@ -207,16 +217,30 @@ public final class Device {
}
public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = serviceManager.getClipboardManager();
if (clipboardManager == null) {
return false;
}
String currentClipboard = getClipboardText();
if (currentClipboard == null || currentClipboard.equals(text)) {
// The clipboard already contains the requested text.
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
// problem, do not explicitly set the clipboard text if it already contains the expected content.
return false;
}
isSettingClipboard.set(true);
boolean ok = serviceManager.getClipboardManager().setText(text);
boolean ok = clipboardManager.setText(text);
isSettingClipboard.set(false);
return ok;
}
/**
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants
* @param mode one of the {@code POWER_MODE_*} constants
*/
public boolean setScreenPowerMode(int mode) {
public static boolean setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay();
if (d == null) {
Ln.e("Could not get built-in display");

View File

@ -49,7 +49,7 @@ public final class Server {
}
}
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn);
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
boolean tunnelForward = options.isTunnelForward();

View File

@ -77,7 +77,14 @@ public final class ServiceManager {
public ClipboardManager getClipboardManager() {
if (clipboardManager == null) {
clipboardManager = new ClipboardManager(getService("clipboard", "android.content.IClipboard"));
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
if (clipboard == null) {
// Some devices have no clipboard manager
// <https://github.com/Genymobile/scrcpy/issues/1440>
// <https://github.com/Genymobile/scrcpy/issues/1556>
return null;
}
clipboardManager = new ClipboardManager(clipboard);
}
return clipboardManager;
}

View File

@ -25,6 +25,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
@ -37,6 +38,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@ -228,9 +230,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
Assert.assertTrue(event.getPaste());
}
@Test
@ -256,9 +256,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
Assert.assertTrue(event.getPaste());
}
@Test
@ -308,11 +306,13 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(0); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(1); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
@ -322,12 +322,14 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(0, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(1, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@ -341,6 +343,7 @@ public class ControlMessageReaderTest {
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(4); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
@ -353,6 +356,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(4, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next();
@ -360,6 +364,7 @@ public class ControlMessageReaderTest {
bos.reset();
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(5); // repeat
dos.writeInt(KeyEvent.META_CTRL_ON);
packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
@ -369,6 +374,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(5, event.getRepeat());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
}