Compare commits

..

10 Commits

Author SHA1 Message Date
Romain Vimont
8da10eb4af WIP remove Meta+Shift+v 2020-05-30 01:48:58 +02:00
Romain Vimont
b330ba448b WIP Synchronize keyboard on Ctrl+v 2020-05-30 01:45:59 +02:00
Romain Vimont
a98b74df3d Add Meta+x shortcut for CUT
Add a scrcpy shortcut to send the CUT keycode.
2020-05-29 23:48:14 +02:00
Romain Vimont
567c5a7da7 Change "resize to fit" shortcut to Meta+w
Meta+x will be used for CUT (cut text to clipboard).
2020-05-29 23:47:29 +02:00
Romain Vimont
9badd2bdf0 Press COPY on "get clipboard" request if possible
If the device runs at least Android 7, just press COPY on the device
(the clipboard content will be sent via the clipboard listener).

<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_COPY>
2020-05-29 23:46:54 +02:00
Romain Vimont
31cee2c49f Reformulate seamless paste documentation 2020-05-29 23:46:04 +02:00
Romain Vimont
51d969b20c 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-05-29 23:44:16 +02:00
Romain Vimont
149de2f788 Forward Shift to the device
This allows to select text using Shift+(arrow keys).

Fixes #942 <https://github.com/Genymobile/scrcpy/issues/942>
2020-05-29 23:44:16 +02:00
Romain Vimont
26cadf1022 Forward Ctrl to the device
Now that Meta is used for scrcpy shortcuts, Ctrl can be forwarded to the
device.

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-05-29 23:44:16 +02:00
Romain Vimont
f650b0195f Replace Ctrl by Meta as shortcut modifier
This paves the way to forward Ctrl to the device.
2020-05-29 23:44:09 +02:00
15 changed files with 213 additions and 170 deletions

3
.gitignore vendored
View File

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

View File

@@ -354,7 +354,7 @@ scrcpy --fullscreen
scrcpy -f # short version
```
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
#### Rotation
@@ -370,18 +370,18 @@ Possibles values are:
- `2`: 180 degrees
- `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
`RCtrl`+`→` _(right)_.
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
`Ctrl`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations:
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
current running app may refuse, if it does support the requested
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 `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
This affects only the display, not the recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
affects only the display, not the recording.
### Other mirroring options
@@ -437,9 +437,9 @@ scrcpy --turn-screen-off
scrcpy -S
```
Or by pressing `RCtrl`+`o` at any time.
Or by pressing `Ctrl`+`o` at any time.
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
It can be useful to also prevent the device to sleep:
@@ -483,22 +483,24 @@ Note that it only shows _physical_ touches (with the finger on the device).
#### Rotate device screen
Press `RCtrl`+`r` to switch between portrait and landscape modes.
Press `Ctrl`+`r` to switch between portrait and landscape modes.
Note that it rotates only if the application in foreground supports the
requested orientation.
#### Copy-paste
Any time the Android clipboard changes, it is automatically synchronized to the
computer clipboard.
It is possible to synchronize clipboards between the computer and the device, in
both directions:
`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.
- `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).
Moreover, any time the Android clipboard changes, it is automatically
synchronized to the computer clipboard.
#### Text injection preference
@@ -558,37 +560,36 @@ Also see [issue #14].
## Shortcuts
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
device).
_`Meta` is typically the `Windows` key on the keyboard, or `Cmd` on macOS._
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`
| Action | Shortcut
| ---------------------------------------------------- |:-----------------------------
| Switch fullscreen mode | `Meta`+`f`
| Rotate display left | `Meta`+`←` _(left)_
| Rotate display right | `Meta`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Meta`+`g`
| Resize window to remove black borders | `Meta`+`w` \| _Double-click¹_
| Click on `HOME` | `Meta`+`h` \| _Middle-click_
| Click on `BACK` | `Meta`+`b` \| _Right-click²_
| Click on `APP_SWITCH` | `Meta`+`s`
| Click on `MENU` | `Meta`+`m`
| Click on `VOLUME_UP` | `Meta`+`` _(up)_
| Click on `VOLUME_DOWN` | `Meta`+`` _(down)_
| Click on `POWER` | `Meta`+`p`
| Power on | _Right-click²_
| Turn device screen off (keep mirroring) | `Meta`+`o`
| Turn device screen on | `Meta`+`Shift`+`o`
| Rotate device screen | `Meta`+`r`
| Expand notification panel | `Meta`+`n`
| Collapse notification panel | `Meta`+`Shift`+`n`
| Press COPY³, then Copy device clipboard to computer | `Meta`+`c`
| Press CUT³ | `Meta`+`x`
| Paste computer clipboard to device | `Meta`+`v`
| Enable/disable FPS counter (on stdout) | `Meta`+`i`
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_³Only if the device runs Android >= 7._
## Custom paths

View File

@@ -203,54 +203,52 @@ Default is 0 (automatic).\n
.SH SHORTCUTS
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
.TP
.B RCtrl+f
.B Meta+f
Switch fullscreen mode
.TP
.B RCtrl+Left
.B Meta+Left
Rotate display left
.TP
.B RCtrl+Right
.B Meta+Right
Rotate display right
.TP
.B RCtrl+g
.B Meta+g
Resize window to 1:1 (pixel\-perfect)
.TP
.B RCtrl+w, Double\-click on black borders
.B Meta+w, Double\-click on black borders
Resize window to remove black borders
.TP
.B RCtrl+h, Home, Middle\-click
.B Meta+h, Home, Middle\-click
Click on HOME
.TP
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
.B Meta+b, Meta+Backspace, Right\-click (when screen is on)
Click on BACK
.TP
.B RCtrl+s
.B Meta+s
Click on APP_SWITCH
.TP
.B RCtrl+m
.B Meta+m
Click on MENU
.TP
.B RCtrl+Up
.B Meta+Up
Click on VOLUME_UP
.TP
.B RCtrl+Down
.B Meta+Down
Click on VOLUME_DOWN
.TP
.B RCtrl+p
.B Meta+p
Click on POWER (turn screen on/off)
.TP
@@ -258,31 +256,39 @@ Click on POWER (turn screen on/off)
Turn screen on
.TP
.B RCtrl+o
.B Meta+o
Turn device screen off (keep mirroring)
.TP
.B RCtrl+Shift+o
.B Meta+Shift+o
Turn device screen on
.TP
.B RCtrl+r
.B Meta+r
Rotate device screen
.TP
.B RCtrl+n
.B Meta+n
Expand notification panel
.TP
.B RCtrl+Shift+n
.B Meta+Shift+n
Collapse notification panel
.TP
.B RCtrl+v
Inject computer clipboard text
.B Meta+c
Press COPY (Android >= 7), then copy device clipboard to computer
.TP
.B RCtrl+i
.B Meta+x
Press CUT (Android >= 7)
.TP
.B Meta+v
Paste computer clipboard to device
.TP
.B Meta+i
Enable/disable FPS counter (print frames/second in logs)
.TP

View File

@@ -12,6 +12,11 @@
void
scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define MOD "Cmd"
#else
# define MOD "Meta"
#endif
fprintf(stderr,
"Usage: %s [options]\n"
"\n"
@@ -181,71 +186,75 @@ scrcpy_print_usage(const char *arg0) {
"\n"
"Shortcuts:\n"
"\n"
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
" the device.\n"
"\n"
" RCtrl+f\n"
" " MOD "+f\n"
" Switch fullscreen mode\n"
"\n"
" RCtrl+Left\n"
" " MOD "+Left\n"
" Rotate display left\n"
"\n"
" RCtrl+Right\n"
" " MOD "+Right\n"
" Rotate display right\n"
"\n"
" RCtrl+g\n"
" " MOD "+g\n"
" Resize window to 1:1 (pixel-perfect)\n"
"\n"
" RCtrl+w\n"
" " MOD "+w\n"
" Double-click on black borders\n"
" Resize window to remove black borders\n"
"\n"
" RCtrl+h\n"
" Ctrl+h\n"
" Middle-click\n"
" Click on HOME\n"
"\n"
" RCtrl+b\n"
" RCtrl+Backspace\n"
" " MOD "+b\n"
" " MOD "+Backspace\n"
" Right-click (when screen is on)\n"
" Click on BACK\n"
"\n"
" RCtrl+s\n"
" " MOD "+s\n"
" Click on APP_SWITCH\n"
"\n"
" RCtrl+m\n"
" Ctrl+m\n"
" Click on MENU\n"
"\n"
" RCtrl+Up\n"
" " MOD "+Up\n"
" Click on VOLUME_UP\n"
"\n"
" RCtrl+Down\n"
" " MOD "+Down\n"
" Click on VOLUME_DOWN\n"
"\n"
" RCtrl+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"
" RCtrl+o\n"
" " MOD "+o\n"
" Turn device screen off (keep mirroring)\n"
"\n"
" RCtrl+Shift+o\n"
" " MOD "+Shift+o\n"
" Turn device screen on\n"
"\n"
" RCtrl+r\n"
" " MOD "+r\n"
" Rotate device screen\n"
"\n"
" RCtrl+n\n"
" " MOD "+n\n"
" Expand notification panel\n"
"\n"
" RCtrl+Shift+n\n"
" " MOD "+Shift+n\n"
" Collapse notification panel\n"
"\n"
" RCtrl+v\n"
" Inject computer clipboard text\n"
" " MOD "+c\n"
" Press COPY (Android >= 7), then copy device clipboard to\n"
" computer\n"
"\n"
" RCtrl+i\n"
" " MOD "+x\n"
" Press CUT (Android >= 7)\n"
"\n"
" " MOD "+v\n"
" Paste computer clipboard to device\n"
"\n"
" " MOD "+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
@@ -256,6 +265,7 @@ scrcpy_print_usage(const char *arg0) {
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
#undef MOD
}
static bool

View File

@@ -66,6 +66,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
@@ -79,7 +82,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;

View File

@@ -60,6 +60,9 @@ struct control_msg {
int32_t hscroll;
int32_t vscroll;
} inject_scroll_event;
struct {
bool copy;
} get_clipboard;
struct {
char *text; // owned, to be freed by SDL_free()
bool paste;

View File

@@ -93,6 +93,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
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);
}
@@ -114,7 +115,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
if (prefer_text && !(mod & KMOD_LCTRL)) {
if (prefer_text) {
// do not forward alpha and space key events
return false;
}

View File

@@ -70,6 +70,11 @@ action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
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,6 +106,17 @@ collapse_notification_panel(struct controller *controller) {
}
}
static void
request_device_clipboard(struct controller *controller, bool copy) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy = copy;
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();
@@ -242,25 +258,6 @@ 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,
@@ -268,22 +265,11 @@ input_manager_process_key(struct input_manager *im,
// control: indicates the state of the command-line option --no-control
// ctrl: the Ctrl key
bool lctrl = event->keysym.mod & KMOD_LCTRL;
bool rctrl = event->keysym.mod & KMOD_RCTRL;
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);
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
bool shortcut_key = rctrl;
#ifdef __APPLE__
shortcut_key |= meta;
#else
if (meta) {
// No shortcut involve Meta, and it is not forwarded to the device
return;
}
#endif
if (alt) {
// No shortcuts involve Alt, and it is not forwarded to the device
return;
@@ -294,14 +280,17 @@ input_manager_process_key(struct input_manager *im,
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
// Capture all RCtrl events
if (shortcut_key) {
// Capture all Meta events
if (meta) {
if (ctrl) {
// No shortcuts involve Ctrl+Meta
return;
}
int action = down ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat;
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 && !shift && !repeat) {
action_home(controller, action);
}
@@ -318,8 +307,6 @@ input_manager_process_key(struct input_manager *im,
}
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 && !shift && !repeat) {
action_menu(controller, action);
}
@@ -359,6 +346,20 @@ input_manager_process_key(struct input_manager *im,
rotate_client_right(im->screen);
}
return;
case SDLK_c:
if (control && !shift && !repeat && down) {
// Press COPY, then get the clipboard content
request_device_clipboard(controller, true);
}
return;
case SDLK_x:
if (control && !shift && !repeat && down) {
// For convenience (especially on macOS), bind Meta+x to
// CUT (even if it is already accessible by pressing Ctrl+x
// on the device)
action_cut(controller, action);
}
return;
case SDLK_v:
if (control && !shift && !repeat && down) {
// Inject the text as input events
@@ -401,15 +402,6 @@ input_manager_process_key(struct input_manager *im,
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;
@@ -419,9 +411,11 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (lctrl && !shift && keycode == SDLK_v && down) {
assert(!meta);
if (ctrl && !shift && keycode == SDLK_v && down) {
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+V, to allow seamless copy-paste.
// sending Ctrl+v
set_device_clipboard(controller, false);
}

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 SDL_LOG_PRIORITY_INFO;
return SC_LOG_LEVEL_INFO;
}
}
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
SDL_LogSetAllPriority(sdl_log);
if (args.help) {
scrcpy_print_usage(argv[0]);

View File

@@ -185,14 +185,18 @@ static void test_serialize_collapse_notification_panel(void) {
static void test_serialize_get_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy = true,
},
};
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 1);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD,
1, // copy
};
assert(!memcmp(buf, expected, sizeof(expected)));
}

View File

@@ -28,7 +28,7 @@ public final class ControlMessage {
private Position position;
private int hScroll;
private int vScroll;
private boolean paste;
private boolean pressCopyOrPaste;
private ControlMessage() {
}
@@ -69,11 +69,18 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createGetClipboard(boolean copy) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_GET_CLIPBOARD;
msg.pressCopyOrPaste = copy;
return msg;
}
public static ControlMessage createSetClipboard(String text, boolean paste) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD;
msg.text = text;
msg.paste = paste;
msg.pressCopyOrPaste = paste;
return msg;
}
@@ -137,7 +144,7 @@ public final class ControlMessage {
return vScroll;
}
public boolean getPaste() {
return paste;
public boolean getPressCopyOrPaste() {
return pressCopyOrPaste;
}
}

