From eabaf6f7bd391d0147eaebda51c79bbf9a75807a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 01/17] 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. --- .../java/com/genymobile/scrcpy/ControlMessage.java | 12 ++++-------- .../com/genymobile/scrcpy/ControlMessageReader.java | 4 ++-- .../main/java/com/genymobile/scrcpy/Controller.java | 3 +-- .../genymobile/scrcpy/ControlMessageReaderTest.java | 8 ++------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index dbb8d382c0..736acf80b9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -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,7 +28,7 @@ public final class ControlMessage { private Position position; private int hScroll; private int vScroll; - private int flags; + private boolean paste; private int repeat; private ControlMessage() { @@ -77,9 +75,7 @@ public static ControlMessage createSetClipboard(String text, boolean paste) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_SET_CLIPBOARD; msg.text = text; - if (paste) { - msg.flags = FLAGS_PASTE; - } + msg.paste = paste; return msg; } @@ -143,8 +139,8 @@ public int getVScroll() { return vScroll; } - public int getFlags() { - return flags; + public boolean getPaste() { + return paste; } public int getRepeat() { diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index 132e3f4eef..ce18510318 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -154,12 +154,12 @@ private ControlMessage parseSetClipboard() { 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() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 6ff8d208a1..f32e5ec04e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -110,8 +110,7 @@ private void handleEvent() throws IOException { } 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()) { diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 1e2b82661c..5eb52760fe 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -230,9 +230,7 @@ public void testParseSetClipboardEvent() throws IOException { 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 @@ -258,9 +256,7 @@ public void testParseBigSetClipboardEvent() throws IOException { 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 From 9d9dd1f143e6dae770263432a11946f1601be82e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 02/17] Make expression order consistent The condition "cmd" was always before "shift" in all expressions except 4. --- app/src/input_manager.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7d500efe41..465ba6e620 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -368,22 +368,22 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (!shift && cmd && !repeat && down) { + if (cmd && !shift && !repeat && down) { struct fps_counter *fps_counter = im->video_buffer->fps_counter; switch_fps_counter_state(fps_counter); From 63cb93d7d7d90354b834e71200eb43ee9228f2a3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 03/17] 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. --- README.md | 48 ++++++++++++++++----------------- app/src/cli.c | 45 ++++++++++++++----------------- app/src/input_manager.c | 59 +++++++++++++++-------------------------- 3 files changed, 66 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 2c01a01e46..3cb9021c3b 100644 --- a/README.md +++ b/README.md @@ -571,30 +571,30 @@ 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` + | Action | Shortcut + | ------------------------------------------- |:----------------------------- + | Switch fullscreen mode | `Ctrl`+`f` + | Rotate display left | `Ctrl`+`←` _(left)_ + | Rotate display right | `Ctrl`+`→` _(right)_ + | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` + | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ + | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ + | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ + | Click on `APP_SWITCH` | `Ctrl`+`s` + | Click on `MENU` | `Ctrl`+`m` + | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ + | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ + | Click on `POWER` | `Ctrl`+`p` + | Power on | _Right-click²_ + | Turn device screen off (keep mirroring) | `Ctrl`+`o` + | Turn device screen on | `Ctrl`+`Shift`+`o` + | Rotate device screen | `Ctrl`+`r` + | Expand notification panel | `Ctrl`+`n` + | Collapse notification panel | `Ctrl`+`Shift`+`n` + | Copy device clipboard to computer | `Ctrl`+`c` + | Paste computer clipboard to device | `Ctrl`+`v` + | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` + | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/src/cli.c b/app/src/cli.c index 9e07e91260..4b65c98ff4 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -13,11 +13,6 @@ void scrcpy_print_usage(const char *arg0) { -#ifdef __APPLE__ -# define CTRL_OR_CMD "Cmd" -#else -# define CTRL_OR_CMD "Ctrl" -#endif fprintf(stderr, "Usage: %s [options]\n" "\n" @@ -190,19 +185,19 @@ scrcpy_print_usage(const char *arg0) { "\n" "Shortcuts:\n" "\n" - " " CTRL_OR_CMD "+f\n" + " Ctrl+f\n" " Switch fullscreen mode\n" "\n" - " " CTRL_OR_CMD "+Left\n" + " Ctrl+Left\n" " Rotate display left\n" "\n" - " " CTRL_OR_CMD "+Right\n" + " Ctrl+Right\n" " Rotate display right\n" "\n" - " " CTRL_OR_CMD "+g\n" + " Ctrl+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " " CTRL_OR_CMD "+x\n" + " Ctrl+x\n" " Double-click on black borders\n" " Resize window to remove black borders\n" "\n" @@ -210,55 +205,55 @@ scrcpy_print_usage(const char *arg0) { " Middle-click\n" " Click on HOME\n" "\n" - " " CTRL_OR_CMD "+b\n" - " " CTRL_OR_CMD "+Backspace\n" + " Ctrl+b\n" + " Ctrl+Backspace\n" " Right-click (when screen is on)\n" " Click on BACK\n" "\n" - " " CTRL_OR_CMD "+s\n" + " Ctrl+s\n" " Click on APP_SWITCH\n" "\n" " Ctrl+m\n" " Click on MENU\n" "\n" - " " CTRL_OR_CMD "+Up\n" + " Ctrl+Up\n" " Click on VOLUME_UP\n" "\n" - " " CTRL_OR_CMD "+Down\n" + " Ctrl+Down\n" " Click on VOLUME_DOWN\n" "\n" - " " CTRL_OR_CMD "+p\n" + " Ctrl+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" + " Ctrl+o\n" " Turn device screen off (keep mirroring)\n" "\n" - " " CTRL_OR_CMD "+Shift+o\n" + " Ctrl+Shift+o\n" " Turn device screen on\n" "\n" - " " CTRL_OR_CMD "+r\n" + " Ctrl+r\n" " Rotate device screen\n" "\n" - " " CTRL_OR_CMD "+n\n" + " Ctrl+n\n" " Expand notification panel\n" "\n" - " " CTRL_OR_CMD "+Shift+n\n" + " Ctrl+Shift+n\n" " Collapse notification panel\n" "\n" - " " CTRL_OR_CMD "+c\n" + " Ctrl+c\n" " Copy device clipboard to computer\n" "\n" - " " CTRL_OR_CMD "+v\n" + " Ctrl+v\n" " Paste computer clipboard to device\n" "\n" - " " CTRL_OR_CMD "+Shift+v\n" + " Ctrl+Shift+v\n" " Copy computer clipboard to device (and paste if the device\n" " runs Android >= 7)\n" "\n" - " " CTRL_OR_CMD "+i\n" + " Ctrl+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" " Drag & drop APK file\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 465ba6e620..e16dee5293 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -264,27 +264,16 @@ input_manager_process_key(struct input_manager *im, 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 + if (alt || meta) { + // no shortcuts involve Alt or Meta, and they must not be forwarded to + // the device return; } struct controller *controller = im->controller; // capture all Ctrl events - if (ctrl || cmd) { + if (ctrl) { SDL_Keycode keycode = event->keysym.sym; bool down = event->type == SDL_KEYDOWN; int action = down ? ACTION_DOWN : ACTION_UP; @@ -292,37 +281,33 @@ input_manager_process_key(struct input_manager *im, 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 && ctrl && !shift && !repeat) { action_home(controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_back(controller, action); } return; case SDLK_s: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !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 && ctrl && !shift && !repeat) { action_menu(controller, action); } return; case SDLK_p: - if (control && cmd && !shift && !repeat) { + if (control && ctrl && !shift && !repeat) { action_power(controller, action); } return; case SDLK_o: - if (control && cmd && !repeat && down) { + if (control && ctrl && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -330,34 +315,34 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_DOWN: - if (control && cmd && !shift) { + if (control && ctrl && !shift) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (control && cmd && !shift) { + if (control && ctrl && !shift) { // forward repeated events action_volume_up(controller, action); } return; case SDLK_LEFT: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { rotate_client_left(im->screen); } return; case SDLK_RIGHT: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { rotate_client_right(im->screen); } return; case SDLK_c: - if (control && cmd && !shift && !repeat && down) { + if (control && ctrl && !shift && !repeat && down) { request_device_clipboard(controller); } return; case SDLK_v: - if (control && cmd && !repeat && down) { + if (control && ctrl && !repeat && down) { if (shift) { // store the text in the device clipboard and paste set_device_clipboard(controller, true); @@ -368,29 +353,29 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (cmd && !shift && !repeat && down) { + if (ctrl && !shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (cmd && !shift && !repeat && down) { + if (ctrl && !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 && ctrl && !repeat && down) { if (shift) { collapse_notification_panel(controller); } else { @@ -399,7 +384,7 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_r: - if (control && cmd && !shift && !repeat && down) { + if (control && ctrl && !shift && !repeat && down) { rotate_device(controller); } return; From 1b76d9fd78c3a88a8503a72d4cd2f65bdb836aa4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 04/17] Customize shortcut modifier Add --shortcut-mod, and use Alt as default modifier. This paves the way to forward the Ctrl key to the device. --- README.md | 82 ++++++++++++-------- app/meson.build | 2 +- app/scrcpy.1 | 55 ++++++++------ app/src/cli.c | 162 ++++++++++++++++++++++++++++++++++------ app/src/cli.h | 5 ++ app/src/input_manager.c | 110 +++++++++++++++++++-------- app/src/input_manager.h | 14 +++- app/src/scrcpy.c | 11 ++- app/src/scrcpy.h | 21 ++++++ app/tests/test_cli.c | 39 ++++++++++ 10 files changed, 392 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 3cb9021c3b..7eb5578abc 100644 --- a/README.md +++ b/README.md @@ -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,7 +494,7 @@ 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. @@ -504,10 +504,10 @@ requested orientation. It is possible to synchronize clipboards between the computer and the device, in both directions: - - `Ctrl`+`c` copies the device clipboard to the computer clipboard; - - `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and + - `MOD`+`c` copies the device clipboard to the computer clipboard; + - `MOD`+`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 + - `MOD`+`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 @@ -571,30 +571,48 @@ Also see [issue #14]. ## Shortcuts +In the following list, `MOD` is the shortcut modifier. By default, it's (left) +`Alt`. + +It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, +`lalt`, `ralt`, `lsuper` and `rsuper`. For example: + +```bash +# use RCtrl for shortcuts +scrcpy --shortcut-mod=rctrl + +# use either LCtrl+LAlt or LSuper for shortcuts +scrcpy --shortcut-mod=lctrl+lalt,lsuper +``` + +_[Super] is typically the "Windows" or "Cmd" key._ + +[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button) + | Action | Shortcut | ------------------------------------------- |:----------------------------- - | Switch fullscreen mode | `Ctrl`+`f` - | Rotate display left | `Ctrl`+`←` _(left)_ - | Rotate display right | `Ctrl`+`→` _(right)_ - | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` - | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ - | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ - | Click on `BACK` | `Ctrl`+`b` \| _Right-click²_ - | Click on `APP_SWITCH` | `Ctrl`+`s` - | Click on `MENU` | `Ctrl`+`m` - | Click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ - | Click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ - | Click on `POWER` | `Ctrl`+`p` + | 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`+`x` \| _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) | `Ctrl`+`o` - | Turn device screen on | `Ctrl`+`Shift`+`o` - | Rotate device screen | `Ctrl`+`r` - | Expand notification panel | `Ctrl`+`n` - | Collapse notification panel | `Ctrl`+`Shift`+`n` - | Copy device clipboard to computer | `Ctrl`+`c` - | Paste computer clipboard to device | `Ctrl`+`v` - | Copy computer clipboard to device and paste | `Ctrl`+`Shift`+`v` - | Enable/disable FPS counter (on stdout) | `Ctrl`+`i` + | 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 device clipboard to computer | `MOD`+`c` + | Paste computer clipboard to device | `MOD`+`v` + | Copy computer clipboard to device and paste | `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._ diff --git a/app/meson.build b/app/meson.build index 505f0819fa..0163dd7fab 100644 --- a/app/meson.build +++ b/app/meson.build @@ -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 diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 4e3c43f233..7ea778f354 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -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", "lsuper" and "rsuper". + +A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','. + +For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". + +Default is "lalt" (left-Alt). + .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, 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+x, 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,39 @@ 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 +.B MOD+c Copy device clipboard to computer .TP -.B Ctrl+v +.B MOD+v Paste computer clipboard to device .TP -.B Ctrl+Shift+v +.B MOD+Shift+v Copy computer clipboard to device (and paste if the device runs Android >= 7) .TP -.B Ctrl+i +.B MOD+i Enable/disable FPS counter (print frames/second in logs) .TP diff --git a/app/src/cli.c b/app/src/cli.c index 4b65c98ff4..de32810e2a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -138,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.\n" + " Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n" + " \"lsuper\" and \"rsuper\".\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 LSuper for scrcpy\n" + " shortcuts, pass \"lctrl+lalt,lsuper\".\n" + "\n" + " Default is \"lalt\" (left-Alt).\n" + "\n" " -S, --turn-screen-off\n" " Turn the device screen off immediately.\n" "\n" @@ -185,75 +198,78 @@ scrcpy_print_usage(const char *arg0) { "\n" "Shortcuts:\n" "\n" - " Ctrl+f\n" + " In the following list, MOD is the shortcut modifier. By default,\n" + " it's (left) Alt, but it can be configured by --shortcut-mod.\n" + "\n" + " MOD+f\n" " Switch fullscreen mode\n" "\n" - " Ctrl+Left\n" + " MOD+Left\n" " Rotate display left\n" "\n" - " Ctrl+Right\n" + " MOD+Right\n" " Rotate display right\n" "\n" - " Ctrl+g\n" + " MOD+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " Ctrl+x\n" + " MOD+x\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+b\n" - " Ctrl+Backspace\n" + " MOD+b\n" + " MOD+Backspace\n" " Right-click (when screen is on)\n" " Click on BACK\n" "\n" - " Ctrl+s\n" + " MOD+s\n" " Click on APP_SWITCH\n" "\n" - " Ctrl+m\n" + " MOD+m\n" " Click on MENU\n" "\n" - " Ctrl+Up\n" + " MOD+Up\n" " Click on VOLUME_UP\n" "\n" - " Ctrl+Down\n" + " MOD+Down\n" " Click on VOLUME_DOWN\n" "\n" - " Ctrl+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+o\n" + " MOD+o\n" " Turn device screen off (keep mirroring)\n" "\n" - " Ctrl+Shift+o\n" + " MOD+Shift+o\n" " Turn device screen on\n" "\n" - " Ctrl+r\n" + " MOD+r\n" " Rotate device screen\n" "\n" - " Ctrl+n\n" + " MOD+n\n" " Expand notification panel\n" "\n" - " Ctrl+Shift+n\n" + " MOD+Shift+n\n" " Collapse notification panel\n" "\n" - " Ctrl+c\n" + " MOD+c\n" " Copy device clipboard to computer\n" "\n" - " Ctrl+v\n" + " MOD+v\n" " Paste computer clipboard to device\n" "\n" - " Ctrl+Shift+v\n" + " MOD+Shift+v\n" " Copy computer clipboard to device (and paste if the device\n" " runs Android >= 7)\n" "\n" - " Ctrl+i\n" + " MOD+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" " Drag & drop APK file\n" @@ -475,6 +491,101 @@ 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("lsuper", item, key_len)) { + mod |= SC_MOD_LSUPER; + } else if (STREQ("rsuper", item, key_len)) { + mod |= SC_MOD_RSUPER; + } 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_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { + unsigned count = 0; + unsigned current = 0; + + // LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper" + + 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")) { @@ -526,6 +637,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[]) { @@ -558,6 +670,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'}, @@ -721,6 +834,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; diff --git a/app/src/cli.h b/app/src/cli.h index 2e2bfe9390..73dfe22863 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -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 diff --git a/app/src/input_manager.c b/app/src/input_manager.c index e16dee5293..b954fbd929 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -1,6 +1,7 @@ #include "input_manager.h" #include +#include #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_LSUPER) { + sdl_mod |= KMOD_LGUI; + } + if (mod & SC_MOD_RSUPER) { + 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) { @@ -258,22 +317,13 @@ 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); - if (alt || meta) { - // no shortcuts involve Alt or Meta, and they 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) { + // The shortcut modifier is pressed + if (smod) { SDL_Keycode keycode = event->keysym.sym; bool down = event->type == SDL_KEYDOWN; int action = down ? ACTION_DOWN : ACTION_UP; @@ -281,33 +331,33 @@ input_manager_process_key(struct input_manager *im, bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_home(controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_back(controller, action); } return; case SDLK_s: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_app_switch(controller, action); } return; case SDLK_m: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_menu(controller, action); } return; case SDLK_p: - if (control && ctrl && !shift && !repeat) { + if (control && !shift && !repeat) { action_power(controller, action); } return; case SDLK_o: - if (control && ctrl && !repeat && down) { + if (control && !repeat && down) { enum screen_power_mode mode = shift ? SCREEN_POWER_MODE_NORMAL : SCREEN_POWER_MODE_OFF; @@ -315,34 +365,34 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_DOWN: - if (control && ctrl && !shift) { + if (control && !shift) { // forward repeated events action_volume_down(controller, action); } return; case SDLK_UP: - if (control && ctrl && !shift) { + if (control && !shift) { // forward repeated events action_volume_up(controller, action); } return; case SDLK_LEFT: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { rotate_client_left(im->screen); } return; case SDLK_RIGHT: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { rotate_client_right(im->screen); } return; case SDLK_c: - if (control && ctrl && !shift && !repeat && down) { + if (control && !shift && !repeat && down) { request_device_clipboard(controller); } return; case SDLK_v: - if (control && ctrl && !repeat && down) { + if (control && !repeat && down) { if (shift) { // store the text in the device clipboard and paste set_device_clipboard(controller, true); @@ -353,29 +403,29 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_f: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_switch_fullscreen(im->screen); } return; case SDLK_x: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (ctrl && !shift && !repeat && down) { + if (!shift && !repeat && down) { screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (ctrl && !shift && !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 && ctrl && !repeat && down) { + if (control && !repeat && down) { if (shift) { collapse_notification_panel(controller); } else { @@ -384,7 +434,7 @@ input_manager_process_key(struct input_manager *im, } return; case SDLK_r: - if (control && ctrl && !shift && !repeat && down) { + if (control && !shift && !repeat && down) { rotate_device(controller); } return; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 90b74fecef..c854664a9e 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -3,12 +3,15 @@ #include +#include + #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; @@ -20,8 +23,17 @@ struct input_manager { 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); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 889cbb873c..06a3b3d154 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -49,7 +49,13 @@ static struct input_manager input_manager = { .video_buffer = &video_buffer, .screen = &screen, .repeat = 0, - .prefer_text = false, // initialized later + + // initialized later + .prefer_text = false, + .sdl_shortcut_mods = { + .data = {0}, + .count = 0, + }, }; #ifdef _WIN32 @@ -437,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..."); diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index d7eb2f5842..220fcc2c6a 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -20,6 +20,22 @@ enum sc_record_format { 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_LSUPER = 1 << 4, + SC_MOD_RSUPER = 1 << 5, +}; + +struct sc_shortcut_mods { + unsigned data[SC_MAX_SHORTCUT_MODS]; + unsigned count; +}; + struct sc_port_range { uint16_t first; uint16_t last; @@ -38,6 +54,7 @@ struct scrcpy_options { enum sc_log_level log_level; 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; @@ -77,6 +94,10 @@ struct scrcpy_options { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ }, \ + .shortcut_mods = { \ + .data = {SC_MOD_LALT}, \ + .count = 1, \ + }, \ .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ .max_fps = 0, \ diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 7a7d408dfc..1024dba632 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -3,6 +3,7 @@ #include "cli.h" #include "common.h" +#include "scrcpy.h" static void test_flag_version(void) { struct scrcpy_cli_args args = { @@ -122,6 +123,43 @@ static void test_options2(void) { assert(opts->record_format == SC_RECORD_FORMAT_MP4); } +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("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); + assert(ok); + assert(mods.count == 3); + assert(mods.data[0] == SC_MOD_LSUPER); + assert(mods.data[1] == (SC_MOD_RSUPER | 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; @@ -130,5 +168,6 @@ int main(int argc, char *argv[]) { test_flag_help(); test_options(); test_options2(); + test_parse_shortcut_mods(); return 0; }; From e4bb7c1d1feb51e3277db086207833611fc20603 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 05/17] Accept Super as shortcut modifier In addition to (left) Alt, also accept (left) Super. This is especially convenient for macOS users (Super is the Cmd key). --- README.md | 2 +- app/scrcpy.1 | 4 ++-- app/src/cli.c | 5 +++-- app/src/scrcpy.h | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7eb5578abc..3aa9180c97 100644 --- a/README.md +++ b/README.md @@ -572,7 +572,7 @@ Also see [issue #14]. ## Shortcuts In the following list, `MOD` is the shortcut modifier. By default, it's (left) -`Alt`. +`Alt` or (left) `Super`. It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper`. For example: diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7ea778f354..d79b3964ce 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -157,7 +157,7 @@ A shortcut can consist in several keys, separated by '+'. Several shortcuts can For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". -Default is "lalt" (left-Alt). +Default is "lalt,lsuper" (left-Alt or left-Super). .TP .B \-S, \-\-turn\-screen\-off @@ -218,7 +218,7 @@ Default is 0 (automatic).\n .SH SHORTCUTS In the following list, MOD is the shortcut modifier. By default, it's (left) -Alt, but it can be configured by \-\-shortcut-mod. +Alt or (left) Super, but it can be configured by \-\-shortcut-mod. .TP .B MOD+f diff --git a/app/src/cli.c b/app/src/cli.c index de32810e2a..216763a2ab 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -149,7 +149,7 @@ scrcpy_print_usage(const char *arg0) { " For example, to use either LCtrl+LAlt or LSuper for scrcpy\n" " shortcuts, pass \"lctrl+lalt,lsuper\".\n" "\n" - " Default is \"lalt\" (left-Alt).\n" + " Default is \"lalt,lsuper\" (left-Alt or left-Super).\n" "\n" " -S, --turn-screen-off\n" " Turn the device screen off immediately.\n" @@ -199,7 +199,8 @@ scrcpy_print_usage(const char *arg0) { "Shortcuts:\n" "\n" " In the following list, MOD is the shortcut modifier. By default,\n" - " it's (left) Alt, but it can be configured by --shortcut-mod.\n" + " it's (left) Alt or (left) Super, but it can be configured by\n" + " --shortcut-mod.\n" "\n" " MOD+f\n" " Switch fullscreen mode\n" diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 220fcc2c6a..05af002fd0 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -95,8 +95,8 @@ struct scrcpy_options { .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ }, \ .shortcut_mods = { \ - .data = {SC_MOD_LALT}, \ - .count = 1, \ + .data = {SC_MOD_LALT, SC_MOD_LSUPER}, \ + .count = 2, \ }, \ .max_size = DEFAULT_MAX_SIZE, \ .bit_rate = DEFAULT_BIT_RATE, \ From a5f8b577c50e9498c43f7d4d9a25287673244c0f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 01:23:08 +0200 Subject: [PATCH 06/17] 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. --- app/src/input_manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index b954fbd929..78cf19a70b 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -269,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 == ' ') { From e6e528f228f762336ad6b37679c592cf46f6c4cf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 07/17] 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 --- app/src/event_converter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/event_converter.c b/app/src/event_converter.c index 1054dcf9ec..fb835fa3bc 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -92,6 +92,8 @@ 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); } if (!(mod & (KMOD_NUM | KMOD_SHIFT))) { @@ -111,8 +113,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; } From d4ca85d6a83ee7bdb3dce648b9739374ae770662 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 08/17] Forward Shift to the device This allows to select text using Shift+(arrow keys). Fixes #942 --- app/src/event_converter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/event_converter.c b/app/src/event_converter.c index fb835fa3bc..ab48898d12 100644 --- a/app/src/event_converter.c +++ b/app/src/event_converter.c @@ -94,6 +94,8 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod, 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))) { From 7683be8159c7372c7def0b868ba76a426fa7426e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 09/17] 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. --- app/src/input_manager.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 78cf19a70b..625f79a7d9 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -326,13 +326,15 @@ input_manager_process_key(struct input_manager *im, struct controller *controller = im->controller; + 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) { - SDL_Keycode keycode = event->keysym.sym; - bool down = event->type == SDL_KEYDOWN; 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: if (control && !shift && !repeat) { @@ -457,6 +459,12 @@ input_manager_process_key(struct input_manager *im, 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, im->repeat)) { if (!controller_push_msg(controller, &msg)) { From 1223a72eb83ebafb878a25356250896c45b5cefa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 10/17] 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. --- server/src/main/java/com/genymobile/scrcpy/Device.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 375c9ed6b6..de551f356c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -221,6 +221,16 @@ public boolean setClipboardText(String text) { 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 = clipboardManager.setText(text); isSettingClipboard.set(false); From 20d39250997635ef882df4e2c11144e5f9f82448 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 11/17] 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 --- app/src/receiver.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/receiver.c b/app/src/receiver.c index 5ecff3b78e..307eb5d580 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -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; + } } } From bccd12bf5c268bdf096ef81971f13703c97bf474 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 12/17] 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. --- README.md | 2 -- app/scrcpy.1 | 4 ---- app/src/cli.c | 3 --- app/src/input_manager.c | 15 --------------- 4 files changed, 24 deletions(-) diff --git a/README.md b/README.md index 3aa9180c97..01c5df71da 100644 --- a/README.md +++ b/README.md @@ -504,7 +504,6 @@ requested orientation. It is possible to synchronize clipboards between the computer and the device, in both directions: - - `MOD`+`c` copies the device clipboard to the computer clipboard; - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and pastes if the device runs Android >= 7); - `MOD`+`v` _pastes_ the computer clipboard as a sequence of text events (but @@ -609,7 +608,6 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Rotate device screen | `MOD`+`r` | Expand notification panel | `MOD`+`n` | Collapse notification panel | `MOD`+`Shift`+`n` - | Copy device clipboard to computer | `MOD`+`c` | Paste computer clipboard to device | `MOD`+`v` | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` | Enable/disable FPS counter (on stdout) | `MOD`+`i` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d79b3964ce..0353f59b94 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -292,10 +292,6 @@ Expand notification panel .B MOD+Shift+n Collapse notification panel -.TP -.B MOD+c -Copy device clipboard to computer - .TP .B MOD+v Paste computer clipboard to device diff --git a/app/src/cli.c b/app/src/cli.c index 216763a2ab..95525401bd 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -260,9 +260,6 @@ scrcpy_print_usage(const char *arg0) { " MOD+Shift+n\n" " Collapse notification panel\n" "\n" - " MOD+c\n" - " Copy device clipboard to computer\n" - "\n" " MOD+v\n" " Paste computer clipboard to device\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 625f79a7d9..099906cb38 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -160,16 +160,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(); @@ -392,11 +382,6 @@ input_manager_process_key(struct input_manager *im, rotate_client_right(im->screen); } return; - case SDLK_c: - if (control && !shift && !repeat && down) { - request_device_clipboard(controller); - } - return; case SDLK_v: if (control && !repeat && down) { if (shift) { From 8f64a5984b35af115aeb106ac7cb3731f83472ad Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 13/17] Change "resize to fit" shortcut to MOD+w For convenience, MOD+x will be used for injecting the CUT keycode. --- README.md | 2 +- app/scrcpy.1 | 2 +- app/src/cli.c | 2 +- app/src/input_manager.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 01c5df71da..5bfd32d427 100644 --- a/README.md +++ b/README.md @@ -594,7 +594,7 @@ _[Super] is typically the "Windows" or "Cmd" key._ | 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`+`x` \| _Double-click¹_ + | 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` diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 0353f59b94..ee32c8fbe0 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -237,7 +237,7 @@ Rotate display right Resize window to 1:1 (pixel\-perfect) .TP -.B MOD+x, Double\-click on black borders +.B MOD+w, Double\-click on black borders Resize window to remove black borders .TP diff --git a/app/src/cli.c b/app/src/cli.c index 95525401bd..0662a552e8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -214,7 +214,7 @@ scrcpy_print_usage(const char *arg0) { " MOD+g\n" " Resize window to 1:1 (pixel-perfect)\n" "\n" - " MOD+x\n" + " MOD+w\n" " Double-click on black borders\n" " Resize window to remove black borders\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 099906cb38..580f1a6799 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -398,7 +398,7 @@ input_manager_process_key(struct input_manager *im, screen_switch_fullscreen(im->screen); } return; - case SDLK_x: + case SDLK_w: if (!shift && !repeat && down) { screen_resize_to_fit(im->screen); } From 56a115b5c5971b7035609d5e0e3eabf97e91f874 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 14/17] 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. --- README.md | 5 ++++- app/scrcpy.1 | 8 ++++++++ app/src/cli.c | 6 ++++++ app/src/input_manager.c | 20 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bfd32d427..96d3926b1a 100644 --- a/README.md +++ b/README.md @@ -608,12 +608,15 @@ _[Super] is typically the "Windows" or "Cmd" key._ | 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` | Paste computer clipboard to device | `MOD`+`v` | Copy computer clipboard to device and paste | `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._ +_²Right-click turns the screen on if it was off, presses BACK otherwise._ +_³Only on Android >= 7._ ## Custom paths diff --git a/app/scrcpy.1 b/app/scrcpy.1 index ee32c8fbe0..9193171e48 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -292,6 +292,14 @@ Expand notification panel .B MOD+Shift+n Collapse notification panel +.TP +.B Mod+c +Copy to clipboard (inject COPY keycode, Android >= 7 only) + +.TP +.B Mod+x +Cut to clipboard (inject CUT keycode, Android >= 7 only) + .TP .B MOD+v Paste computer clipboard to device diff --git a/app/src/cli.c b/app/src/cli.c index 0662a552e8..ff810aedd1 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -260,6 +260,12 @@ scrcpy_print_usage(const char *arg0) { " MOD+Shift+n\n" " Collapse notification panel\n" "\n" + " MOD+c\n" + " Copy to clipboard (inject COPY keycode, Android >= 7 only)\n" + "\n" + " MOD+x\n" + " Cut to clipboard (inject CUT keycode, Android >= 7 only)\n" + "\n" " MOD+v\n" " Paste computer clipboard to device\n" "\n" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 580f1a6799..75808f7562 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -129,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) { @@ -382,6 +392,16 @@ input_manager_process_key(struct input_manager *im, rotate_client_right(im->screen); } return; + case SDLK_c: + 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 && !repeat && down) { if (shift) { From 7ad47dfaab57a7e6c7462d7e6a2798a5f122ebaf Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 15/17] 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. --- README.md | 4 ++-- app/scrcpy.1 | 4 ++-- app/src/cli.c | 6 +++--- app/src/input_manager.c | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 96d3926b1a..c20be5083f 100644 --- a/README.md +++ b/README.md @@ -610,8 +610,8 @@ _[Super] is typically the "Windows" or "Cmd" key._ | Collapse notification panel | `MOD`+`Shift`+`n` | Copy to clipboard³ | `MOD`+`c` | Cut to clipboard³ | `MOD`+`x` - | Paste computer clipboard to device | `MOD`+`v` - | Copy computer clipboard to device and paste | `MOD`+`Shift`+`v` + | 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._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 9193171e48..728d14fe0b 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -302,11 +302,11 @@ Cut to clipboard (inject CUT keycode, Android >= 7 only) .TP .B MOD+v -Paste computer clipboard to device +Copy computer clipboard to device, then paste (inject PASTE keycode, Android >= 7 only) .TP .B MOD+Shift+v -Copy computer clipboard to device (and paste if the device runs Android >= 7) +Inject computer clipboard text as a sequence of key events .TP .B MOD+i diff --git a/app/src/cli.c b/app/src/cli.c index ff810aedd1..b75ab41a44 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -267,11 +267,11 @@ scrcpy_print_usage(const char *arg0) { " Cut to clipboard (inject CUT keycode, Android >= 7 only)\n" "\n" " MOD+v\n" - " Paste computer clipboard to device\n" + " Copy computer clipboard to device, then paste (inject PASTE\n" + " keycode, Android >= 7 only)\n" "\n" " MOD+Shift+v\n" - " Copy computer clipboard to device (and paste if the device\n" - " runs Android >= 7)\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" diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 75808f7562..ec1d5a4efc 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -405,11 +405,11 @@ input_manager_process_key(struct input_manager *im, case SDLK_v: 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; From d8b3ba170cb005e67be7dc866aa91134c4101edd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 16/17] Update copy-paste section in README Update documentation regarding the recent copy-paste changes. --- README.md | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c20be5083f..0a8df97396 100644 --- a/README.md +++ b/README.md @@ -501,16 +501,35 @@ requested orientation. #### Copy-paste -It is possible to synchronize clipboards between the computer and the device, in -both directions: - - - `MOD`+`Shift`+`v` copies the computer clipboard to the device clipboard (and - pastes if the device runs Android >= 7); - - `MOD`+`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. +Any time the Android clipboard changes, it is automatically synchronized to the +computer clipboard. + +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) + +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 such 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 into 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 From dfb7324d7b06e649c8355164a7d97bd592ef5af8 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Jul 2020 00:00:42 +0200 Subject: [PATCH 17/17] Mention in README that Ctrl is forwarded --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0a8df97396..6df68fde09 100644 --- a/README.md +++ b/README.md @@ -637,6 +637,9 @@ _¹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