Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8da10eb4af | ||
|
|
b330ba448b | ||
|
|
a98b74df3d | ||
|
|
567c5a7da7 | ||
|
|
9badd2bdf0 | ||
|
|
31cee2c49f | ||
|
|
51d969b20c | ||
|
|
149de2f788 | ||
|
|
26cadf1022 | ||
|
|
f650b0195f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,5 @@
|
||||
build/
|
||||
/dist/
|
||||
/build-*/
|
||||
/build_*/
|
||||
/release-*/
|
||||
.idea/
|
||||
.gradle/
|
||||
/x/
|
||||
|
||||
89
README.md
89
README.md
@@ -354,7 +354,7 @@ scrcpy --fullscreen
|
||||
scrcpy -f # short version
|
||||
```
|
||||
|
||||
Fullscreen can then be toggled dynamically with `RCtrl`+`f`.
|
||||
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
||||
|
||||
#### Rotation
|
||||
|
||||
@@ -370,18 +370,18 @@ Possibles values are:
|
||||
- `2`: 180 degrees
|
||||
- `3`: 90 degrees clockwise
|
||||
|
||||
The rotation can also be changed dynamically with `RCtrl`+`←` _(left)_ and
|
||||
`RCtrl`+`→` _(right)_.
|
||||
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
|
||||
`Ctrl`+`→` _(right)_.
|
||||
|
||||
Note that _scrcpy_ manages 3 different rotations:
|
||||
- `RCtrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
|
||||
current running app may refuse, if it does support the requested
|
||||
orientation).
|
||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||
of the video sent from the device to the computer). This affects the
|
||||
recording.
|
||||
- `--rotation` (or `RCtrl`+`←`/`RCtrl`+`→`) rotates only the window content.
|
||||
This affects only the display, not the recording.
|
||||
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
||||
affects only the display, not the recording.
|
||||
|
||||
|
||||
### Other mirroring options
|
||||
@@ -437,9 +437,9 @@ scrcpy --turn-screen-off
|
||||
scrcpy -S
|
||||
```
|
||||
|
||||
Or by pressing `RCtrl`+`o` at any time.
|
||||
Or by pressing `Ctrl`+`o` at any time.
|
||||
|
||||
To turn it back on, press `RCtrl`+`Shift`+`o` (or `POWER`, `RCtrl`+`p`).
|
||||
To turn it back on, press `Ctrl`+`Shift`+`o` (or `POWER`, `Ctrl`+`p`).
|
||||
|
||||
It can be useful to also prevent the device to sleep:
|
||||
|
||||
@@ -483,22 +483,24 @@ Note that it only shows _physical_ touches (with the finger on the device).
|
||||
|
||||
#### Rotate device screen
|
||||
|
||||
Press `RCtrl`+`r` to switch between portrait and landscape modes.
|
||||
Press `Ctrl`+`r` to switch between portrait and landscape modes.
|
||||
|
||||
Note that it rotates only if the application in foreground supports the
|
||||
requested orientation.
|
||||
|
||||
#### Copy-paste
|
||||
|
||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||
computer clipboard.
|
||||
It is possible to synchronize clipboards between the computer and the device, in
|
||||
both directions:
|
||||
|
||||
`Ctrl`+`c` (copy), `Ctrl`+`x` (cut) and `LCtrl`+`v` (paste) work as you expect.
|
||||
|
||||
In addition, `RCtrl`+`v` allows to inject the computer clipboard content as a
|
||||
sequence of text event. Even if it can break non-ASCII content, this is
|
||||
sometimes necessary when pasting directly is not possible.
|
||||
- `Ctrl`+`c` copies the device clipboard to the computer clipboard;
|
||||
- `Ctrl`+`Shift`+`v` copies the computer clipboard to the device clipboard (and
|
||||
pastes if the device runs Android >= 7);
|
||||
- `Ctrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||
breaks non-ASCII characters).
|
||||
|
||||
Moreover, any time the Android clipboard changes, it is automatically
|
||||
synchronized to the computer clipboard.
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
@@ -558,37 +560,36 @@ Also see [issue #14].
|
||||
|
||||
## Shortcuts
|
||||
|
||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
||||
device).
|
||||
_`Meta` is typically the `Windows` key on the keyboard, or `Cmd` on macOS._
|
||||
|
||||
On macOS, `Cmd` also works (for shortcuts which are not already captured by the
|
||||
system).
|
||||
|
||||
| Action | Shortcut
|
||||
| ------------------------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `RCtrl`+`f`
|
||||
| Rotate display left | `RCtrl`+`←`
|
||||
| Rotate display right | `RCtrl`+`→`
|
||||
| Resize window to 1:1 (pixel-perfect) | `RCtrl`+`g`
|
||||
| Resize window to remove black borders | `RCtrl`+`w` \| _Double-click¹_
|
||||
| Click on `HOME` | `RCtrl`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `RCtrl`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `RCtrl`+`s`
|
||||
| Click on `MENU` | `RCtrl`+`m`
|
||||
| Click on `VOLUME_UP` | `RCtrl`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `RCtrl`+`↓` _(down)_
|
||||
| Click on `POWER` | `RCtrl`+`p`
|
||||
| Power on | _Right-click²_
|
||||
| Turn device screen off (keep mirroring) | `RCtrl`+`o`
|
||||
| Turn device screen on | `RCtrl`+`Shift`+`o`
|
||||
| Rotate device screen | `RCtrl`+`r`
|
||||
| Expand notification panel | `RCtrl`+`n`
|
||||
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
||||
| Inject computer clipboard text | `RCtrl`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
|
||||
| Action | Shortcut
|
||||
| ---------------------------------------------------- |:-----------------------------
|
||||
| Switch fullscreen mode | `Meta`+`f`
|
||||
| Rotate display left | `Meta`+`←` _(left)_
|
||||
| Rotate display right | `Meta`+`→` _(right)_
|
||||
| Resize window to 1:1 (pixel-perfect) | `Meta`+`g`
|
||||
| Resize window to remove black borders | `Meta`+`w` \| _Double-click¹_
|
||||
| Click on `HOME` | `Meta`+`h` \| _Middle-click_
|
||||
| Click on `BACK` | `Meta`+`b` \| _Right-click²_
|
||||
| Click on `APP_SWITCH` | `Meta`+`s`
|
||||
| Click on `MENU` | `Meta`+`m`
|
||||
| Click on `VOLUME_UP` | `Meta`+`↑` _(up)_
|
||||
| Click on `VOLUME_DOWN` | `Meta`+`↓` _(down)_
|
||||
| Click on `POWER` | `Meta`+`p`
|
||||
| Power on | _Right-click²_
|
||||
| Turn device screen off (keep mirroring) | `Meta`+`o`
|
||||
| Turn device screen on | `Meta`+`Shift`+`o`
|
||||
| Rotate device screen | `Meta`+`r`
|
||||
| Expand notification panel | `Meta`+`n`
|
||||
| Collapse notification panel | `Meta`+`Shift`+`n`
|
||||
| Press COPY³, then Copy device clipboard to computer | `Meta`+`c`
|
||||
| Press CUT³ | `Meta`+`x`
|
||||
| Paste computer clipboard to device | `Meta`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `Meta`+`i`
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
_³Only if the device runs Android >= 7._
|
||||
|
||||
|
||||
## Custom paths
|
||||
|
||||
50
app/scrcpy.1
50
app/scrcpy.1
@@ -203,54 +203,52 @@ Default is 0 (automatic).\n
|
||||
|
||||
.SH SHORTCUTS
|
||||
|
||||
RCtrl is the right Ctrl key (the left Ctrl key is forwarded to the device).
|
||||
|
||||
.TP
|
||||
.B RCtrl+f
|
||||
.B Meta+f
|
||||
Switch fullscreen mode
|
||||
|
||||
.TP
|
||||
.B RCtrl+Left
|
||||
.B Meta+Left
|
||||
Rotate display left
|
||||
|
||||
.TP
|
||||
.B RCtrl+Right
|
||||
.B Meta+Right
|
||||
Rotate display right
|
||||
|
||||
.TP
|
||||
.B RCtrl+g
|
||||
.B Meta+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
||||
.TP
|
||||
.B RCtrl+w, Double\-click on black borders
|
||||
.B Meta+w, Double\-click on black borders
|
||||
Resize window to remove black borders
|
||||
|
||||
.TP
|
||||
.B RCtrl+h, Home, Middle\-click
|
||||
.B Meta+h, Home, Middle\-click
|
||||
Click on HOME
|
||||
|
||||
.TP
|
||||
.B RCtrl+b, RCtrl+Backspace, Right\-click (when screen is on)
|
||||
.B Meta+b, Meta+Backspace, Right\-click (when screen is on)
|
||||
Click on BACK
|
||||
|
||||
.TP
|
||||
.B RCtrl+s
|
||||
.B Meta+s
|
||||
Click on APP_SWITCH
|
||||
|
||||
.TP
|
||||
.B RCtrl+m
|
||||
.B Meta+m
|
||||
Click on MENU
|
||||
|
||||
.TP
|
||||
.B RCtrl+Up
|
||||
.B Meta+Up
|
||||
Click on VOLUME_UP
|
||||
|
||||
.TP
|
||||
.B RCtrl+Down
|
||||
.B Meta+Down
|
||||
Click on VOLUME_DOWN
|
||||
|
||||
.TP
|
||||
.B RCtrl+p
|
||||
.B Meta+p
|
||||
Click on POWER (turn screen on/off)
|
||||
|
||||
.TP
|
||||
@@ -258,31 +256,39 @@ Click on POWER (turn screen on/off)
|
||||
Turn screen on
|
||||
|
||||
.TP
|
||||
.B RCtrl+o
|
||||
.B Meta+o
|
||||
Turn device screen off (keep mirroring)
|
||||
|
||||
.TP
|
||||
.B RCtrl+Shift+o
|
||||
.B Meta+Shift+o
|
||||
Turn device screen on
|
||||
|
||||
.TP
|
||||
.B RCtrl+r
|
||||
.B Meta+r
|
||||
Rotate device screen
|
||||
|
||||
.TP
|
||||
.B RCtrl+n
|
||||
.B Meta+n
|
||||
Expand notification panel
|
||||
|
||||
.TP
|
||||
.B RCtrl+Shift+n
|
||||
.B Meta+Shift+n
|
||||
Collapse notification panel
|
||||
|
||||
.TP
|
||||
.B RCtrl+v
|
||||
Inject computer clipboard text
|
||||
.B Meta+c
|
||||
Press COPY (Android >= 7), then copy device clipboard to computer
|
||||
|
||||
.TP
|
||||
.B RCtrl+i
|
||||
.B Meta+x
|
||||
Press CUT (Android >= 7)
|
||||
|
||||
.TP
|
||||
.B Meta+v
|
||||
Paste computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B Meta+i
|
||||
Enable/disable FPS counter (print frames/second in logs)
|
||||
|
||||
.TP
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
|
||||
void
|
||||
scrcpy_print_usage(const char *arg0) {
|
||||
#ifdef __APPLE__
|
||||
# define MOD "Cmd"
|
||||
#else
|
||||
# define MOD "Meta"
|
||||
#endif
|
||||
fprintf(stderr,
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
@@ -181,71 +186,75 @@ scrcpy_print_usage(const char *arg0) {
|
||||
"\n"
|
||||
"Shortcuts:\n"
|
||||
"\n"
|
||||
" RCtrl is the right Ctrl key (the left Ctrl key is forwarded to\n"
|
||||
" the device.\n"
|
||||
"\n"
|
||||
" RCtrl+f\n"
|
||||
" " MOD "+f\n"
|
||||
" Switch fullscreen mode\n"
|
||||
"\n"
|
||||
" RCtrl+Left\n"
|
||||
" " MOD "+Left\n"
|
||||
" Rotate display left\n"
|
||||
"\n"
|
||||
" RCtrl+Right\n"
|
||||
" " MOD "+Right\n"
|
||||
" Rotate display right\n"
|
||||
"\n"
|
||||
" RCtrl+g\n"
|
||||
" " MOD "+g\n"
|
||||
" Resize window to 1:1 (pixel-perfect)\n"
|
||||
"\n"
|
||||
" RCtrl+w\n"
|
||||
" " MOD "+w\n"
|
||||
" Double-click on black borders\n"
|
||||
" Resize window to remove black borders\n"
|
||||
"\n"
|
||||
" RCtrl+h\n"
|
||||
" Ctrl+h\n"
|
||||
" Middle-click\n"
|
||||
" Click on HOME\n"
|
||||
"\n"
|
||||
" RCtrl+b\n"
|
||||
" RCtrl+Backspace\n"
|
||||
" " MOD "+b\n"
|
||||
" " MOD "+Backspace\n"
|
||||
" Right-click (when screen is on)\n"
|
||||
" Click on BACK\n"
|
||||
"\n"
|
||||
" RCtrl+s\n"
|
||||
" " MOD "+s\n"
|
||||
" Click on APP_SWITCH\n"
|
||||
"\n"
|
||||
" RCtrl+m\n"
|
||||
" Ctrl+m\n"
|
||||
" Click on MENU\n"
|
||||
"\n"
|
||||
" RCtrl+Up\n"
|
||||
" " MOD "+Up\n"
|
||||
" Click on VOLUME_UP\n"
|
||||
"\n"
|
||||
" RCtrl+Down\n"
|
||||
" " MOD "+Down\n"
|
||||
" Click on VOLUME_DOWN\n"
|
||||
"\n"
|
||||
" RCtrl+p\n"
|
||||
" " MOD "+p\n"
|
||||
" Click on POWER (turn screen on/off)\n"
|
||||
"\n"
|
||||
" Right-click (when screen is off)\n"
|
||||
" Power on\n"
|
||||
"\n"
|
||||
" RCtrl+o\n"
|
||||
" " MOD "+o\n"
|
||||
" Turn device screen off (keep mirroring)\n"
|
||||
"\n"
|
||||
" RCtrl+Shift+o\n"
|
||||
" " MOD "+Shift+o\n"
|
||||
" Turn device screen on\n"
|
||||
"\n"
|
||||
" RCtrl+r\n"
|
||||
" " MOD "+r\n"
|
||||
" Rotate device screen\n"
|
||||
"\n"
|
||||
" RCtrl+n\n"
|
||||
" " MOD "+n\n"
|
||||
" Expand notification panel\n"
|
||||
"\n"
|
||||
" RCtrl+Shift+n\n"
|
||||
" " MOD "+Shift+n\n"
|
||||
" Collapse notification panel\n"
|
||||
"\n"
|
||||
" RCtrl+v\n"
|
||||
" Inject computer clipboard text\n"
|
||||
" " MOD "+c\n"
|
||||
" Press COPY (Android >= 7), then copy device clipboard to\n"
|
||||
" computer\n"
|
||||
"\n"
|
||||
" RCtrl+i\n"
|
||||
" " MOD "+x\n"
|
||||
" Press CUT (Android >= 7)\n"
|
||||
"\n"
|
||||
" " MOD "+v\n"
|
||||
" Paste computer clipboard to device\n"
|
||||
"\n"
|
||||
" " MOD "+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
"\n"
|
||||
" Drag & drop APK file\n"
|
||||
@@ -256,6 +265,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
|
||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
|
||||
#undef MOD
|
||||
}
|
||||
|
||||
static bool
|
||||
|
||||
@@ -66,6 +66,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
buffer_write32be(&buf[17],
|
||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||
return 21;
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
buf[1] = msg->get_clipboard.copy;
|
||||
return 2;
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||
buf[1] = !!msg->set_clipboard.paste;
|
||||
size_t len = write_string(msg->set_clipboard.text,
|
||||
@@ -79,7 +82,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
|
||||
@@ -60,6 +60,9 @@ struct control_msg {
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
} inject_scroll_event;
|
||||
struct {
|
||||
bool copy;
|
||||
} get_clipboard;
|
||||
struct {
|
||||
char *text; // owned, to be freed by SDL_free()
|
||||
bool paste;
|
||||
|
||||
@@ -93,6 +93,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
|
||||
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
|
||||
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
|
||||
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
|
||||
}
|
||||
@@ -114,7 +115,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
}
|
||||
}
|
||||
|
||||
if (prefer_text && !(mod & KMOD_LCTRL)) {
|
||||
if (prefer_text) {
|
||||
// do not forward alpha and space key events
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ action_menu(struct controller *controller, int actions) {
|
||||
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
||||
}
|
||||
|
||||
static inline void
|
||||
action_cut(struct controller *controller, int actions) {
|
||||
send_keycode(controller, AKEYCODE_CUT, actions, "CUT");
|
||||
}
|
||||
|
||||
// turn the screen on if it was off, press BACK otherwise
|
||||
static void
|
||||
press_back_or_turn_screen_on(struct controller *controller) {
|
||||
@@ -101,6 +106,17 @@ collapse_notification_panel(struct controller *controller) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
request_device_clipboard(struct controller *controller, bool copy) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||
msg.get_clipboard.copy = copy;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_device_clipboard(struct controller *controller, bool paste) {
|
||||
char *text = SDL_GetClipboardText();
|
||||
@@ -242,25 +258,6 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
inject_as_ctrl(struct input_manager *im, const SDL_KeyboardEvent *event) {
|
||||
struct control_msg msg;
|
||||
|
||||
if (!convert_input_key(event, &msg, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable RCtrl and Meta
|
||||
msg.inject_keycode.metastate &=
|
||||
~(AMETA_CTRL_RIGHT_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
|
||||
// Enable LCtrl
|
||||
msg.inject_keycode.metastate |= AMETA_CTRL_LEFT_ON;
|
||||
|
||||
if (!controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
input_manager_process_key(struct input_manager *im,
|
||||
const SDL_KeyboardEvent *event,
|
||||
@@ -268,22 +265,11 @@ input_manager_process_key(struct input_manager *im,
|
||||
// control: indicates the state of the command-line option --no-control
|
||||
// ctrl: the Ctrl key
|
||||
|
||||
bool lctrl = event->keysym.mod & KMOD_LCTRL;
|
||||
bool rctrl = event->keysym.mod & KMOD_RCTRL;
|
||||
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
|
||||
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
|
||||
bool shortcut_key = rctrl;
|
||||
#ifdef __APPLE__
|
||||
shortcut_key |= meta;
|
||||
#else
|
||||
if (meta) {
|
||||
// No shortcut involve Meta, and it is not forwarded to the device
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (alt) {
|
||||
// No shortcuts involve Alt, and it is not forwarded to the device
|
||||
return;
|
||||
@@ -294,14 +280,17 @@ input_manager_process_key(struct input_manager *im,
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
|
||||
// Capture all RCtrl events
|
||||
if (shortcut_key) {
|
||||
// Capture all Meta events
|
||||
if (meta) {
|
||||
if (ctrl) {
|
||||
// No shortcuts involve Ctrl+Meta
|
||||
return;
|
||||
}
|
||||
|
||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||
bool repeat = event->repeat;
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||
// the system on macOS to hide the window
|
||||
if (control && !shift && !repeat) {
|
||||
action_home(controller, action);
|
||||
}
|
||||
@@ -318,8 +307,6 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
// Ctrl+m on all platform, since Cmd+m is already captured by
|
||||
// the system on macOS to minimize the window
|
||||
if (control && !shift && !repeat) {
|
||||
action_menu(controller, action);
|
||||
}
|
||||
@@ -359,6 +346,20 @@ input_manager_process_key(struct input_manager *im,
|
||||
rotate_client_right(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && !shift && !repeat && down) {
|
||||
// Press COPY, then get the clipboard content
|
||||
request_device_clipboard(controller, true);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (control && !shift && !repeat && down) {
|
||||
// For convenience (especially on macOS), bind Meta+x to
|
||||
// CUT (even if it is already accessible by pressing Ctrl+x
|
||||
// on the device)
|
||||
action_cut(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (control && !shift && !repeat && down) {
|
||||
// Inject the text as input events
|
||||
@@ -401,15 +402,6 @@ input_manager_process_key(struct input_manager *im,
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
case SDLK_x:
|
||||
if (control && !shift) {
|
||||
// For convenience, forward shortcut_key+c and
|
||||
// shortcut_key+x as Ctrl+c and Ctrl+x (typically "copy" and
|
||||
// "cut", but not always) to the device
|
||||
inject_as_ctrl(im, event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -419,9 +411,11 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (lctrl && !shift && keycode == SDLK_v && down) {
|
||||
assert(!meta);
|
||||
|
||||
if (ctrl && !shift && keycode == SDLK_v && down) {
|
||||
// Synchronize the computer clipboard to the device clipboard before
|
||||
// sending Ctrl+V, to allow seamless copy-paste.
|
||||
// sending Ctrl+v
|
||||
set_device_clipboard(controller, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ convert_log_level_to_sdl(enum sc_log_level level) {
|
||||
return SDL_LOG_PRIORITY_ERROR;
|
||||
default:
|
||||
assert(!"unexpected log level");
|
||||
return SDL_LOG_PRIORITY_INFO;
|
||||
return SC_LOG_LEVEL_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||
SDL_LogSetAllPriority(sdl_log);
|
||||
|
||||
if (args.help) {
|
||||
scrcpy_print_usage(argv[0]);
|
||||
|
||||
@@ -185,14 +185,18 @@ static void test_serialize_collapse_notification_panel(void) {
|
||||
static void test_serialize_get_clipboard(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
.get_clipboard = {
|
||||
.copy = true,
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||
int size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
assert(size == 2);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
1, // copy
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public final class ControlMessage {
|
||||
private Position position;
|
||||
private int hScroll;
|
||||
private int vScroll;
|
||||
private boolean paste;
|
||||
private boolean pressCopyOrPaste;
|
||||
|
||||
private ControlMessage() {
|
||||
}
|
||||
@@ -69,11 +69,18 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createGetClipboard(boolean copy) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_GET_CLIPBOARD;
|
||||
msg.pressCopyOrPaste = copy;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
msg.text = text;
|
||||
msg.paste = paste;
|
||||
msg.pressCopyOrPaste = paste;
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -137,7 +144,7 @@ public final class ControlMessage {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public boolean getPaste() {
|
||||
return paste;
|
||||
public boolean getPressCopyOrPaste() {
|
||||
return pressCopyOrPaste;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ControlMessageReader {
|
||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||
static final int GET_CLIPBOARD_PAYLOAD_LENGTH = 1;
|
||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||
|
||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
|
||||
@@ -21,6 +22,7 @@ public class ControlMessageReader {
|
||||
|
||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||
private final byte[] textBuffer = new byte[CLIPBOARD_TEXT_MAX_LENGTH];
|
||||
|
||||
public ControlMessageReader() {
|
||||
// invariant: the buffer is always in "get" mode
|
||||
@@ -66,6 +68,9 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||
msg = parseInjectScrollEvent();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
msg = parseGetClipboard();
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
msg = parseSetClipboard();
|
||||
break;
|
||||
@@ -75,7 +80,6 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
msg = ControlMessage.createEmpty(type);
|
||||
break;
|
||||
@@ -110,10 +114,8 @@ public class ControlMessageReader {
|
||||
if (buffer.remaining() < len) {
|
||||
return null;
|
||||
}
|
||||
int position = buffer.position();
|
||||
// Move the buffer position to consume the text
|
||||
buffer.position(position + len);
|
||||
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
||||
buffer.get(textBuffer, 0, len);
|
||||
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private ControlMessage parseInjectText() {
|
||||
@@ -149,6 +151,14 @@ public class ControlMessageReader {
|
||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||
}
|
||||
|
||||
private ControlMessage parseGetClipboard() {
|
||||
if (buffer.remaining() < GET_CLIPBOARD_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
boolean copy = buffer.get() != 0;
|
||||
return ControlMessage.createGetClipboard(copy);
|
||||
}
|
||||
|
||||
private ControlMessage parseSetClipboard() {
|
||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
|
||||
@@ -104,13 +104,10 @@ public class Controller {
|
||||
device.collapsePanels();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
String clipboardText = device.getClipboardText();
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
}
|
||||
getClipboard(msg.getPressCopyOrPaste());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
setClipboard(msg.getText(), msg.getPaste());
|
||||
setClipboard(msg.getText(), msg.getPressCopyOrPaste());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
@@ -228,6 +225,23 @@ public class Controller {
|
||||
return device.injectKeycode(keycode);
|
||||
}
|
||||
|
||||
private boolean getClipboard(boolean copy) {
|
||||
// On Android >= 7, also press the COPY key if requested
|
||||
if (copy && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||
// If there is something to copy, the clipboard will be automatically sent to the computer clipboard via the ClipboardListener
|
||||
return device.injectKeycode(KeyEvent.KEYCODE_COPY);
|
||||
}
|
||||
|
||||
// We can't press COPY, so only synchronize the current clipboard
|
||||
String clipboardText = device.getClipboardText();
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean setClipboard(String text, boolean paste) {
|
||||
boolean ok = device.setClipboardText(text);
|
||||
if (ok) {
|
||||
|
||||
@@ -207,14 +207,6 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean setClipboardText(String text) {
|
||||
String currentClipboard = getClipboardText();
|
||||
if (currentClipboard == null || currentClipboard.equals(text)) {
|
||||
// The clipboard already contains the requested text.
|
||||
// Since pasting text from the computer involves setting the device clipboard, it could be set twice on a copy-paste. This would cause
|
||||
// the clipboard listeners to be notified twice, and that would flood the Android keyboard clipboard history. To workaround this
|
||||
// problem, do not explicitly set the clipboard text if it already contains the expected content.
|
||||
return false;
|
||||
}
|
||||
isSettingClipboard.set(true);
|
||||
boolean ok = serviceManager.getClipboardManager().setText(text);
|
||||
isSettingClipboard.set(false);
|
||||
|
||||
@@ -200,6 +200,7 @@ public class ControlMessageReaderTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||
dos.writeByte(1); // copy
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@@ -207,6 +208,7 @@ public class ControlMessageReaderTest {
|
||||
ControlMessage event = reader.next();
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||
Assert.assertTrue(event.getPressCopyOrPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -228,7 +230,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals("testé", event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
Assert.assertTrue(event.getPressCopyOrPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -254,7 +256,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(text, event.getText());
|
||||
Assert.assertTrue(event.getPaste());
|
||||
Assert.assertTrue(event.getPressCopyOrPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user