2021-10-03 23:11:20 +08:00
|
|
|
#include "keyboard_inject.h"
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <SDL2/SDL_events.h>
|
|
|
|
|
|
|
|
#include "android/input.h"
|
|
|
|
#include "control_msg.h"
|
|
|
|
#include "controller.h"
|
|
|
|
#include "util/log.h"
|
|
|
|
|
|
|
|
/** Downcast key processor to sc_keyboard_inject */
|
2021-11-27 04:57:06 +08:00
|
|
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
|
2021-10-03 23:11:20 +08:00
|
|
|
|
|
|
|
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
|
|
|
#define FAIL default: return false
|
|
|
|
static bool
|
|
|
|
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
|
|
|
switch (from) {
|
|
|
|
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
|
|
|
|
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
|
|
|
|
FAIL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
|
|
|
bool prefer_text) {
|
|
|
|
switch (from) {
|
|
|
|
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
|
|
|
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
|
|
|
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
|
|
|
|
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
|
|
|
|
MAP(SDLK_TAB, AKEYCODE_TAB);
|
|
|
|
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
|
|
|
|
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
|
|
|
|
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
|
|
|
|
MAP(SDLK_END, AKEYCODE_MOVE_END);
|
|
|
|
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
|
|
|
|
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
|
|
|
|
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
|
|
|
|
// Handle Numpad events when Num Lock is disabled
|
|
|
|
// If SHIFT is pressed, a text event will be sent instead
|
|
|
|
switch(from) {
|
|
|
|
MAP(SDLK_KP_0, AKEYCODE_INSERT);
|
|
|
|
MAP(SDLK_KP_1, AKEYCODE_MOVE_END);
|
|
|
|
MAP(SDLK_KP_2, AKEYCODE_DPAD_DOWN);
|
|
|
|
MAP(SDLK_KP_3, AKEYCODE_PAGE_DOWN);
|
|
|
|
MAP(SDLK_KP_4, AKEYCODE_DPAD_LEFT);
|
|
|
|
MAP(SDLK_KP_6, AKEYCODE_DPAD_RIGHT);
|
|
|
|
MAP(SDLK_KP_7, AKEYCODE_MOVE_HOME);
|
|
|
|
MAP(SDLK_KP_8, AKEYCODE_DPAD_UP);
|
|
|
|
MAP(SDLK_KP_9, AKEYCODE_PAGE_UP);
|
|
|
|
MAP(SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prefer_text && !(mod & KMOD_CTRL)) {
|
|
|
|
// do not forward alpha and space key events (unless Ctrl is pressed)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// if ALT and META are not pressed, also handle letters and space
|
|
|
|
switch (from) {
|
|
|
|
MAP(SDLK_a, AKEYCODE_A);
|
|
|
|
MAP(SDLK_b, AKEYCODE_B);
|
|
|
|
MAP(SDLK_c, AKEYCODE_C);
|
|
|
|
MAP(SDLK_d, AKEYCODE_D);
|
|
|
|
MAP(SDLK_e, AKEYCODE_E);
|
|
|
|
MAP(SDLK_f, AKEYCODE_F);
|
|
|
|
MAP(SDLK_g, AKEYCODE_G);
|
|
|
|
MAP(SDLK_h, AKEYCODE_H);
|
|
|
|
MAP(SDLK_i, AKEYCODE_I);
|
|
|
|
MAP(SDLK_j, AKEYCODE_J);
|
|
|
|
MAP(SDLK_k, AKEYCODE_K);
|
|
|
|
MAP(SDLK_l, AKEYCODE_L);
|
|
|
|
MAP(SDLK_m, AKEYCODE_M);
|
|
|
|
MAP(SDLK_n, AKEYCODE_N);
|
|
|
|
MAP(SDLK_o, AKEYCODE_O);
|
|
|
|
MAP(SDLK_p, AKEYCODE_P);
|
|
|
|
MAP(SDLK_q, AKEYCODE_Q);
|
|
|
|
MAP(SDLK_r, AKEYCODE_R);
|
|
|
|
MAP(SDLK_s, AKEYCODE_S);
|
|
|
|
MAP(SDLK_t, AKEYCODE_T);
|
|
|
|
MAP(SDLK_u, AKEYCODE_U);
|
|
|
|
MAP(SDLK_v, AKEYCODE_V);
|
|
|
|
MAP(SDLK_w, AKEYCODE_W);
|
|
|
|
MAP(SDLK_x, AKEYCODE_X);
|
|
|
|
MAP(SDLK_y, AKEYCODE_Y);
|
|
|
|
MAP(SDLK_z, AKEYCODE_Z);
|
|
|
|
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
|
|
|
FAIL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum android_metastate
|
|
|
|
autocomplete_metastate(enum android_metastate metastate) {
|
|
|
|
// fill dependent flags
|
|
|
|
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
|
|
|
|
metastate |= AMETA_SHIFT_ON;
|
|
|
|
}
|
|
|
|
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
|
|
|
|
metastate |= AMETA_CTRL_ON;
|
|
|
|
}
|
|
|
|
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
|
|
|
|
metastate |= AMETA_ALT_ON;
|
|
|
|
}
|
|
|
|
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
|
|
|
|
metastate |= AMETA_META_ON;
|
|
|
|
}
|
|
|
|
|
|
|
|
return metastate;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum android_metastate
|
|
|
|
convert_meta_state(SDL_Keymod mod) {
|
|
|
|
enum android_metastate metastate = 0;
|
|
|
|
if (mod & KMOD_LSHIFT) {
|
|
|
|
metastate |= AMETA_SHIFT_LEFT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_RSHIFT) {
|
|
|
|
metastate |= AMETA_SHIFT_RIGHT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_LCTRL) {
|
|
|
|
metastate |= AMETA_CTRL_LEFT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_RCTRL) {
|
|
|
|
metastate |= AMETA_CTRL_RIGHT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_LALT) {
|
|
|
|
metastate |= AMETA_ALT_LEFT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_RALT) {
|
|
|
|
metastate |= AMETA_ALT_RIGHT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_LGUI) { // Windows key
|
|
|
|
metastate |= AMETA_META_LEFT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_RGUI) { // Windows key
|
|
|
|
metastate |= AMETA_META_RIGHT_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_NUM) {
|
|
|
|
metastate |= AMETA_NUM_LOCK_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_CAPS) {
|
|
|
|
metastate |= AMETA_CAPS_LOCK_ON;
|
|
|
|
}
|
|
|
|
if (mod & KMOD_MODE) { // Alt Gr
|
|
|
|
// no mapping?
|
|
|
|
}
|
|
|
|
|
|
|
|
// fill the dependent fields
|
|
|
|
return autocomplete_metastate(metastate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|
|
|
bool prefer_text, uint32_t repeat) {
|
|
|
|
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
|
|
|
|
|
|
|
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t mod = from->keysym.mod;
|
|
|
|
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
|
|
|
prefer_text)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
to->inject_keycode.repeat = repeat;
|
|
|
|
to->inject_keycode.metastate = convert_meta_state(mod);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
2021-11-22 00:24:34 +08:00
|
|
|
const SDL_KeyboardEvent *event,
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
uint64_t ack_to_wait) {
|
2021-11-22 00:24:34 +08:00
|
|
|
// The device clipboard synchronization and the key event messages are
|
|
|
|
// serialized, there is nothing special to do to ensure that the clipboard
|
|
|
|
// is set before injecting Ctrl+v.
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
(void) ack_to_wait;
|
2021-11-22 00:24:34 +08:00
|
|
|
|
2021-10-03 23:11:20 +08:00
|
|
|
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
|
|
|
|
|
|
|
if (event->repeat) {
|
|
|
|
if (!ki->forward_key_repeat) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
++ki->repeat;
|
|
|
|
} else {
|
|
|
|
ki->repeat = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct control_msg msg;
|
|
|
|
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
|
|
|
|
if (!controller_push_msg(ki->controller, &msg)) {
|
|
|
|
LOGW("Could not request 'inject keycode'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
sc_key_processor_process_text(struct sc_key_processor *kp,
|
|
|
|
const SDL_TextInputEvent *event) {
|
|
|
|
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
|
|
|
|
|
|
|
if (!ki->prefer_text) {
|
|
|
|
char c = event->text[0];
|
|
|
|
if (isalpha(c) || c == ' ') {
|
|
|
|
assert(event->text[1] == '\0');
|
|
|
|
// letters and space are handled as raw key event
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct control_msg msg;
|
|
|
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
|
|
|
msg.inject_text.text = strdup(event->text);
|
|
|
|
if (!msg.inject_text.text) {
|
|
|
|
LOGW("Could not strdup input text");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!controller_push_msg(ki->controller, &msg)) {
|
|
|
|
free(msg.inject_text.text);
|
|
|
|
LOGW("Could not request 'inject text'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
|
|
|
struct controller *controller,
|
|
|
|
const struct scrcpy_options *options) {
|
|
|
|
ki->controller = controller;
|
|
|
|
ki->prefer_text = options->prefer_text;
|
|
|
|
ki->forward_key_repeat = options->forward_key_repeat;
|
|
|
|
|
|
|
|
ki->repeat = 0;
|
|
|
|
|
|
|
|
static const struct sc_key_processor_ops ops = {
|
|
|
|
.process_key = sc_key_processor_process_key,
|
|
|
|
.process_text = sc_key_processor_process_text,
|
|
|
|
};
|
|
|
|
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
// Key injection and clipboard synchronization are serialized
|
|
|
|
ki->key_processor.async_paste = false;
|
2021-10-03 23:11:20 +08:00
|
|
|
ki->key_processor.ops = &ops;
|
|
|
|
}
|