View File

@@ -12,6 +12,7 @@ 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 GET_CLIPBOARD_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
@@ -21,6 +22,7 @@ public class ControlMessageReader {
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
@@ -66,6 +68,9 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent();
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
msg = parseGetClipboard();
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
msg = parseSetClipboard();
break;
@@ -75,7 +80,6 @@ public class ControlMessageReader {
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
case ControlMessage.TYPE_GET_CLIPBOARD:
case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type);
break;
@@ -110,10 +114,8 @@ public class ControlMessageReader {
if (buffer.remaining() < len) {
return null;
}
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);
buffer.get(textBuffer, 0, len);
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
}
private ControlMessage parseInjectText() {
@@ -149,6 +151,14 @@ public class ControlMessageReader {
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
}
private ControlMessage parseGetClipboard() {
if (buffer.remaining() < GET_CLIPBOARD_PAYLOAD_LENGTH) {
return null;
}
boolean copy = buffer.get() != 0;
return ControlMessage.createGetClipboard(copy);
}
private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null;

View File

@@ -104,13 +104,10 @@ public class Controller {
device.collapsePanels();
break;
case ControlMessage.TYPE_GET_CLIPBOARD:
String clipboardText = device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
}
getClipboard(msg.getPressCopyOrPaste());
break;
case ControlMessage.TYPE_SET_CLIPBOARD:
setClipboard(msg.getText(), msg.getPaste());
setClipboard(msg.getText(), msg.getPressCopyOrPaste());
break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
@@ -228,6 +225,23 @@ public class Controller {
return device.injectKeycode(keycode);
}
private boolean getClipboard(boolean copy) {
// On Android >= 7, also press the COPY key if requested
if (copy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
// If there is something to copy, the clipboard will be automatically sent to the computer clipboard via the ClipboardListener
return device.injectKeycode(KeyEvent.KEYCODE_COPY);
}
// We can't press COPY, so only synchronize the current clipboard
String clipboardText = device.getClipboardText();
if (clipboardText != null) {
sender.pushClipboardText(clipboardText);
return true;
}
return false;
}
private boolean setClipboard(String text, boolean paste) {
boolean ok = device.setClipboardText(text);
if (ok) {

View File

@@ -207,14 +207,6 @@ public final class Device {
}
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);

View File

@@ -200,6 +200,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
dos.writeByte(1); // copy
byte[] packet = bos.toByteArray();
@@ -207,6 +208,7 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test
@@ -228,7 +230,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText());
Assert.assertTrue(event.getPaste());
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test
@@ -254,7 +256,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText());
Assert.assertTrue(event.getPaste());
Assert.assertTrue(event.getPressCopyOrPaste());
}
@Test