Compare commits

...

14 Commits

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

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

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

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

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

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

This allows to trigger Android shortcuts.

Fixes #555 <https://github.com/Genymobile/scrcpy/issues/555>
2020-06-02 18:27:24 +02:00
Romain Vimont
fbd2d0bf3e Only use RCtrl for scrcpy shortcuts
This paves the way to forward LCtrl to the device.
2020-06-02 18:27:24 +02:00
10 changed files with 179 additions and 175 deletions

View File

@ -354,7 +354,7 @@ scrcpy --fullscreen
scrcpy -f # short version scrcpy -f # short version
``` ```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`. Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
#### Rotation #### Rotation
@ -370,18 +370,18 @@ Possibles values are:
- `2`: 180 degrees - `2`: 180 degrees
- `3`: 90 degrees clockwise - `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
`Ctrl`+`→` _(right)_. `RCtrl`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations: Note that _scrcpy_ manages 3 different rotations:
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the - `RCtrl`+`r` requests the device to switch between portrait and landscape (the
current running app may refuse, if it does support the requested current running app may refuse, if it does support the requested
orientation). orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation - `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the of the video sent from the device to the computer). This affects the
recording. recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This - `--rotation` (or `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
affects only the display, not the recording. This affects only the display, not the recording.
### Other mirroring options ### Other mirroring options
@ -437,9 +437,9 @@ scrcpy --turn-screen-off
scrcpy -S scrcpy -S
``` ```
Or by pressing `Ctrl`+`o` at any time. Or by pressing `RCtrl`+`o` at any time.
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`). To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
It can be useful to also prevent the device to sleep: It can be useful to also prevent the device to sleep:
@ -483,24 +483,22 @@ Note that it only shows _physical_ touches (with the finger on the device).
#### Rotate device screen #### Rotate device screen
Press `Ctrl`+`r` to switch between portrait and landscape modes. Press `RCtrl`+`r` to switch between portrait and landscape modes.
Note that it rotates only if the application in foreground supports the Note that it rotates only if the application in foreground supports the
requested orientation. requested orientation.
#### Copy-paste #### Copy-paste
It is possible to synchronize clipboards between the computer and the device, in Any time the Android clipboard changes, it is automatically synchronized to the
both directions: computer clipboard.
- `Ctrl`+`c` copies the device clipboard to the computer clipboard; `Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
pastes if the device runs Android >= 7); In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but sequence of text event. Even if it can break non-ASCII content, this is
breaks non-ASCII characters). sometimes necessary when pasting directly is not possible.
Moreover, any time the Android clipboard changes, it is automatically
synchronized to the computer clipboard.
#### Text injection preference #### Text injection preference
@ -560,30 +558,34 @@ Also see [issue #14].
## Shortcuts ## Shortcuts
| Action | Shortcut | Shortcut (macOS) `RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
| ------------------------------------------- |:----------------------------- |:----------------------------- device).
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_ On macOS, `Cmd` also works (for shortcuts which are not already captured by the
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_ system).
| 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¹_ | Action | Shortcut
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ | ------------------------------------------- |:-----------------------------
| Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ | `Cmd`+`b` \| _Right-click²_ | Switch fullscreen mode | `RCtrl`+`f`
| Click on `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` | Rotate display left | `RCtrl`+`←`
| Click on `MENU` | `Ctrl`+`m` | `Ctrl`+`m` | Rotate display right | `RCtrl`+`→`
| Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ | `Cmd`+`↑` _(up)_ | Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
| Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ | `Cmd`+`↓` _(down)_ | Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
| Click on `POWER` | `Ctrl`+`p` | `Cmd`+`p` | Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
| Power on | _Right-click²_ | _Right-click²_ | Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
| Turn device screen off (keep mirroring) | `Ctrl`+`o` | `Cmd`+`o` | Click on `APP_SWITCH` | `RCtrl`+`s`
| Turn device screen on | `Ctrl`+`Shift`+`o` | `Cmd`+`Shift`+`o` | Click on `MENU` | `RCtrl`+`m`
| Rotate device screen | `Ctrl`+`r` | `Cmd`+`r` | Click on `VOLUME_UP` | `RCtrl`+`↑` _(up)_
| Expand notification panel | `Ctrl`+`n` | `Cmd`+`n` | Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
| Collapse notification panel | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` | Click on `POWER` | `RCtrl`+`p`
| Copy device clipboard to computer | `Ctrl`+`c` | `Cmd`+`c` | Power on | _Right-click²_
| Paste computer clipboard to device | `Ctrl`+`v` | `Cmd`+`v` | Turn device screen off (keep mirroring) | `RCtrl`+`o`
| Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` | Turn device screen on | `RCtrl`+`Shift`+`o`
| Enable/disable FPS counter (on stdout) | `Ctrl`+`i` | `Cmd`+`i` | Rotate device screen | `RCtrl`+`r`
| Expand notification panel | `RCtrl`+`n`
| Collapse notification panel | `RCtrl`+`Shift`+`n`
| Inject computer clipboard text | `RCtrl`+`v`
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._

View File

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

View File

@ -12,11 +12,6 @@
void void
scrcpy_print_usage(const char *arg0) { scrcpy_print_usage(const char *arg0) {
#ifdef __APPLE__
# define CTRL_OR_CMD "Cmd"
#else
# define CTRL_OR_CMD "Ctrl"
#endif
fprintf(stderr, fprintf(stderr,
"Usage: %s [options]\n" "Usage: %s [options]\n"
"\n" "\n"
@ -186,75 +181,71 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
"Shortcuts:\n" "Shortcuts:\n"
"\n" "\n"
" " CTRL_OR_CMD "+f\n" " RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
" the device.\n"
"\n"
" RCtrl+f\n"
" Switch fullscreen mode\n" " Switch fullscreen mode\n"
"\n" "\n"
" " CTRL_OR_CMD "+Left\n" " RCtrl+Left\n"
" Rotate display left\n" " Rotate display left\n"
"\n" "\n"
" " CTRL_OR_CMD "+Right\n" " RCtrl+Right\n"
" Rotate display right\n" " Rotate display right\n"
"\n" "\n"
" " CTRL_OR_CMD "+g\n" " RCtrl+g\n"
" Resize window to 1:1 (pixel-perfect)\n" " Resize window to 1:1 (pixel-perfect)\n"
"\n" "\n"
" " CTRL_OR_CMD "+x\n" " RCtrl+w\n"
" Double-click on black borders\n" " Double-click on black borders\n"
" Resize window to remove black borders\n" " Resize window to remove black borders\n"
"\n" "\n"
" Ctrl+h\n" " RCtrl+h\n"
" Middle-click\n" " Middle-click\n"
" Click on HOME\n" " Click on HOME\n"
"\n" "\n"
" " CTRL_OR_CMD "+b\n" " RCtrl+b\n"
" " CTRL_OR_CMD "+Backspace\n" " RCtrl+Backspace\n"
" Right-click (when screen is on)\n" " Right-click (when screen is on)\n"
" Click on BACK\n" " Click on BACK\n"
"\n" "\n"
" " CTRL_OR_CMD "+s\n" " RCtrl+s\n"
" Click on APP_SWITCH\n" " Click on APP_SWITCH\n"
"\n" "\n"
" Ctrl+m\n" " RCtrl+m\n"
" Click on MENU\n" " Click on MENU\n"
"\n" "\n"
" " CTRL_OR_CMD "+Up\n" " RCtrl+Up\n"
" Click on VOLUME_UP\n" " Click on VOLUME_UP\n"
"\n" "\n"
" " CTRL_OR_CMD "+Down\n" " RCtrl+Down\n"
" Click on VOLUME_DOWN\n" " Click on VOLUME_DOWN\n"
"\n" "\n"
" " CTRL_OR_CMD "+p\n" " RCtrl+p\n"
" Click on POWER (turn screen on/off)\n" " Click on POWER (turn screen on/off)\n"
"\n" "\n"
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" Power on\n" " Power on\n"
"\n" "\n"
" " CTRL_OR_CMD "+o\n" " RCtrl+o\n"
" Turn device screen off (keep mirroring)\n" " Turn device screen off (keep mirroring)\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+o\n" " RCtrl+Shift+o\n"
" Turn device screen on\n" " Turn device screen on\n"
"\n" "\n"
" " CTRL_OR_CMD "+r\n" " RCtrl+r\n"
" Rotate device screen\n" " Rotate device screen\n"
"\n" "\n"
" " CTRL_OR_CMD "+n\n" " RCtrl+n\n"
" Expand notification panel\n" " Expand notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+n\n" " RCtrl+Shift+n\n"
" Collapse notification panel\n" " Collapse notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+c\n" " RCtrl+v\n"
" Copy device clipboard to computer\n" " Inject computer clipboard text\n"
"\n" "\n"
" " CTRL_OR_CMD "+v\n" " RCtrl+i\n"
" Paste computer clipboard to device\n"
"\n"
" " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device (and paste if the device\n"
" runs Android >= 7)\n"
"\n"
" " CTRL_OR_CMD "+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n" " Enable/disable FPS counter (print frames/second in logs)\n"
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"

View File

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

View File

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

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_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_ROTATE_DEVICE = 10; public static final int TYPE_ROTATE_DEVICE = 10;
public static final int FLAGS_PASTE = 1;
private int type; private int type;
private String text; private String text;
private int metaState; // KeyEvent.META_* private int metaState; // KeyEvent.META_*
@ -30,7 +28,7 @@ public final class ControlMessage {
private Position position; private Position position;
private int hScroll; private int hScroll;
private int vScroll; private int vScroll;
private int flags; private boolean paste;
private ControlMessage() { private ControlMessage() {
} }
@ -75,9 +73,7 @@ public final class ControlMessage {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;
msg.text = text; msg.text = text;
if (paste) { msg.paste = paste;
msg.flags = FLAGS_PASTE;
}
return msg; return msg;
} }
@ -141,7 +137,7 @@ public final class ControlMessage {
return vScroll; return vScroll;
} }
public int getFlags() { public boolean getPaste() {
return flags; return paste;
} }
} }

View File

@ -21,7 +21,6 @@ public class ControlMessageReader {
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
public ControlMessageReader() { public ControlMessageReader() {
// invariant: the buffer is always in "get" mode // invariant: the buffer is always in "get" mode
@ -111,8 +110,10 @@ public class ControlMessageReader {
if (buffer.remaining() < len) { if (buffer.remaining() < len) {
return null; return null;
} }
buffer.get(textBuffer, 0, len); int position = buffer.position();
return new String(textBuffer, 0, len, StandardCharsets.UTF_8); // Move the buffer position to consume the text
buffer.position(position + len);
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
} }
private ControlMessage parseInjectText() { private ControlMessage parseInjectText() {

View File

@ -110,8 +110,7 @@ public class Controller {
} }
break; break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0; setClipboard(msg.getText(), msg.getPaste());
setClipboard(msg.getText(), paste);
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {

View File

@ -207,6 +207,14 @@ public final class Device {
} }
public boolean setClipboardText(String text) { 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); isSettingClipboard.set(true);
boolean ok = serviceManager.getClipboardManager().setText(text); boolean ok = serviceManager.getClipboardManager().setText(text);
isSettingClipboard.set(false); isSettingClipboard.set(false);

View File

@ -228,9 +228,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals("testé", event.getText()); Assert.assertEquals("testé", event.getText());
Assert.assertTrue(event.getPaste());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
} }
@Test @Test
@ -256,9 +254,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType()); Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
Assert.assertEquals(text, event.getText()); Assert.assertEquals(text, event.getText());
Assert.assertTrue(event.getPaste());
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
Assert.assertTrue(parse);
} }
@Test @Test