Copy on "get clipboard" if possible
Ctrl+c synchronizes the Android device clipboard to the computer clipboard. To make the copy more straightforward, if the device runs at least Android 7, also send a COPY keycode before copying the clipboard. <https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_COPY>
This commit is contained in:
parent
0f6cdc56fa
commit
7a77f3bab4
@ -66,6 +66,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buffer_write32be(&buf[17],
|
buffer_write32be(&buf[17],
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
return 21;
|
return 21;
|
||||||
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
buf[1] = msg->get_clipboard.copy;
|
||||||
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buf[1] = !!msg->set_clipboard.paste;
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
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_BACK_OR_SCREEN_ON:
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
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;
|
||||||
|
@ -60,6 +60,9 @@ struct control_msg {
|
|||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
|
struct {
|
||||||
|
bool copy;
|
||||||
|
} get_clipboard;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by SDL_free()
|
||||||
bool paste;
|
bool paste;
|
||||||
|
@ -102,9 +102,10 @@ collapse_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
request_device_clipboard(struct controller *controller) {
|
request_device_clipboard(struct controller *controller, bool copy) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||||
|
msg.get_clipboard.copy = copy;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request device clipboard");
|
LOGW("Could not request device clipboard");
|
||||||
@ -352,7 +353,8 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_c:
|
case SDLK_c:
|
||||||
if (control && cmd && !shift && !repeat && down) {
|
if (control && cmd && !shift && !repeat && down) {
|
||||||
request_device_clipboard(controller);
|
// press COPY and get the clipboard content
|
||||||
|
request_device_clipboard(controller, true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
|
@ -185,14 +185,18 @@ static void test_serialize_collapse_notification_panel(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 = true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int 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,
|
||||||
|
1, // copy
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
public static final int TYPE_ROTATE_DEVICE = 10;
|
||||||
|
|
||||||
public static final int FLAGS_PASTE = 1;
|
public static final int FLAGS_PASTE = 1;
|
||||||
|
public static final int FLAGS_COPY = 2;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
@ -81,6 +82,15 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createGetClipboard(boolean copy) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_GET_CLIPBOARD;
|
||||||
|
if (copy) {
|
||||||
|
msg.flags = FLAGS_COPY;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
||||||
*/
|
*/
|
||||||
|
@ -12,6 +12,7 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
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_PAYLOAD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_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)
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4092; // 4096 - 1 (type) - 1 (parse flag) - 2 (length)
|
||||||
@ -67,6 +68,9 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
msg = parseInjectScrollEvent();
|
msg = parseInjectScrollEvent();
|
||||||
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;
|
||||||
@ -76,7 +80,6 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
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;
|
||||||
@ -148,6 +151,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
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() {
|
private ControlMessage parseSetClipboard() {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -104,10 +104,8 @@ public class Controller {
|
|||||||
device.collapsePanels();
|
device.collapsePanels();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
String clipboardText = device.getClipboardText();
|
boolean copy = (msg.getFlags() & ControlMessage.FLAGS_COPY) != 0;
|
||||||
if (clipboardText != null) {
|
getClipboard(copy);
|
||||||
sender.pushClipboardText(clipboardText);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
boolean paste = (msg.getFlags() & ControlMessage.FLAGS_PASTE) != 0;
|
||||||
@ -229,6 +227,19 @@ public class Controller {
|
|||||||
return device.injectKeycode(keycode);
|
return device.injectKeycode(keycode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void 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()) {
|
||||||
|
// Must wait until the COPY has been executed
|
||||||
|
device.injectCopyKeycode();
|
||||||
|
}
|
||||||
|
|
||||||
|
String clipboardText = device.getClipboardText();
|
||||||
|
if (clipboardText != null) {
|
||||||
|
sender.pushClipboardText(clipboardText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste) {
|
private boolean setClipboard(String text, boolean paste) {
|
||||||
boolean ok = device.setClipboardText(text);
|
boolean ok = device.setClipboardText(text);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
@ -186,6 +186,14 @@ public final class Device {
|
|||||||
return injectKeycode(keyCode, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
return injectKeycode(keyCode, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean injectCopyKeycode() {
|
||||||
|
isSettingClipboard.set(true);
|
||||||
|
// Must wait until the COPY has been executed
|
||||||
|
boolean ret = injectKeycode(KeyEvent.KEYCODE_COPY, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
|
||||||
|
isSettingClipboard.set(false);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean injectPasteKeycode() {
|
public boolean injectPasteKeycode() {
|
||||||
return injectKeycode(KeyEvent.KEYCODE_PASTE);
|
return injectKeycode(KeyEvent.KEYCODE_PASTE);
|
||||||
}
|
}
|
||||||
|
@ -200,6 +200,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(1); // copy
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@ -207,6 +208,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(1, (event.getFlags() & ControlMessage.FLAGS_COPY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user