Always synchronize clipboard on explicit COPY/CUT
If --no-clipboard-autosync is enabled, the automatic clipboard synchronization performed whenever the device clipboard changes is disabled. But on explicit COPY and CUT scrcpy shortcuts (MOD+c and MOD+x), the clipboard should still be synchronized, so that it remains possible to copy-paste from the device to the computer. This is consistent with the behavior of MOD+v, which pastes the computer clipboard to the device. Refs #2228 <https://github.com/Genymobile/scrcpy/issues/2228> Refs #2817 <https://github.com/Genymobile/scrcpy/pull/2817> PR #2834 <https://github.com/Genymobile/scrcpy/pull/2834>
This commit is contained in:
parent
bfcb9d06c3
commit
e2b3968c66
9 changed files with 102 additions and 26 deletions
|
@ -55,6 +55,12 @@ static const char *const screen_power_mode_labels[] = {
|
||||||
"suspend",
|
"suspend",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *const copy_key_labels[] = {
|
||||||
|
"none",
|
||||||
|
"copy",
|
||||||
|
"cut",
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
write_position(uint8_t *buf, const struct sc_position *position) {
|
||||||
buffer_write32be(&buf[0], position->point.x);
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
|
@ -117,6 +123,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
buf[1] = msg->get_clipboard.copy_key;
|
||||||
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
|
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||||
buf[9] = !!msg->set_clipboard.paste;
|
buf[9] = !!msg->set_clipboard.paste;
|
||||||
|
@ -131,7 +140,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
|
||||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
// no additional data
|
// no additional data
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -194,6 +202,10 @@ control_msg_log(const struct control_msg *msg) {
|
||||||
LOG_CMSG("back-or-screen-on %s",
|
LOG_CMSG("back-or-screen-on %s",
|
||||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||||
break;
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
LOG_CMSG("get clipboard copy_key=%s",
|
||||||
|
copy_key_labels[msg->get_clipboard.copy_key]);
|
||||||
|
break;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
||||||
msg->set_clipboard.sequence,
|
msg->set_clipboard.sequence,
|
||||||
|
@ -213,9 +225,6 @@ control_msg_log(const struct control_msg *msg) {
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
LOG_CMSG("collapse panels");
|
LOG_CMSG("collapse panels");
|
||||||
break;
|
break;
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
|
||||||
LOG_CMSG("get clipboard");
|
|
||||||
break;
|
|
||||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
LOG_CMSG("rotate device");
|
LOG_CMSG("rotate device");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -41,6 +41,12 @@ enum screen_power_mode {
|
||||||
SCREEN_POWER_MODE_NORMAL = 2,
|
SCREEN_POWER_MODE_NORMAL = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum get_clipboard_copy_key {
|
||||||
|
GET_CLIPBOARD_COPY_KEY_NONE,
|
||||||
|
GET_CLIPBOARD_COPY_KEY_COPY,
|
||||||
|
GET_CLIPBOARD_COPY_KEY_CUT,
|
||||||
|
};
|
||||||
|
|
||||||
struct control_msg {
|
struct control_msg {
|
||||||
enum control_msg_type type;
|
enum control_msg_type type;
|
||||||
union {
|
union {
|
||||||
|
@ -69,6 +75,9 @@ struct control_msg {
|
||||||
enum android_keyevent_action action; // action for the BACK key
|
enum android_keyevent_action action; // action for the BACK key
|
||||||
// screen may only be turned on on ACTION_DOWN
|
// screen may only be turned on on ACTION_DOWN
|
||||||
} back_or_screen_on;
|
} back_or_screen_on;
|
||||||
|
struct {
|
||||||
|
enum get_clipboard_copy_key copy_key;
|
||||||
|
} get_clipboard;
|
||||||
struct {
|
struct {
|
||||||
uint64_t sequence;
|
uint64_t sequence;
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
|
|
|
@ -148,16 +148,6 @@ action_menu(struct controller *controller, int actions) {
|
||||||
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
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
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
// If the screen is off, it is turned on only on ACTION_DOWN
|
// If the screen is off, it is turned on only on ACTION_DOWN
|
||||||
static void
|
static void
|
||||||
|
@ -211,6 +201,21 @@ collapse_panels(struct controller *controller) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_device_clipboard(struct controller *controller,
|
||||||
|
enum get_clipboard_copy_key copy_key) {
|
||||||
|
struct control_msg msg;
|
||||||
|
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||||
|
msg.get_clipboard.copy_key = copy_key;
|
||||||
|
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'get device clipboard'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
set_device_clipboard(struct controller *controller, bool paste,
|
set_device_clipboard(struct controller *controller, bool paste,
|
||||||
uint64_t sequence) {
|
uint64_t sequence) {
|
||||||
|
@ -450,13 +455,15 @@ input_manager_process_key(struct input_manager *im,
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_c:
|
case SDLK_c:
|
||||||
if (control && !shift && !repeat) {
|
if (control && !shift && !repeat && down) {
|
||||||
action_copy(controller, action);
|
get_device_clipboard(controller,
|
||||||
|
GET_CLIPBOARD_COPY_KEY_COPY);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_x:
|
||||||
if (control && !shift && !repeat) {
|
if (control && !shift && !repeat && down) {
|
||||||
action_cut(controller, action);
|
get_device_clipboard(controller,
|
||||||
|
GET_CLIPBOARD_COPY_KEY_CUT);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
|
|
|
@ -210,14 +210,18 @@ static void test_serialize_collapse_panels(void) {
|
||||||
static void test_serialize_get_clipboard(void) {
|
static void test_serialize_get_clipboard(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
|
.get_clipboard = {
|
||||||
|
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 2);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
|
GET_CLIPBOARD_COPY_KEY_COPY,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ public final class ControlMessage {
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = 0;
|
public static final long SEQUENCE_INVALID = 0;
|
||||||
|
|
||||||
|
public static final int COPY_KEY_NONE = 0;
|
||||||
|
public static final int COPY_KEY_COPY = 1;
|
||||||
|
public static final int COPY_KEY_CUT = 2;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private int metaState; // KeyEvent.META_*
|
private int metaState; // KeyEvent.META_*
|
||||||
|
@ -31,6 +35,7 @@ public final class ControlMessage {
|
||||||
private Position position;
|
private Position position;
|
||||||
private int hScroll;
|
private int hScroll;
|
||||||
private int vScroll;
|
private int vScroll;
|
||||||
|
private int copyKey;
|
||||||
private boolean paste;
|
private boolean paste;
|
||||||
private int repeat;
|
private int repeat;
|
||||||
private long sequence;
|
private long sequence;
|
||||||
|
@ -82,6 +87,13 @@ public final class ControlMessage {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createGetClipboard(int copyKey) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_GET_CLIPBOARD;
|
||||||
|
msg.copyKey = copyKey;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
|
public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
|
@ -151,6 +163,10 @@ public final class ControlMessage {
|
||||||
return vScroll;
|
return vScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCopyKey() {
|
||||||
|
return copyKey;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getPaste() {
|
public boolean getPaste() {
|
||||||
return paste;
|
return paste;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ public class ControlMessageReader {
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
static final int GET_CLIPBOARD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
@ -70,6 +71,9 @@ public class ControlMessageReader {
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
msg = parseBackOrScreenOnEvent();
|
msg = parseBackOrScreenOnEvent();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
|
msg = parseGetClipboard();
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
msg = parseSetClipboard();
|
msg = parseSetClipboard();
|
||||||
break;
|
break;
|
||||||
|
@ -79,7 +83,6 @@ public class ControlMessageReader {
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
msg = ControlMessage.createEmpty(type);
|
msg = ControlMessage.createEmpty(type);
|
||||||
break;
|
break;
|
||||||
|
@ -162,6 +165,14 @@ public class ControlMessageReader {
|
||||||
return ControlMessage.createBackOrScreenOn(action);
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseGetClipboard() {
|
||||||
|
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int copyKey = toUnsigned(buffer.get());
|
||||||
|
return ControlMessage.createGetClipboard(copyKey);
|
||||||
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class Controller {
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final DesktopConnection connection;
|
private final DesktopConnection connection;
|
||||||
private final DeviceMessageSender sender;
|
private final DeviceMessageSender sender;
|
||||||
|
private final boolean clipboardAutosync;
|
||||||
|
|
||||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
|
@ -31,9 +32,10 @@ public class Controller {
|
||||||
|
|
||||||
private boolean keepPowerModeOff;
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
public Controller(Device device, DesktopConnection connection) {
|
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
initPointers();
|
initPointers();
|
||||||
sender = new DeviceMessageSender(connection);
|
sender = new DeviceMessageSender(connection);
|
||||||
}
|
}
|
||||||
|
@ -114,10 +116,7 @@ public class Controller {
|
||||||
Device.collapsePanels();
|
Device.collapsePanels();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
String clipboardText = Device.getClipboardText();
|
getClipboard(msg.getCopyKey());
|
||||||
if (clipboardText != null) {
|
|
||||||
sender.pushClipboardText(clipboardText);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
|
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
|
||||||
|
@ -276,6 +275,25 @@ public class Controller {
|
||||||
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void getClipboard(int copyKey) {
|
||||||
|
// On Android >= 7, press the COPY or CUT key if requested
|
||||||
|
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||||
|
int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT;
|
||||||
|
// Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one
|
||||||
|
device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If clipboard autosync is enabled, then the device clipboard is synchronized to the computer clipboard whenever it changes, in
|
||||||
|
// particular when COPY or CUT are injected, so it should not be synchronized twice. On Android < 7, do not synchronize at all rather than
|
||||||
|
// copying an old clipboard content.
|
||||||
|
if (!clipboardAutosync) {
|
||||||
|
String clipboardText = Device.getClipboardText();
|
||||||
|
if (clipboardText != null) {
|
||||||
|
sender.pushClipboardText(clipboardText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste, long sequence) {
|
private boolean setClipboard(String text, boolean paste, long sequence) {
|
||||||
boolean ok = device.setClipboardText(text);
|
boolean ok = device.setClipboardText(text);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ public final class Server {
|
||||||
Thread controllerThread = null;
|
Thread controllerThread = null;
|
||||||
Thread deviceMessageSenderThread = null;
|
Thread deviceMessageSenderThread = null;
|
||||||
if (options.getControl()) {
|
if (options.getControl()) {
|
||||||
final Controller controller = new Controller(device, connection);
|
final Controller controller = new Controller(device, connection, options.getClipboardAutosync());
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
controllerThread = startController(controller);
|
controllerThread = startController(controller);
|
||||||
|
|
|
@ -219,6 +219,7 @@ public class ControlMessageReaderTest {
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||||
|
dos.writeByte(ControlMessage.COPY_KEY_COPY);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
@ -226,6 +227,7 @@ public class ControlMessageReaderTest {
|
||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||||
|
Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue