Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
e2b3968c66 | |||
bfcb9d06c3 | |||
dc19ae334d | |||
cbe73b0bc3 | |||
bf97a46b0c | |||
bd56d81f72 | |||
5e918ac0c3 | |||
0c0f62e4ab | |||
c96505200a | |||
82a053015d |
17
README.md
17
README.md
@ -803,6 +803,15 @@ of the host key mapping. Therefore, if your keyboard layout does not match, it
|
||||
must be configured on the Android device, in Settings → System → Languages and
|
||||
input → [Physical keyboard].
|
||||
|
||||
This settings page can be started directly:
|
||||
|
||||
```bash
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
```
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or when
|
||||
a physical keyboard is connected).
|
||||
|
||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
||||
|
||||
|
||||
@ -824,7 +833,13 @@ scrcpy --prefer-text
|
||||
|
||||
(but this will break keyboard behavior in games)
|
||||
|
||||
This option has no effect on HID keyboard (all key events are sent as
|
||||
On the contrary, you could force to always inject raw key events:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
These options have no effect on HID keyboard (all key events are sent as
|
||||
scancodes in this mode).
|
||||
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
|
@ -28,6 +28,7 @@ src = [
|
||||
'src/video_buffer.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/file.c',
|
||||
'src/util/intmap.c',
|
||||
'src/util/intr.c',
|
||||
'src/util/log.c',
|
||||
'src/util/net.c',
|
||||
|
10
app/scrcpy.1
10
app/scrcpy.1
@ -90,6 +90,12 @@ This provides a better experience for IME users, and allows to generate non-ASCI
|
||||
|
||||
It may only work over USB, and is currently only supported on Linux.
|
||||
|
||||
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
||||
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
.TP
|
||||
.B \-\-legacy\-paste
|
||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||
@ -159,6 +165,10 @@ Set the target directory for pushing files to the device by drag & drop. It is p
|
||||
|
||||
Default is "/sdcard/Download/".
|
||||
|
||||
.TP
|
||||
.B \-\-raw\-key\-events
|
||||
Inject key events for all input keys, and ignore text events.
|
||||
|
||||
.TP
|
||||
.BI "\-r, \-\-record " file
|
||||
Record screen to
|
||||
|
@ -51,6 +51,7 @@
|
||||
#define OPT_TUNNEL_PORT 1031
|
||||
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
||||
#define OPT_TCPIP 1033
|
||||
#define OPT_RAW_KEY_EVENTS 1034
|
||||
|
||||
struct sc_option {
|
||||
char shortopt;
|
||||
@ -165,7 +166,14 @@ static const struct sc_option options[] = {
|
||||
"generate non-ASCII characters, contrary to the default "
|
||||
"injection method.\n"
|
||||
"It may only work over USB, and is currently only supported "
|
||||
"on Linux.",
|
||||
"on Linux.\n"
|
||||
"The keyboard layout must be configured (once and for all) on "
|
||||
"the device, via Settings -> System -> Languages and input -> "
|
||||
"Physical keyboard. This settings page can be started "
|
||||
"directly: `adb shell am start -a "
|
||||
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"However, the option is only available when the HID keyboard "
|
||||
"is enabled (or a physical keyboard is connected).",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
@ -275,6 +283,11 @@ static const struct sc_option options[] = {
|
||||
"drag & drop. It is passed as is to \"adb push\".\n"
|
||||
"Default is \"/sdcard/Download/\".",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_RAW_KEY_EVENTS,
|
||||
.longopt = "raw-key-events",
|
||||
.text = "Inject key events for all input keys, and ignore text events."
|
||||
},
|
||||
{
|
||||
.shortopt = 'r',
|
||||
.longopt = "record",
|
||||
@ -1343,7 +1356,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
opts->push_target = optarg;
|
||||
break;
|
||||
case OPT_PREFER_TEXT:
|
||||
opts->prefer_text = true;
|
||||
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
|
||||
LOGE("--prefer-text is incompatible with --raw-key-events");
|
||||
return false;
|
||||
}
|
||||
opts->key_inject_mode = SC_KEY_INJECT_MODE_TEXT;
|
||||
break;
|
||||
case OPT_RAW_KEY_EVENTS:
|
||||
if (opts->key_inject_mode != SC_KEY_INJECT_MODE_MIXED) {
|
||||
LOGE("--prefer-text is incompatible with --raw-key-events");
|
||||
return false;
|
||||
}
|
||||
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
|
||||
break;
|
||||
case OPT_ROTATION:
|
||||
if (!parse_rotation(optarg, &opts->rotation)) {
|
||||
|
@ -55,6 +55,12 @@ static const char *const screen_power_mode_labels[] = {
|
||||
"suspend",
|
||||
};
|
||||
|
||||
static const char *const copy_key_labels[] = {
|
||||
"none",
|
||||
"copy",
|
||||
"cut",
|
||||
};
|
||||
|
||||
static void
|
||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
||||
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:
|
||||
buf[1] = msg->inject_keycode.action;
|
||||
return 2;
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
buf[1] = msg->get_clipboard.copy_key;
|
||||
return 2;
|
||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||
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_SETTINGS_PANEL:
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
// no additional data
|
||||
return 1;
|
||||
@ -194,10 +202,14 @@ control_msg_log(const struct control_msg *msg) {
|
||||
LOG_CMSG("back-or-screen-on %s",
|
||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||
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:
|
||||
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
||||
msg->set_clipboard.sequence,
|
||||
msg->set_clipboard.paste ? "paste" : "copy",
|
||||
msg->set_clipboard.paste ? "paste" : "nopaste",
|
||||
msg->set_clipboard.text);
|
||||
break;
|
||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
@ -213,9 +225,6 @@ control_msg_log(const struct control_msg *msg) {
|
||||
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
LOG_CMSG("collapse panels");
|
||||
break;
|
||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||
LOG_CMSG("get clipboard");
|
||||
break;
|
||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
LOG_CMSG("rotate device");
|
||||
break;
|
||||
|
@ -41,6 +41,12 @@ enum screen_power_mode {
|
||||
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 {
|
||||
enum control_msg_type type;
|
||||
union {
|
||||
@ -69,6 +75,9 @@ struct control_msg {
|
||||
enum android_keyevent_action action; // action for the BACK key
|
||||
// screen may only be turned on on ACTION_DOWN
|
||||
} back_or_screen_on;
|
||||
struct {
|
||||
enum get_clipboard_copy_key copy_key;
|
||||
} get_clipboard;
|
||||
struct {
|
||||
uint64_t sequence;
|
||||
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");
|
||||
}
|
||||
|
||||
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
|
||||
// If the screen is off, it is turned on only on ACTION_DOWN
|
||||
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
|
||||
set_device_clipboard(struct controller *controller, bool paste,
|
||||
uint64_t sequence) {
|
||||
@ -450,13 +455,15 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && !shift && !repeat) {
|
||||
action_copy(controller, action);
|
||||
if (control && !shift && !repeat && down) {
|
||||
get_device_clipboard(controller,
|
||||
GET_CLIPBOARD_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (control && !shift && !repeat) {
|
||||
action_cut(controller, action);
|
||||
if (control && !shift && !repeat && down) {
|
||||
get_device_clipboard(controller,
|
||||
GET_CLIPBOARD_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
|
@ -6,65 +6,168 @@
|
||||
#include "android/input.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to sc_keyboard_inject */
|
||||
#define DOWNCAST(KP) \
|
||||
container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||
|
||||
#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 const struct sc_intmap_entry actions[] = {
|
||||
{SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN},
|
||||
{SDL_KEYUP, AKEY_EVENT_ACTION_UP},
|
||||
};
|
||||
|
||||
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
enum sc_key_inject_mode key_inject_mode) {
|
||||
// Navigation keys and ENTER.
|
||||
// Used in all modes.
|
||||
static const struct sc_intmap_entry special_keys[] = {
|
||||
{SDLK_RETURN, AKEYCODE_ENTER},
|
||||
{SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
|
||||
{SDLK_ESCAPE, AKEYCODE_ESCAPE},
|
||||
{SDLK_BACKSPACE, AKEYCODE_DEL},
|
||||
{SDLK_TAB, AKEYCODE_TAB},
|
||||
{SDLK_PAGEUP, AKEYCODE_PAGE_UP},
|
||||
{SDLK_DELETE, AKEYCODE_FORWARD_DEL},
|
||||
{SDLK_HOME, AKEYCODE_MOVE_HOME},
|
||||
{SDLK_END, AKEYCODE_MOVE_END},
|
||||
{SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN},
|
||||
{SDLK_RIGHT, AKEYCODE_DPAD_RIGHT},
|
||||
{SDLK_LEFT, AKEYCODE_DPAD_LEFT},
|
||||
{SDLK_DOWN, AKEYCODE_DPAD_DOWN},
|
||||
{SDLK_UP, AKEYCODE_DPAD_UP},
|
||||
{SDLK_LCTRL, AKEYCODE_CTRL_LEFT},
|
||||
{SDLK_RCTRL, AKEYCODE_CTRL_RIGHT},
|
||||
{SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT},
|
||||
{SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT},
|
||||
};
|
||||
|
||||
// Numpad navigation keys.
|
||||
// Used in all modes, when NumLock and Shift are disabled.
|
||||
static const struct sc_intmap_entry kp_nav_keys[] = {
|
||||
{SDLK_KP_0, AKEYCODE_INSERT},
|
||||
{SDLK_KP_1, AKEYCODE_MOVE_END},
|
||||
{SDLK_KP_2, AKEYCODE_DPAD_DOWN},
|
||||
{SDLK_KP_3, AKEYCODE_PAGE_DOWN},
|
||||
{SDLK_KP_4, AKEYCODE_DPAD_LEFT},
|
||||
{SDLK_KP_6, AKEYCODE_DPAD_RIGHT},
|
||||
{SDLK_KP_7, AKEYCODE_MOVE_HOME},
|
||||
{SDLK_KP_8, AKEYCODE_DPAD_UP},
|
||||
{SDLK_KP_9, AKEYCODE_PAGE_UP},
|
||||
{SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL},
|
||||
};
|
||||
|
||||
// Letters and space.
|
||||
// Used in non-text mode.
|
||||
static const struct sc_intmap_entry alphaspace_keys[] = {
|
||||
{SDLK_a, AKEYCODE_A},
|
||||
{SDLK_b, AKEYCODE_B},
|
||||
{SDLK_c, AKEYCODE_C},
|
||||
{SDLK_d, AKEYCODE_D},
|
||||
{SDLK_e, AKEYCODE_E},
|
||||
{SDLK_f, AKEYCODE_F},
|
||||
{SDLK_g, AKEYCODE_G},
|
||||
{SDLK_h, AKEYCODE_H},
|
||||
{SDLK_i, AKEYCODE_I},
|
||||
{SDLK_j, AKEYCODE_J},
|
||||
{SDLK_k, AKEYCODE_K},
|
||||
{SDLK_l, AKEYCODE_L},
|
||||
{SDLK_m, AKEYCODE_M},
|
||||
{SDLK_n, AKEYCODE_N},
|
||||
{SDLK_o, AKEYCODE_O},
|
||||
{SDLK_p, AKEYCODE_P},
|
||||
{SDLK_q, AKEYCODE_Q},
|
||||
{SDLK_r, AKEYCODE_R},
|
||||
{SDLK_s, AKEYCODE_S},
|
||||
{SDLK_t, AKEYCODE_T},
|
||||
{SDLK_u, AKEYCODE_U},
|
||||
{SDLK_v, AKEYCODE_V},
|
||||
{SDLK_w, AKEYCODE_W},
|
||||
{SDLK_x, AKEYCODE_X},
|
||||
{SDLK_y, AKEYCODE_Y},
|
||||
{SDLK_z, AKEYCODE_Z},
|
||||
{SDLK_SPACE, AKEYCODE_SPACE},
|
||||
};
|
||||
|
||||
// Numbers and punctuation keys.
|
||||
// Used in raw mode only.
|
||||
static const struct sc_intmap_entry numbers_punct_keys[] = {
|
||||
{SDLK_HASH, AKEYCODE_POUND},
|
||||
{SDLK_PERCENT, AKEYCODE_PERIOD},
|
||||
{SDLK_QUOTE, AKEYCODE_APOSTROPHE},
|
||||
{SDLK_ASTERISK, AKEYCODE_STAR},
|
||||
{SDLK_PLUS, AKEYCODE_PLUS},
|
||||
{SDLK_COMMA, AKEYCODE_COMMA},
|
||||
{SDLK_MINUS, AKEYCODE_MINUS},
|
||||
{SDLK_PERIOD, AKEYCODE_PERIOD},
|
||||
{SDLK_SLASH, AKEYCODE_SLASH},
|
||||
{SDLK_0, AKEYCODE_0},
|
||||
{SDLK_1, AKEYCODE_1},
|
||||
{SDLK_2, AKEYCODE_2},
|
||||
{SDLK_3, AKEYCODE_3},
|
||||
{SDLK_4, AKEYCODE_4},
|
||||
{SDLK_5, AKEYCODE_5},
|
||||
{SDLK_6, AKEYCODE_6},
|
||||
{SDLK_7, AKEYCODE_7},
|
||||
{SDLK_8, AKEYCODE_8},
|
||||
{SDLK_9, AKEYCODE_9},
|
||||
{SDLK_SEMICOLON, AKEYCODE_SEMICOLON},
|
||||
{SDLK_EQUALS, AKEYCODE_EQUALS},
|
||||
{SDLK_AT, AKEYCODE_AT},
|
||||
{SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
|
||||
{SDLK_BACKSLASH, AKEYCODE_BACKSLASH},
|
||||
{SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
|
||||
{SDLK_BACKQUOTE, AKEYCODE_GRAVE},
|
||||
{SDLK_KP_1, AKEYCODE_NUMPAD_1},
|
||||
{SDLK_KP_2, AKEYCODE_NUMPAD_2},
|
||||
{SDLK_KP_3, AKEYCODE_NUMPAD_3},
|
||||
{SDLK_KP_4, AKEYCODE_NUMPAD_4},
|
||||
{SDLK_KP_5, AKEYCODE_NUMPAD_5},
|
||||
{SDLK_KP_6, AKEYCODE_NUMPAD_6},
|
||||
{SDLK_KP_7, AKEYCODE_NUMPAD_7},
|
||||
{SDLK_KP_8, AKEYCODE_NUMPAD_8},
|
||||
{SDLK_KP_9, AKEYCODE_NUMPAD_9},
|
||||
{SDLK_KP_0, AKEYCODE_NUMPAD_0},
|
||||
{SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
|
||||
{SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
|
||||
{SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
|
||||
{SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD},
|
||||
{SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
|
||||
{SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
|
||||
{SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
|
||||
{SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
|
||||
};
|
||||
|
||||
const struct sc_intmap_entry *entry =
|
||||
SC_INTMAP_FIND_ENTRY(special_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefer_text && !(mod & KMOD_CTRL)) {
|
||||
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) {
|
||||
// do not forward alpha and space key events (unless Ctrl is pressed)
|
||||
return false;
|
||||
}
|
||||
@ -72,37 +175,23 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
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;
|
||||
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
entry = SC_INTMAP_FIND_ENTRY(numbers_punct_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum android_metastate
|
||||
@ -167,7 +256,7 @@ convert_meta_state(SDL_Keymod mod) {
|
||||
|
||||
static bool
|
||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
bool prefer_text, uint32_t repeat) {
|
||||
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
|
||||
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
|
||||
|
||||
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
|
||||
@ -176,7 +265,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||
|
||||
uint16_t mod = from->keysym.mod;
|
||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||
prefer_text)) {
|
||||
key_inject_mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -207,7 +296,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
}
|
||||
|
||||
struct control_msg msg;
|
||||
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
|
||||
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
|
||||
if (!controller_push_msg(ki->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
@ -219,11 +308,16 @@ 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) {
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
// Never inject text events
|
||||
return;
|
||||
}
|
||||
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
assert(event->text[1] == '\0');
|
||||
// letters and space are handled as raw key event
|
||||
// Letters and space are handled as raw key events
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -246,7 +340,7 @@ 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->key_inject_mode = options->key_inject_mode;
|
||||
ki->forward_key_repeat = options->forward_key_repeat;
|
||||
|
||||
ki->repeat = 0;
|
||||
|
@ -18,7 +18,7 @@ struct sc_keyboard_inject {
|
||||
// number of repetitions. This variable keeps track of the count.
|
||||
unsigned repeat;
|
||||
|
||||
bool prefer_text;
|
||||
enum sc_key_inject_mode key_inject_mode;
|
||||
bool forward_key_repeat;
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "android/input.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast mouse processor to sc_mouse_inject */
|
||||
@ -32,25 +33,37 @@ convert_mouse_buttons(uint32_t state) {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||
#define FAIL default: return false
|
||||
static bool
|
||||
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
|
||||
FAIL;
|
||||
static const struct sc_intmap_entry actions[] = {
|
||||
{SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN},
|
||||
{SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP},
|
||||
};
|
||||
|
||||
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
|
||||
switch (from) {
|
||||
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
|
||||
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
|
||||
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
|
||||
FAIL;
|
||||
static const struct sc_intmap_entry actions[] = {
|
||||
{SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE},
|
||||
{SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN},
|
||||
{SDL_FINGERUP, AMOTION_EVENT_ACTION_UP},
|
||||
};
|
||||
|
||||
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -43,7 +43,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.control = true,
|
||||
.display = true,
|
||||
.turn_screen_off = false,
|
||||
.prefer_text = false,
|
||||
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
|
||||
.window_borderless = false,
|
||||
.mipmaps = true,
|
||||
.stay_awake = false,
|
||||
|
@ -38,6 +38,20 @@ enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_HID,
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
// Inject special keys, letters and space as key events.
|
||||
// Inject numbers and punctuation as text events.
|
||||
// This is the default mode.
|
||||
SC_KEY_INJECT_MODE_MIXED,
|
||||
|
||||
// Inject special keys as key events.
|
||||
// Inject letters and space, numbers and punctuation as text events.
|
||||
SC_KEY_INJECT_MODE_TEXT,
|
||||
|
||||
// Inject everything as key events.
|
||||
SC_KEY_INJECT_MODE_RAW,
|
||||
};
|
||||
|
||||
#define SC_MAX_SHORTCUT_MODS 8
|
||||
|
||||
enum sc_shortcut_mod {
|
||||
@ -98,7 +112,7 @@ struct scrcpy_options {
|
||||
bool control;
|
||||
bool display;
|
||||
bool turn_screen_off;
|
||||
bool prefer_text;
|
||||
enum sc_key_inject_mode key_inject_mode;
|
||||
bool window_borderless;
|
||||
bool mipmaps;
|
||||
bool stay_awake;
|
||||
|
13
app/src/util/intmap.c
Normal file
13
app/src/util/intmap.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "intmap.h"
|
||||
|
||||
const struct sc_intmap_entry *
|
||||
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
|
||||
int32_t key) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
const struct sc_intmap_entry *entry = &entries[i];
|
||||
if (entry->key == key) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
24
app/src/util/intmap.h
Normal file
24
app/src/util/intmap.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef SC_ARRAYMAP_H
|
||||
#define SC_ARRAYMAP_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct sc_intmap_entry {
|
||||
int32_t key;
|
||||
int32_t value;
|
||||
};
|
||||
|
||||
const struct sc_intmap_entry *
|
||||
sc_intmap_find_entry(const struct sc_intmap_entry entries[], size_t len,
|
||||
int32_t key);
|
||||
|
||||
/**
|
||||
* MAP is expected to be a static array of sc_intmap_entry, so that
|
||||
* ARRAY_LEN(MAP) can be computed statically.
|
||||
*/
|
||||
#define SC_INTMAP_FIND_ENTRY(MAP, KEY) \
|
||||
sc_intmap_find_entry(MAP, ARRAY_LEN(MAP), KEY)
|
||||
|
||||
#endif
|
@ -89,7 +89,7 @@ static void test_options(void) {
|
||||
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
||||
assert(opts->show_touches);
|
||||
assert(opts->turn_screen_off);
|
||||
assert(opts->prefer_text);
|
||||
assert(opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT);
|
||||
assert(!strcmp(opts->window_title, "my device"));
|
||||
assert(opts->window_x == 100);
|
||||
assert(opts->window_y == -1);
|
||||
|
@ -210,14 +210,18 @@ static void test_serialize_collapse_panels(void) {
|
||||
static void test_serialize_get_clipboard(void) {
|
||||
struct control_msg msg = {
|
||||
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
.get_clipboard = {
|
||||
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
|
||||
},
|
||||
};
|
||||
|
||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
assert(size == 2);
|
||||
|
||||
const unsigned char expected[] = {
|
||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
GET_CLIPBOARD_COPY_KEY_COPY,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -20,6 +20,10 @@ public final class ControlMessage {
|
||||
|
||||
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 String text;
|
||||
private int metaState; // KeyEvent.META_*
|
||||
@ -31,6 +35,7 @@ public final class ControlMessage {
|
||||
private Position position;
|
||||
private int hScroll;
|
||||
private int vScroll;
|
||||
private int copyKey;
|
||||
private boolean paste;
|
||||
private int repeat;
|
||||
private long sequence;
|
||||
@ -82,6 +87,13 @@ public final class ControlMessage {
|
||||
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) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CLIPBOARD;
|
||||
@ -151,6 +163,10 @@ public final class ControlMessage {
|
||||
return vScroll;
|
||||
}
|
||||
|
||||
public int getCopyKey() {
|
||||
return copyKey;
|
||||
}
|
||||
|
||||
public boolean getPaste() {
|
||||
return paste;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ public class ControlMessageReader {
|
||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||
static final int BACK_OR_SCREEN_ON_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;
|
||||
|
||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||
@ -70,6 +71,9 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||
msg = parseBackOrScreenOnEvent();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
msg = parseGetClipboard();
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
msg = parseSetClipboard();
|
||||
break;
|
||||
@ -79,7 +83,6 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
msg = ControlMessage.createEmpty(type);
|
||||
break;
|
||||
@ -162,6 +165,14 @@ public class ControlMessageReader {
|
||||
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() {
|
||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
|
@ -21,6 +21,7 @@ public class Controller {
|
||||
private final Device device;
|
||||
private final DesktopConnection connection;
|
||||
private final DeviceMessageSender sender;
|
||||
private final boolean clipboardAutosync;
|
||||
|
||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||
|
||||
@ -31,9 +32,10 @@ public class Controller {
|
||||
|
||||
private boolean keepPowerModeOff;
|
||||
|
||||
public Controller(Device device, DesktopConnection connection) {
|
||||
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) {
|
||||
this.device = device;
|
||||
this.connection = connection;
|
||||
this.clipboardAutosync = clipboardAutosync;
|
||||
initPointers();
|
||||
sender = new DeviceMessageSender(connection);
|
||||
}
|
||||
@ -55,7 +57,7 @@ public class Controller {
|
||||
public void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (!Device.isScreenOn()) {
|
||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
||||
|
||||
// dirty hack
|
||||
// After POWER is injected, the device is powered on asynchronously.
|
||||
@ -114,18 +116,10 @@ public class Controller {
|
||||
Device.collapsePanels();
|
||||
break;
|
||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||
String clipboardText = Device.getClipboardText();
|
||||
if (clipboardText != null) {
|
||||
sender.pushClipboardText(clipboardText);
|
||||
}
|
||||
getClipboard(msg.getCopyKey());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||
long sequence = msg.getSequence();
|
||||
setClipboard(msg.getText(), msg.getPaste());
|
||||
if (sequence != ControlMessage.SEQUENCE_INVALID) {
|
||||
// Acknowledgement requested
|
||||
sender.pushAckClipboard(sequence);
|
||||
}
|
||||
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
@ -149,7 +143,7 @@ public class Controller {
|
||||
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
|
||||
schedulePowerModeOff();
|
||||
}
|
||||
return device.injectKeyEvent(action, keycode, repeat, metaState);
|
||||
return device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
private boolean injectChar(char c) {
|
||||
@ -160,7 +154,7 @@ public class Controller {
|
||||
return false;
|
||||
}
|
||||
for (KeyEvent event : events) {
|
||||
if (!device.injectEvent(event)) {
|
||||
if (!device.injectEvent(event, Device.INJECT_MODE_ASYNC)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -224,7 +218,7 @@ public class Controller {
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
|
||||
0);
|
||||
return device.injectEvent(event);
|
||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
||||
@ -247,7 +241,7 @@ public class Controller {
|
||||
MotionEvent event = MotionEvent
|
||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||
InputDevice.SOURCE_MOUSE, 0);
|
||||
return device.injectEvent(event);
|
||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,7 +259,7 @@ public class Controller {
|
||||
|
||||
private boolean pressBackOrTurnScreenOn(int action) {
|
||||
if (Device.isScreenOn()) {
|
||||
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0);
|
||||
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
// Screen is off
|
||||
@ -278,10 +272,29 @@ public class Controller {
|
||||
if (keepPowerModeOff) {
|
||||
schedulePowerModeOff();
|
||||
}
|
||||
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
private boolean setClipboard(String text, boolean paste) {
|
||||
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) {
|
||||
boolean ok = device.setClipboardText(text);
|
||||
if (ok) {
|
||||
Ln.i("Device clipboard set");
|
||||
@ -289,7 +302,12 @@ public class Controller {
|
||||
|
||||
// On Android >= 7, also press the PASTE key if requested
|
||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE);
|
||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
if (sequence != ControlMessage.SEQUENCE_INVALID) {
|
||||
// Acknowledgement requested
|
||||
sender.pushAckClipboard(sequence);
|
||||
}
|
||||
|
||||
return ok;
|
||||
|
@ -24,6 +24,10 @@ public final class Device {
|
||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||
|
||||
public static final int INJECT_MODE_ASYNC = InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
|
||||
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
|
||||
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
|
||||
|
||||
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
||||
|
||||
@ -164,7 +168,7 @@ public final class Device {
|
||||
return supportsInputEvents;
|
||||
}
|
||||
|
||||
public static boolean injectEvent(InputEvent inputEvent, int displayId) {
|
||||
public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) {
|
||||
if (!supportsInputEvents(displayId)) {
|
||||
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||
}
|
||||
@ -173,30 +177,31 @@ public final class Device {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
|
||||
}
|
||||
|
||||
public boolean injectEvent(InputEvent event) {
|
||||
return injectEvent(event, displayId);
|
||||
public boolean injectEvent(InputEvent event, int injectMode) {
|
||||
return injectEvent(event, displayId, injectMode);
|
||||
}
|
||||
|
||||
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
|
||||
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||
InputDevice.SOURCE_KEYBOARD);
|
||||
return injectEvent(event, displayId);
|
||||
return injectEvent(event, displayId, injectMode);
|
||||
}
|
||||
|
||||
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||
return injectKeyEvent(action, keyCode, repeat, metaState, displayId);
|
||||
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int injectMode) {
|
||||
return injectKeyEvent(action, keyCode, repeat, metaState, displayId, injectMode);
|
||||
}
|
||||
|
||||
public static boolean pressReleaseKeycode(int keyCode, int displayId) {
|
||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
|
||||
public static boolean pressReleaseKeycode(int keyCode, int displayId, int injectMode) {
|
||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId, injectMode)
|
||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
||||
}
|
||||
|
||||
public boolean pressReleaseKeycode(int keyCode) {
|
||||
return pressReleaseKeycode(keyCode, displayId);
|
||||
public boolean pressReleaseKeycode(int keyCode, int injectMode) {
|
||||
return pressReleaseKeycode(keyCode, displayId, injectMode);
|
||||
}
|
||||
|
||||
public static boolean isScreenOn() {
|
||||
@ -272,7 +277,7 @@ public final class Device {
|
||||
if (!isScreenOn()) {
|
||||
return true;
|
||||
}
|
||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId);
|
||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,7 +74,7 @@ public final class Server {
|
||||
Thread controllerThread = null;
|
||||
Thread deviceMessageSenderThread = null;
|
||||
if (options.getControl()) {
|
||||
final Controller controller = new Controller(device, connection);
|
||||
final Controller controller = new Controller(device, connection, options.getClipboardAutosync());
|
||||
|
||||
// asynchronous
|
||||
controllerThread = startController(controller);
|
||||
|
@ -219,6 +219,7 @@ public class ControlMessageReaderTest {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_GET_CLIPBOARD);
|
||||
dos.writeByte(ControlMessage.COPY_KEY_COPY);
|
||||
|
||||
byte[] packet = bos.toByteArray();
|
||||
|
||||
@ -226,6 +227,7 @@ public class ControlMessageReaderTest {
|
||||
ControlMessage event = reader.next();
|
||||
|
||||
Assert.assertEquals(ControlMessage.TYPE_GET_CLIPBOARD, event.getType());
|
||||
Assert.assertEquals(ControlMessage.COPY_KEY_COPY, event.getCopyKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Reference in New Issue
Block a user