mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-03 18:54:27 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01e9c2812 | ||
|
|
80f5a7c43d | ||
|
|
ddb36e3436 | ||
|
|
cac1765091 | ||
|
|
f5a14b285b | ||
|
|
65edae0ca6 | ||
|
|
e4a0fada10 | ||
|
|
8a037e3d9b | ||
|
|
b3aa88c751 | ||
|
|
b9602e56d9 | ||
|
|
0c01ac34b4 |
25
README.md
25
README.md
@@ -490,17 +490,15 @@ requested orientation.
|
||||
|
||||
#### Copy-paste
|
||||
|
||||
It is possible to synchronize clipboards between the computer and the device, in
|
||||
both directions:
|
||||
Any time the Android clipboard changes, it is automatically synchronized to the
|
||||
computer clipboard.
|
||||
|
||||
- `RCtrl`+`c` copies the device clipboard to the computer clipboard;
|
||||
- `RCtrl`+`Shift`+`v` copies the computer clipboard to the device clipboard
|
||||
(and pastes if the device runs Android >= 7);
|
||||
- `RCtrl`+`v` _pastes_ the computer clipboard as a sequence of text events (but
|
||||
breaks non-ASCII characters).
|
||||
`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.
|
||||
|
||||
Moreover, any time the Android clipboard changes, it is automatically
|
||||
synchronized to the computer clipboard.
|
||||
|
||||
#### Text injection preference
|
||||
|
||||
@@ -563,13 +561,16 @@ Also see [issue #14].
|
||||
`RCtrl` is the right `Ctrl` key (the left `Ctrl` key is forwarded to the
|
||||
device).
|
||||
|
||||
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`+`x` \| _Double-click¹_
|
||||
| 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`
|
||||
@@ -583,9 +584,7 @@ device).
|
||||
| Rotate device screen | `RCtrl`+`r`
|
||||
| Expand notification panel | `RCtrl`+`n`
|
||||
| Collapse notification panel | `RCtrl`+`Shift`+`n`
|
||||
| Copy device clipboard to computer | `RCtrl`+`c`
|
||||
| Paste computer clipboard to device | `RCtrl`+`v`
|
||||
| Copy computer clipboard to device and paste | `RCtrl`+`Shift`+`v`
|
||||
| Inject computer clipboard text | `RCtrl`+`v`
|
||||
| Enable/disable FPS counter (on stdout) | `RCtrl`+`i`
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
|
||||
12
app/scrcpy.1
12
app/scrcpy.1
@@ -222,7 +222,7 @@ Rotate display right
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
||||
.TP
|
||||
.B RCtrl+x, Double\-click on black borders
|
||||
.B RCtrl+w, Double\-click on black borders
|
||||
Resize window to remove black borders
|
||||
|
||||
.TP
|
||||
@@ -277,17 +277,9 @@ Expand notification panel
|
||||
.B RCtrl+Shift+n
|
||||
Collapse notification panel
|
||||
|
||||
.TP
|
||||
.B RCtrl+c
|
||||
Copy device clipboard to computer
|
||||
|
||||
.TP
|
||||
.B RCtrl+v
|
||||
Paste computer clipboard to device
|
||||
|
||||
.TP
|
||||
.B RCtrl+Shift+v
|
||||
Copy computer clipboard to device (and paste if the device runs Android >= 7)
|
||||
Inject computer clipboard text
|
||||
|
||||
.TP
|
||||
.B RCtrl+i
|
||||
|
||||
@@ -196,7 +196,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" RCtrl+g\n"
|
||||
" Resize window to 1:1 (pixel-perfect)\n"
|
||||
"\n"
|
||||
" RCtrl+x\n"
|
||||
" RCtrl+w\n"
|
||||
" Double-click on black borders\n"
|
||||
" Resize window to remove black borders\n"
|
||||
"\n"
|
||||
@@ -242,15 +242,8 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" RCtrl+Shift+n\n"
|
||||
" Collapse notification panel\n"
|
||||
"\n"
|
||||
" RCtrl+c\n"
|
||||
" Copy device clipboard to computer\n"
|
||||
"\n"
|
||||
" RCtrl+v\n"
|
||||
" Paste computer clipboard to device\n"
|
||||
"\n"
|
||||
" RCtrl+Shift+v\n"
|
||||
" Copy computer clipboard to device (and paste if the device\n"
|
||||
" runs Android >= 7)\n"
|
||||
" Inject computer clipboard text\n"
|
||||
"\n"
|
||||
" RCtrl+i\n"
|
||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||
|
||||
@@ -101,16 +101,6 @@ collapse_notification_panel(struct controller *controller) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
request_device_clipboard(struct controller *controller) {
|
||||
struct control_msg msg;
|
||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
LOGW("Could not request device clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_device_clipboard(struct controller *controller, bool paste) {
|
||||
char *text = SDL_GetClipboardText();
|
||||
@@ -252,6 +242,25 @@ 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,
|
||||
@@ -259,25 +268,36 @@ 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 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);
|
||||
|
||||
if (alt || meta) {
|
||||
// No shortcuts involve Alt or Meta, and they are not forwarded to the
|
||||
// device
|
||||
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;
|
||||
}
|
||||
|
||||
struct controller *controller = im->controller;
|
||||
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
|
||||
// Capture all RCtrl events
|
||||
if (rctrl) {
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
if (shortcut_key) {
|
||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||
bool repeat = event->repeat;
|
||||
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
// Ctrl+h on all platform, since Cmd+h is already captured by
|
||||
@@ -339,20 +359,10 @@ 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) {
|
||||
// store the text in the device clipboard and paste
|
||||
set_device_clipboard(controller, true);
|
||||
} else {
|
||||
// inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
}
|
||||
if (control && !shift && !repeat && down) {
|
||||
// Inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
}
|
||||
return;
|
||||
case SDLK_f:
|
||||
@@ -360,7 +370,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);
|
||||
}
|
||||
@@ -391,6 +401,15 @@ 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;
|
||||
@@ -400,6 +419,12 @@ input_manager_process_key(struct input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (lctrl && !shift && keycode == SDLK_v && down) {
|
||||
// Synchronize the computer clipboard to the device clipboard before
|
||||
// sending Ctrl+V, to allow seamless copy-paste.
|
||||
set_device_clipboard(controller, false);
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
||||
if (!controller_push_msg(controller, &msg)) {
|
||||
|
||||
@@ -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 ControlMessage() {
|
||||
}
|
||||
@@ -75,9 +73,7 @@ public final class ControlMessage {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
msg.text = text;
|
||||
if (paste) {
|
||||
msg.flags = FLAGS_PASTE;
|
||||
}
|
||||
msg.paste = paste;
|
||||
return msg;
|
||||
}
|
||||
|
||||
@@ -141,7 +137,7 @@ public final class ControlMessage {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
public boolean getPaste() {
|
||||
return paste;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ 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
|
||||
@@ -111,8 +110,10 @@ public class ControlMessageReader {
|
||||
if (buffer.remaining() < len) {
|
||||
return null;
|
||||
}
|
||||
buffer.get(textBuffer, 0, len);
|
||||
return new String(textBuffer, 0, len, StandardCharsets.UTF_8);
|
||||
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);
|
||||
}
|
||||
|
||||
private ControlMessage parseInjectText() {
|
||||
|
||||
@@ -110,8 +110,7 @@ public class Controller {
|
||||
}
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
setClipboard(msg.getText(), paste);
|
||||
setClipboard(msg.getText(), msg.getPaste());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
|
||||
@@ -207,6 +207,14 @@ 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);
|
||||
|
||||
@@ -228,9 +228,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals("testé", event.getText());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
Assert.assertTrue(event.getPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -256,9 +254,7 @@ public class ControlMessageReaderTest {
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(text, event.getText());
|
||||
|
||||
boolean parse = (event.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||
Assert.assertTrue(parse);
|
||||
Assert.assertTrue(event.getPaste());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user