Handle repeating keycodes
Initialize "repeat" count on key events. PR #1519 <https://github.com/Genymobile/scrcpy/pull/1519> Refs #1013 <https://github.com/Genymobile/scrcpy/pull/1013> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
parent
0ba74fbd9a
commit
3c1ed5d86c
10 changed files with 45 additions and 11 deletions
|
@ -42,8 +42,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
|
||||||
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
|
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
|
||||||
return 10;
|
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||||
|
return 14;
|
||||||
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
size_t len =
|
size_t len =
|
||||||
write_string(msg->inject_text.text,
|
write_string(msg->inject_text.text,
|
||||||
|
|
|
@ -44,6 +44,7 @@ struct control_msg {
|
||||||
struct {
|
struct {
|
||||||
enum android_keyevent_action action;
|
enum android_keyevent_action action;
|
||||||
enum android_keycode keycode;
|
enum android_keycode keycode;
|
||||||
|
uint32_t repeat;
|
||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} inject_keycode;
|
} inject_keycode;
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -234,7 +234,7 @@ input_manager_process_text_input(struct input_manager *im,
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
bool prefer_text) {
|
bool prefer_text, uint32_t repeat) {
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||||
|
|
||||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||||
|
@ -247,6 +247,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
to->inject_keycode.repeat = repeat;
|
||||||
to->inject_keycode.metastate = convert_meta_state(mod);
|
to->inject_keycode.metastate = convert_meta_state(mod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -411,8 +412,14 @@ input_manager_process_key(struct input_manager *im,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->repeat) {
|
||||||
|
++im->repeat;
|
||||||
|
} else {
|
||||||
|
im->repeat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,11 @@ struct input_manager {
|
||||||
struct controller *controller;
|
struct controller *controller;
|
||||||
struct video_buffer *video_buffer;
|
struct video_buffer *video_buffer;
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
|
|
||||||
|
// SDL reports repeated events as a boolean, but Android expects the actual
|
||||||
|
// number of repetitions. This variable keeps track of the count.
|
||||||
|
unsigned repeat;
|
||||||
|
|
||||||
bool prefer_text;
|
bool prefer_text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ static struct input_manager input_manager = {
|
||||||
.controller = &controller,
|
.controller = &controller,
|
||||||
.video_buffer = &video_buffer,
|
.video_buffer = &video_buffer,
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
|
.repeat = 0,
|
||||||
.prefer_text = false, // initialized later
|
.prefer_text = false, // initialized later
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,18 +9,20 @@ static void test_serialize_inject_keycode(void) {
|
||||||
.inject_keycode = {
|
.inject_keycode = {
|
||||||
.action = AKEY_EVENT_ACTION_UP,
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
.keycode = AKEYCODE_ENTER,
|
.keycode = AKEYCODE_ENTER,
|
||||||
|
.repeat = 5,
|
||||||
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
.metastate = AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 10);
|
assert(size == 14);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
0x01, // AKEY_EVENT_ACTION_UP
|
0x01, // AKEY_EVENT_ACTION_UP
|
||||||
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
|
||||||
|
0x00, 0x00, 0x00, 0X05, // repeat
|
||||||
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
0x00, 0x00, 0x00, 0x41, // AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
|
|
@ -31,15 +31,17 @@ public final class ControlMessage {
|
||||||
private int hScroll;
|
private int hScroll;
|
||||||
private int vScroll;
|
private int vScroll;
|
||||||
private int flags;
|
private int flags;
|
||||||
|
private int repeat;
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createInjectKeycode(int action, int keycode, int metaState) {
|
public static ControlMessage createInjectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_KEYCODE;
|
msg.type = TYPE_INJECT_KEYCODE;
|
||||||
msg.action = action;
|
msg.action = action;
|
||||||
msg.keycode = keycode;
|
msg.keycode = keycode;
|
||||||
|
msg.repeat = repeat;
|
||||||
msg.metaState = metaState;
|
msg.metaState = metaState;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
@ -144,4 +146,8 @@ public final class ControlMessage {
|
||||||
public int getFlags() {
|
public int getFlags() {
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getRepeat() {
|
||||||
|
return repeat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class ControlMessageReader {
|
public class ControlMessageReader {
|
||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
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;
|
||||||
|
@ -98,8 +98,9 @@ public class ControlMessageReader {
|
||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = toUnsigned(buffer.get());
|
||||||
int keycode = buffer.getInt();
|
int keycode = buffer.getInt();
|
||||||
|
int repeat = buffer.getInt();
|
||||||
int metaState = buffer.getInt();
|
int metaState = buffer.getInt();
|
||||||
return ControlMessage.createInjectKeycode(action, keycode, metaState);
|
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseString() {
|
private String parseString() {
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class Controller {
|
||||||
switch (msg.getType()) {
|
switch (msg.getType()) {
|
||||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
|
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
case ControlMessage.TYPE_INJECT_TEXT:
|
||||||
|
@ -130,8 +130,8 @@ public class Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
return device.injectKeyEvent(action, keycode, 0, metaState);
|
return device.injectKeyEvent(action, keycode, repeat, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectChar(char c) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ public class ControlMessageReaderTest {
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(5); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ public class ControlMessageReaderTest {
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(5, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,11 +310,13 @@ public class ControlMessageReaderTest {
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(0); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(MotionEvent.ACTION_DOWN);
|
dos.writeByte(MotionEvent.ACTION_DOWN);
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(1); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
@ -322,12 +326,14 @@ public class ControlMessageReaderTest {
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(0, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
event = reader.next();
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(1, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +347,7 @@ public class ControlMessageReaderTest {
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
dos.writeByte(KeyEvent.ACTION_UP);
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
dos.writeInt(KeyEvent.KEYCODE_ENTER);
|
||||||
|
dos.writeInt(4); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
|
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
dos.writeByte(ControlMessage.TYPE_INJECT_KEYCODE);
|
||||||
|
@ -353,6 +360,7 @@ public class ControlMessageReaderTest {
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
|
||||||
|
Assert.assertEquals(4, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
|
|
||||||
event = reader.next();
|
event = reader.next();
|
||||||
|
@ -360,6 +368,7 @@ public class ControlMessageReaderTest {
|
||||||
|
|
||||||
bos.reset();
|
bos.reset();
|
||||||
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
|
||||||
|
dos.writeInt(5); // repeat
|
||||||
dos.writeInt(KeyEvent.META_CTRL_ON);
|
dos.writeInt(KeyEvent.META_CTRL_ON);
|
||||||
packet = bos.toByteArray();
|
packet = bos.toByteArray();
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
@ -369,6 +378,7 @@ public class ControlMessageReaderTest {
|
||||||
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_INJECT_KEYCODE, event.getType());
|
||||||
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
|
||||||
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
|
||||||
|
Assert.assertEquals(5, event.getRepeat());
|
||||||
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue