mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-02 22:29:25 +00:00
Adding the support of KeyToTouch, which allows users to initiate an touch event
via pressing some key on a keyboard. Following are the list of changes associated with this function: 1. A xml file "key-to-touch-map.xml" containing user-defined groups of mappings 2. The starting procedure is modified so that "key-to-touch-map.xml" is adb-pushed along with the executable "scrcpy-server"; then the scrcpy server will read that xml to prepare the KeyToTouch utility. 3. A new control message CONTROL_MSG_TYPE_SWITCH_KEY_MAPPING_GROUP (10) 4. Two shortcut commands "Ctrl+"Shift"+"," and "Ctrl"+"Shift"+".", which send CONTROL_MSG_TYPE_SWITCH_KEY_MAPPING_GROUP to the server for switching between key mapping groups 5. A new command-line option "--key-event-only". Under this option, scrcpy will treat all allowed keyboard inputs as key events rather than text events.
This commit is contained in:
parent
bdd05b4a16
commit
280a20e0e0
20 changed files with 580 additions and 33 deletions
|
@ -98,6 +98,7 @@ build-win64-noconsole: prepare-deps-win64
|
||||||
dist-win32: build-server build-win32 build-win32-noconsole
|
dist-win32: build-server build-win32 build-win32-noconsole
|
||||||
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
|
cp "$(SERVER_BUILD_DIR)"/server/key-to-touch-map.xml "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.2.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
|
@ -113,6 +114,7 @@ dist-win32: build-server build-win32 build-win32-noconsole
|
||||||
dist-win64: build-server build-win64 build-win64-noconsole
|
dist-win64: build-server build-win64 build-win64-noconsole
|
||||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
cp "$(SERVER_BUILD_DIR)"/server/key-to-touch-map.xml "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||||
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.2.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
|
|
@ -74,6 +74,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
|
case CONTROL_MSG_TYPE_SWITCH_KEY_MAPPING_GROUP:
|
||||||
|
buf[1] = msg->key_mapping_group_switch.direction;
|
||||||
|
return 2;
|
||||||
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:
|
||||||
|
|
|
@ -28,6 +28,7 @@ enum control_msg_type {
|
||||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
|
CONTROL_MSG_TYPE_SWITCH_KEY_MAPPING_GROUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum screen_power_mode {
|
enum screen_power_mode {
|
||||||
|
@ -65,6 +66,9 @@ struct control_msg {
|
||||||
struct {
|
struct {
|
||||||
enum screen_power_mode mode;
|
enum screen_power_mode mode;
|
||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
|
struct {
|
||||||
|
int32_t direction;
|
||||||
|
} key_mapping_group_switch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ convert_meta_state(SDL_Keymod mod) {
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
bool prefer_text) {
|
enum key_input_mode mode) {
|
||||||
switch (from) {
|
switch (from) {
|
||||||
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||||
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||||
|
@ -94,7 +94,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer_text) {
|
if (mode == KEY_TEXT_PREFERRED) {
|
||||||
// do not forward alpha and space key events
|
// do not forward alpha and space key events
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,33 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
MAP(SDLK_y, AKEYCODE_Y);
|
MAP(SDLK_y, AKEYCODE_Y);
|
||||||
MAP(SDLK_z, AKEYCODE_Z);
|
MAP(SDLK_z, AKEYCODE_Z);
|
||||||
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode != KEY_EVENT_ONLY)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (from) {
|
||||||
|
MAP(SDLK_SLASH, AKEYCODE_SLASH);
|
||||||
|
MAP(SDLK_PERIOD, AKEYCODE_PERIOD);
|
||||||
|
MAP(SDLK_COMMA, AKEYCODE_COMMA);
|
||||||
|
MAP(SDLK_SEMICOLON, AKEYCODE_SEMICOLON);
|
||||||
|
MAP(SDLK_QUOTE, AKEYCODE_APOSTROPHE);
|
||||||
|
MAP(SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET);
|
||||||
|
MAP(SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET);
|
||||||
|
MAP(SDLK_BACKSLASH, AKEYCODE_BACKSLASH);
|
||||||
|
MAP(SDLK_MINUS, AKEYCODE_MINUS);
|
||||||
|
MAP(SDLK_EQUALS, AKEYCODE_EQUALS);
|
||||||
|
MAP(SDLK_0, AKEYCODE_0);
|
||||||
|
MAP(SDLK_1, AKEYCODE_1);
|
||||||
|
MAP(SDLK_2, AKEYCODE_2);
|
||||||
|
MAP(SDLK_3, AKEYCODE_3);
|
||||||
|
MAP(SDLK_4, AKEYCODE_4);
|
||||||
|
MAP(SDLK_5, AKEYCODE_5);
|
||||||
|
MAP(SDLK_6, AKEYCODE_6);
|
||||||
|
MAP(SDLK_7, AKEYCODE_7);
|
||||||
|
MAP(SDLK_8, AKEYCODE_8);
|
||||||
|
MAP(SDLK_9, AKEYCODE_9);
|
||||||
|
MAP(SDLK_BACKQUOTE, AKEYCODE_GRAVE);
|
||||||
FAIL;
|
FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
#include "input_manager.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
|
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
|
||||||
|
@ -15,7 +16,7 @@ convert_meta_state(SDL_Keymod mod);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||||
bool prefer_text);
|
enum key_input_mode mode);
|
||||||
|
|
||||||
enum android_motionevent_buttons
|
enum android_motionevent_buttons
|
||||||
convert_mouse_buttons(uint32_t state);
|
convert_mouse_buttons(uint32_t state);
|
||||||
|
|
|
@ -97,6 +97,17 @@ action_menu(struct controller *controller, int actions) {
|
||||||
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
switch_key_mapping_group(struct controller *controller, bool isForward) {
|
||||||
|
struct control_msg msg;
|
||||||
|
msg.type = CONTROL_MSG_TYPE_SWITCH_KEY_MAPPING_GROUP;
|
||||||
|
msg.key_mapping_group_switch.direction = (int32_t) isForward;
|
||||||
|
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'switch key mapping group forwards/backwards'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller) {
|
||||||
|
@ -214,13 +225,22 @@ clipboard_paste(struct controller *controller) {
|
||||||
void
|
void
|
||||||
input_manager_process_text_input(struct input_manager *im,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
if (!im->prefer_text) {
|
switch (im->_key_input_mode)
|
||||||
|
{
|
||||||
|
case KEY_EVENT_ONLY:
|
||||||
|
return;
|
||||||
|
case KEY_COMBINED:
|
||||||
|
{
|
||||||
char c = event->text[0];
|
char c = event->text[0];
|
||||||
if (isalpha(c) || c == ' ') {
|
if (isalpha(c) || c == ' ') {
|
||||||
assert(event->text[1] == '\0');
|
SDL_assert(event->text[1] == '\0');
|
||||||
// letters and space are handled as raw key event
|
// letters and space are handled as raw key event
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
|
@ -238,7 +258,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) {
|
enum key_input_mode mode) {
|
||||||
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,7 +267,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
|
|
||||||
uint16_t mod = from->keysym.mod;
|
uint16_t mod = from->keysym.mod;
|
||||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||||
prefer_text)) {
|
mode)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,6 +408,16 @@ input_manager_process_key(struct input_manager *im,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case SDLK_COMMA:
|
||||||
|
if (control && cmd && shift && !repeat && down) {
|
||||||
|
switch_key_mapping_group(controller, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case SDLK_PERIOD:
|
||||||
|
if (control && cmd && shift && !repeat && down) {
|
||||||
|
switch_key_mapping_group(controller, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -398,7 +428,7 @@ input_manager_process_key(struct input_manager *im,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg, im->prefer_text)) {
|
if (convert_input_key(event, &msg, im->_key_input_mode)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,17 @@
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
|
enum key_input_mode {
|
||||||
|
KEY_COMBINED = 0,
|
||||||
|
KEY_TEXT_PREFERRED = 1,
|
||||||
|
KEY_EVENT_ONLY = 2,
|
||||||
|
};
|
||||||
|
|
||||||
struct input_manager {
|
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;
|
||||||
bool prefer_text;
|
enum key_input_mode _key_input_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -78,6 +78,11 @@ static void usage(const char *arg0) {
|
||||||
" special character, but breaks the expected behavior of alpha\n"
|
" special character, but breaks the expected behavior of alpha\n"
|
||||||
" keys in games (typically WASD).\n"
|
" keys in games (typically WASD).\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --key-event-only\n"
|
||||||
|
" Typing only generates key events rather than text events.\n"
|
||||||
|
" The resulting behaviour is opposite to the one induced by\n"
|
||||||
|
" --prefer-text.\n"
|
||||||
|
"\n"
|
||||||
" --push-target path\n"
|
" --push-target path\n"
|
||||||
" Set the target directory for pushing files to the device by\n"
|
" Set the target directory for pushing files to the device by\n"
|
||||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||||
|
@ -193,6 +198,14 @@ static void usage(const char *arg0) {
|
||||||
" " CTRL_OR_CMD "+i\n"
|
" " CTRL_OR_CMD "+i\n"
|
||||||
" enable/disable FPS counter (print frames/second in logs)\n"
|
" enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Shfit+.\n"
|
||||||
|
" switching to the next key mapping group defined in the xml\n"
|
||||||
|
" (if provided).\n"
|
||||||
|
"\n"
|
||||||
|
" " CTRL_OR_CMD "+Shfit+,\n"
|
||||||
|
" switching to the previous key mapping group defined in the xml\n"
|
||||||
|
" (if provided).\n"
|
||||||
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
" install APK from computer\n"
|
" install APK from computer\n"
|
||||||
"\n",
|
"\n",
|
||||||
|
@ -401,6 +414,7 @@ guess_record_format(const char *filename) {
|
||||||
#define OPT_WINDOW_HEIGHT 1010
|
#define OPT_WINDOW_HEIGHT 1010
|
||||||
#define OPT_WINDOW_BORDERLESS 1011
|
#define OPT_WINDOW_BORDERLESS 1011
|
||||||
#define OPT_MAX_FPS 1012
|
#define OPT_MAX_FPS 1012
|
||||||
|
#define OPT_KEY_EVENT_ONLY 1013
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_args(struct args *args, int argc, char *argv[]) {
|
parse_args(struct args *args, int argc, char *argv[]) {
|
||||||
|
@ -424,6 +438,7 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||||
|
{"key-event-only", no_argument, NULL, OPT_KEY_EVENT_ONLY},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||||
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
{"window-x", required_argument, NULL, OPT_WINDOW_X},
|
||||||
|
@ -541,7 +556,10 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||||
opts->push_target = optarg;
|
opts->push_target = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_PREFER_TEXT:
|
case OPT_PREFER_TEXT:
|
||||||
opts->prefer_text = true;
|
opts->_key_input_mode = KEY_TEXT_PREFERRED;
|
||||||
|
break;
|
||||||
|
case OPT_KEY_EVENT_ONLY:
|
||||||
|
opts->_key_input_mode = KEY_EVENT_ONLY;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
|
|
|
@ -42,7 +42,7 @@ static struct input_manager input_manager = {
|
||||||
.controller = &controller,
|
.controller = &controller,
|
||||||
.video_buffer = &video_buffer,
|
.video_buffer = &video_buffer,
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
.prefer_text = false, // initialized later
|
._key_input_mode = KEY_COMBINED, // initialized later
|
||||||
};
|
};
|
||||||
|
|
||||||
// init SDL and set appropriate hints
|
// init SDL and set appropriate hints
|
||||||
|
@ -418,7 +418,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||||
show_touches_waited = true;
|
show_touches_waited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
input_manager.prefer_text = options->prefer_text;
|
input_manager._key_input_mode = options->_key_input_mode;
|
||||||
|
|
||||||
ret = event_loop(options->display, options->control);
|
ret = event_loop(options->display, options->control);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
|
@ -30,7 +30,7 @@ struct scrcpy_options {
|
||||||
bool display;
|
bool display;
|
||||||
bool turn_screen_off;
|
bool turn_screen_off;
|
||||||
bool render_expired_frames;
|
bool render_expired_frames;
|
||||||
bool prefer_text;
|
enum key_input_mode _key_input_mode;
|
||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ struct scrcpy_options {
|
||||||
.display = true, \
|
.display = true, \
|
||||||
.turn_screen_off = false, \
|
.turn_screen_off = false, \
|
||||||
.render_expired_frames = false, \
|
.render_expired_frames = false, \
|
||||||
.prefer_text = false, \
|
._key_input_mode = KEY_COMBINED, \
|
||||||
.window_borderless = false, \
|
.window_borderless = false, \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,24 +13,30 @@
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
|
||||||
#define SOCKET_NAME "scrcpy"
|
#define SOCKET_NAME "scrcpy"
|
||||||
#define SERVER_FILENAME "scrcpy-server"
|
#define SERVER_FILENAME "scrcpy-server.jar"
|
||||||
|
#define KEY_TO_TOUCH_MAP_FILENAME "key-to-touch-map.xml"
|
||||||
|
|
||||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
#define DEVICE_PATH_PREFIX "/data/local/tmp/"
|
||||||
|
#define DEVICE_SERVER_PATH DEVICE_PATH_PREFIX SERVER_FILENAME
|
||||||
|
#define DEVICE_XML_PATH DEVICE_PATH_PREFIX KEY_TO_TOUCH_MAP_FILENAME
|
||||||
|
|
||||||
static const char *
|
// the returned bool is used to indicate whether callers should be responsible for freeing the memory holding the path string
|
||||||
get_server_path(void) {
|
static bool
|
||||||
|
get_server_path(const char **ptr) {
|
||||||
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
||||||
if (server_path_env) {
|
if (server_path_env) {
|
||||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
|
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
|
||||||
// if the envvar is set, use it
|
// if the envvar is set, use it
|
||||||
return server_path_env;
|
*ptr = server_path_env;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||||
// the absolute path is hardcoded
|
// the absolute path is hardcoded
|
||||||
return DEFAULT_SERVER_PATH;
|
*ptr = DEFAULT_SERVER_PATH;
|
||||||
|
return false;
|
||||||
#else
|
#else
|
||||||
// use scrcpy-server in the same directory as the executable
|
// use scrcpy-server in the same directory as the executable
|
||||||
char *executable_path = get_executable_path();
|
char *executable_path = get_executable_path();
|
||||||
|
@ -38,7 +44,8 @@ get_server_path(void) {
|
||||||
LOGE("Could not get executable path, "
|
LOGE("Could not get executable path, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
// not found, use current directory
|
// not found, use current directory
|
||||||
return SERVER_FILENAME;
|
*ptr = SERVER_FILENAME;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
char *dir = dirname(executable_path);
|
char *dir = dirname(executable_path);
|
||||||
size_t dirlen = strlen(dir);
|
size_t dirlen = strlen(dir);
|
||||||
|
@ -50,7 +57,8 @@ get_server_path(void) {
|
||||||
LOGE("Could not alloc server path string, "
|
LOGE("Could not alloc server path string, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
SDL_free(executable_path);
|
SDL_free(executable_path);
|
||||||
return SERVER_FILENAME;
|
*ptr = SERVER_FILENAME;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(server_path, dir, dirlen);
|
memcpy(server_path, dir, dirlen);
|
||||||
|
@ -61,13 +69,47 @@ get_server_path(void) {
|
||||||
SDL_free(executable_path);
|
SDL_free(executable_path);
|
||||||
|
|
||||||
LOGD("Using server (portable): %s", server_path);
|
LOGD("Using server (portable): %s", server_path);
|
||||||
return server_path;
|
*ptr = server_path;
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_server(const char *serial) {
|
push_server(const char *serial) {
|
||||||
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
|
|
||||||
|
const char *server_path = NULL;
|
||||||
|
bool is_allocated = get_server_path(&server_path);
|
||||||
|
const char *beginningOfFilename = strrchr(server_path, '/');
|
||||||
|
if(beginningOfFilename == NULL)
|
||||||
|
beginningOfFilename = strrchr(server_path, '\\');
|
||||||
|
|
||||||
|
size_t prefix_len = 0;
|
||||||
|
size_t xml_path_len = strlen(KEY_TO_TOUCH_MAP_FILENAME) + 1; // extra one for storing a null character
|
||||||
|
char *xml_path = NULL;
|
||||||
|
if(beginningOfFilename != NULL)
|
||||||
|
{
|
||||||
|
prefix_len = beginningOfFilename - server_path + 1;
|
||||||
|
xml_path_len = prefix_len + xml_path_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_path = SDL_malloc(xml_path_len);
|
||||||
|
if(xml_path == NULL)
|
||||||
|
LOGE("Fail to allocate buffer for \"%s\". Skipping the push of it", KEY_TO_TOUCH_MAP_FILENAME);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strcpy(strncpy(xml_path, server_path, prefix_len) + prefix_len, KEY_TO_TOUCH_MAP_FILENAME);
|
||||||
|
|
||||||
|
process_t process = adb_push(serial, xml_path, DEVICE_XML_PATH);
|
||||||
|
process_check_success(process, "adb push");
|
||||||
|
|
||||||
|
SDL_free(xml_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||||
|
if(is_allocated && server_path != NULL)
|
||||||
|
SDL_free(server_path);
|
||||||
|
|
||||||
return process_check_success(process, "adb push");
|
return process_check_success(process, "adb push");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
93
server/key-to-touch-map.xml
Normal file
93
server/key-to-touch-map.xml
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<KeyToTouch>
|
||||||
|
<!--
|
||||||
|
The following two groups are only for demonstration.
|
||||||
|
Users can design their own mappings and add more groups for meeting their needs.
|
||||||
|
|
||||||
|
The "x" and "y" refer to the physical coordinates of the display of the connected device.
|
||||||
|
Such information can be shown by turning on the option "System"->"Developer options"->"Pointer location"
|
||||||
|
-->
|
||||||
|
<Group name="Portrait_English">
|
||||||
|
|
||||||
|
<Mapping key="\u0009" x="589" y="1114" repeating="false" /> <!--tab-->
|
||||||
|
|
||||||
|
|
||||||
|
<Mapping key="\u0020" x="350" y="1110" repeating="false" /> <!--space-->
|
||||||
|
<Mapping key="\u002C" x="198" y="1114" repeating="false" /> <!--,-->
|
||||||
|
<Mapping key="\u002E" x="508" y="1114" repeating="false" /> <!--.-->
|
||||||
|
|
||||||
|
|
||||||
|
<Mapping key="\u0043" x="52" y="1000" repeating="false" /> <!--C-->
|
||||||
|
|
||||||
|
<Mapping key="\u0061" x="68" y="880" repeating="false" /> <!--a-->
|
||||||
|
<Mapping key="\u0062" x="426" y="1005" repeating="false" /> <!--b-->
|
||||||
|
<Mapping key="\u0063" x="293" y="1006" repeating="false" /> <!--c-->
|
||||||
|
<Mapping key="\u0064" x="214" y="885" repeating="false" /> <!--d-->
|
||||||
|
<Mapping key="\u0065" x="176" y="771" repeating="false" /> <!--e-->
|
||||||
|
<Mapping key="\u0066" x="286" y="890" repeating="false" /> <!--f-->
|
||||||
|
<Mapping key="\u0067" x="355" y="890" repeating="false" /> <!--g-->
|
||||||
|
<Mapping key="\u0068" x="431" y="892" repeating="false" /> <!--h-->
|
||||||
|
<Mapping key="\u0069" x="543" y="778" repeating="false" /> <!--i-->
|
||||||
|
<Mapping key="\u006A" x="505" y="896" repeating="false" /> <!--j-->
|
||||||
|
<Mapping key="\u006B" x="579" y="894" repeating="false" /> <!--k-->
|
||||||
|
<Mapping key="\u006C" x="650" y="890" repeating="false" /> <!--l-->
|
||||||
|
<Mapping key="\u006D" x="555" y="1001" repeating="false" /> <!--m-->
|
||||||
|
<Mapping key="\u006E" x="491" y="990" repeating="false" /> <!--n-->
|
||||||
|
<Mapping key="\u006F" x="610" y="774" repeating="false" /> <!--o-->
|
||||||
|
<Mapping key="\u0070" x="684" y="781" repeating="false" /> <!--p-->
|
||||||
|
<Mapping key="\u0071" x="35" y="778" repeating="false" /> <!--q-->
|
||||||
|
<Mapping key="\u0072" x="248" y="779" repeating="false" /> <!--r-->
|
||||||
|
<Mapping key="\u0073" x="142" y="885" repeating="false" /> <!--s-->
|
||||||
|
<Mapping key="\u0074" x="322" y="785" repeating="false" /> <!--t-->
|
||||||
|
<Mapping key="\u0075" x="470" y="776" repeating="false" /> <!--u-->
|
||||||
|
<Mapping key="\u0076" x="360" y="1006" repeating="false" /> <!--v-->
|
||||||
|
<Mapping key="\u0077" x="107" y="783" repeating="false" /> <!--w-->
|
||||||
|
<Mapping key="\u0078" x="227" y="1006" repeating="false" /> <!--x-->
|
||||||
|
<Mapping key="\u0079" x="396" y="781" repeating="false" /> <!--y-->
|
||||||
|
<Mapping key="\u007A" x="163" y="1005" repeating="false" /> <!--z-->
|
||||||
|
|
||||||
|
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group name="Landscape_English">
|
||||||
|
|
||||||
|
<Mapping key="\u0009" x="1062" y="665" repeating="false" /> <!--tab-->
|
||||||
|
|
||||||
|
|
||||||
|
<Mapping key="\u0020" x="675" y="660" repeating="false" /> <!--space-->
|
||||||
|
<Mapping key="\u002C" x="423" y="663" repeating="false" /> <!--,-->
|
||||||
|
<Mapping key="\u002E" x="935" y="661" repeating="false" /> <!--.-->
|
||||||
|
|
||||||
|
<Mapping key="\u0043" x="182" y="562" repeating="false" /> <!--C-->
|
||||||
|
|
||||||
|
<Mapping key="\u0061" x="211" y="472" repeating="false" /> <!--a-->
|
||||||
|
<Mapping key="\u0062" x="793" y="568" repeating="false" /> <!--b-->
|
||||||
|
<Mapping key="\u0063" x="580" y="570" repeating="false" /> <!--c-->
|
||||||
|
<Mapping key="\u0064" x="449" y="472" repeating="false" /> <!--d-->
|
||||||
|
<Mapping key="\u0065" x="387" y="384" repeating="false" /> <!--e-->
|
||||||
|
<Mapping key="\u0066" x="567" y="472" repeating="false" /> <!--f-->
|
||||||
|
<Mapping key="\u0067" x="686" y="472" repeating="false" /> <!--g-->
|
||||||
|
<Mapping key="\u0068" x="807" y="474" repeating="false" /> <!--h-->
|
||||||
|
<Mapping key="\u0069" x="984" y="382" repeating="false" /> <!--i-->
|
||||||
|
<Mapping key="\u006A" x="926" y="474" repeating="false" /> <!--j-->
|
||||||
|
<Mapping key="\u006B" x="1045" y="474" repeating="false" /> <!--k-->
|
||||||
|
<Mapping key="\u006C" x="1157" y="471" repeating="false" /> <!--l-->
|
||||||
|
<Mapping key="\u006D" x="1007" y="568" repeating="false" /> <!--m-->
|
||||||
|
<Mapping key="\u006E" x="905" y="568" repeating="false" /> <!--n-->
|
||||||
|
<Mapping key="\u006F" x="1102" y="384" repeating="false" /> <!--o-->
|
||||||
|
<Mapping key="\u0070" x="1224" y="387" repeating="false" /> <!--p-->
|
||||||
|
<Mapping key="\u0071" x="155" y="378" repeating="false" /> <!--q-->
|
||||||
|
<Mapping key="\u0072" x="512" y="384" repeating="false" /> <!--r-->
|
||||||
|
<Mapping key="\u0073" x="330" y="476" repeating="false" /> <!--s-->
|
||||||
|
<Mapping key="\u0074" x="629" y="385" repeating="false" /> <!--t-->
|
||||||
|
<Mapping key="\u0075" x="865" y="382" repeating="false" /> <!--u-->
|
||||||
|
<Mapping key="\u0076" x="686" y="570" repeating="false" /> <!--v-->
|
||||||
|
<Mapping key="\u0077" x="272" y="384" repeating="false" /> <!--w-->
|
||||||
|
<Mapping key="\u0078" x="471" y="570" repeating="false" /> <!--x-->
|
||||||
|
<Mapping key="\u0079" x="744" y="382" repeating="false" /> <!--y-->
|
||||||
|
<Mapping key="\u007A" x="368" y="568" repeating="false" /> <!--z-->
|
||||||
|
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
|
||||||
|
</KeyToTouch>
|
|
@ -21,3 +21,10 @@ else
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: 'share/scrcpy')
|
install_dir: 'share/scrcpy')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
custom_target('copy_xml',
|
||||||
|
input: 'key-to-touch-map.xml',
|
||||||
|
output: 'key-to-touch-map.xml',
|
||||||
|
command: ['cp', '@INPUT@', '@OUTPUT@'],
|
||||||
|
install: true,
|
||||||
|
install_dir: 'share/scrcpy')
|
||||||
|
|
|
@ -15,11 +15,12 @@ public final class ControlMessage {
|
||||||
public static final int TYPE_GET_CLIPBOARD = 7;
|
public static final int TYPE_GET_CLIPBOARD = 7;
|
||||||
public static final int TYPE_SET_CLIPBOARD = 8;
|
public static final int TYPE_SET_CLIPBOARD = 8;
|
||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
||||||
|
public static final int TYPE_SWITCH_KEY_MAPPING_GROUP = 10;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private int metaState; // KeyEvent.META_*
|
private int metaState; // KeyEvent.META_*
|
||||||
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
|
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* or the direction of switching
|
||||||
private int keycode; // KeyEvent.KEYCODE_*
|
private int keycode; // KeyEvent.KEYCODE_*
|
||||||
private int buttons; // MotionEvent.BUTTON_*
|
private int buttons; // MotionEvent.BUTTON_*
|
||||||
private long pointerId;
|
private long pointerId;
|
||||||
|
@ -74,6 +75,13 @@ public final class ControlMessage {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createSwitchKeyMappingGroup(int direction) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_SWITCH_KEY_MAPPING_GROUP;
|
||||||
|
msg.action = direction;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,6 +13,7 @@ public class ControlMessageReader {
|
||||||
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
|
private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
|
||||||
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
private static final int SWITCH_KEY_MAPPING_GROUP_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
public static final int TEXT_MAX_LENGTH = 300;
|
public static final int TEXT_MAX_LENGTH = 300;
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
|
||||||
|
@ -72,6 +73,9 @@ public class ControlMessageReader {
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SWITCH_KEY_MAPPING_GROUP:
|
||||||
|
msg = parseSwitchKeyMappingGroup();
|
||||||
|
break;
|
||||||
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:
|
||||||
|
@ -155,6 +159,14 @@ public class ControlMessageReader {
|
||||||
return ControlMessage.createSetClipboard(text);
|
return ControlMessage.createSetClipboard(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseSwitchKeyMappingGroup() {
|
||||||
|
if (buffer.remaining() < SWITCH_KEY_MAPPING_GROUP_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int direction = buffer.get();
|
||||||
|
return ControlMessage.createSwitchKeyMappingGroup(direction);
|
||||||
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
private ControlMessage parseSetScreenPowerMode() {
|
||||||
if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.key2touch.TouchEvent;
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
@ -21,6 +22,10 @@ public class Controller {
|
||||||
|
|
||||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
|
private int lastKeyCodeAction = -1;
|
||||||
|
private int lastKeyCode = -1;
|
||||||
|
private long lastKeyCodeTimestamp = 0;
|
||||||
|
|
||||||
private long lastTouchDown;
|
private long lastTouchDown;
|
||||||
private final PointersState pointersState = new PointersState();
|
private final PointersState pointersState = new PointersState();
|
||||||
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
|
@ -106,14 +111,69 @@ public class Controller {
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
device.setScreenPowerMode(msg.getAction());
|
device.setScreenPowerMode(msg.getAction());
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SWITCH_KEY_MAPPING_GROUP:
|
||||||
|
if(msg.getAction() == 0)
|
||||||
|
KeyToTouchMap.instance.previousGroup();
|
||||||
|
else
|
||||||
|
KeyToTouchMap.instance.nextGroup();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean injectKeycode(int action, int keycode, int metaState) {
|
private boolean injectKeycode(int action, int keycode, int metaState) {
|
||||||
|
int key = charMap.get(keycode, metaState);
|
||||||
|
boolean keyRepeating = false;
|
||||||
|
if(lastKeyCode == key && lastKeyCodeAction == action && action == KeyEvent.ACTION_DOWN)
|
||||||
|
keyRepeating = true;
|
||||||
|
|
||||||
|
lastKeyCode = key;
|
||||||
|
lastKeyCodeAction = action;
|
||||||
|
|
||||||
|
TouchEvent touchEvent = KeyToTouchMap.instance.lookup(key);
|
||||||
|
if(touchEvent == null) {
|
||||||
|
lastKeyCodeTimestamp = SystemClock.uptimeMillis();
|
||||||
return injectKeyEvent(action, keycode, 0, metaState);
|
return injectKeyEvent(action, keycode, 0, metaState);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(keyRepeating)
|
||||||
|
{
|
||||||
|
if(!touchEvent.repeating)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(SystemClock.uptimeMillis() < lastKeyCodeTimestamp + touchEvent.repeatingInterval)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case KeyEvent.ACTION_DOWN: {
|
||||||
|
action = MotionEvent.ACTION_DOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KeyEvent.ACTION_UP: {
|
||||||
|
action = MotionEvent.ACTION_UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(keyRepeating)
|
||||||
|
injectTouch(MotionEvent.ACTION_UP, touchEvent.pointerId, touchEvent.point, touchEvent.pressure, touchEvent.buttons, lastKeyCodeTimestamp+1);
|
||||||
|
|
||||||
|
lastKeyCodeTimestamp = SystemClock.uptimeMillis();
|
||||||
|
return injectTouch(action, touchEvent.pointerId, touchEvent.point, touchEvent.pressure, touchEvent.buttons, lastKeyCodeTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectChar(char c) {
|
||||||
String decomposed = KeyComposition.decompose(c);
|
String decomposed = KeyComposition.decompose(c);
|
||||||
|
@ -137,25 +197,33 @@ public class Controller {
|
||||||
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
|
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
return successCount;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
|
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
|
||||||
|
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
// ignore event
|
// ignore event
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
|
return injectTouch(action, pointerId, point, pressure, buttons, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean injectTouch(int action, long pointerId, Point point, float pressure, int buttons, long now) {
|
||||||
|
|
||||||
|
|
||||||
int pointerIndex = pointersState.getPointerIndex(pointerId);
|
int pointerIndex = pointersState.getPointerIndex(pointerId);
|
||||||
if (pointerIndex == -1) {
|
if (pointerIndex == -1) {
|
||||||
Ln.w("Too many pointers for touch event");
|
Ln.w("Too many pointers for touch event");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pointer pointer = pointersState.get(pointerIndex);
|
Pointer pointer = pointersState.get(pointerIndex);
|
||||||
pointer.setPoint(point);
|
pointer.setPoint(point);
|
||||||
pointer.setPressure(pressure);
|
pointer.setPressure(pressure);
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.util.Xml;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.key2touch.MappingGroup;
|
||||||
|
import com.genymobile.scrcpy.key2touch.TouchEvent;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class KeyToTouchMap {
|
||||||
|
|
||||||
|
|
||||||
|
public static final KeyToTouchMap instance = new KeyToTouchMap();
|
||||||
|
|
||||||
|
|
||||||
|
private final ArrayList<MappingGroup> groupList = new ArrayList<MappingGroup>();
|
||||||
|
private int currentGroup = 0;
|
||||||
|
|
||||||
|
|
||||||
|
private KeyToTouchMap() {
|
||||||
|
groupList.add(new MappingGroup("None"));
|
||||||
|
currentGroup = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextGroup()
|
||||||
|
{
|
||||||
|
++currentGroup;
|
||||||
|
if(currentGroup >= groupList.size())
|
||||||
|
currentGroup = 0;
|
||||||
|
|
||||||
|
Ln.i("KeyToTouchMap: Current group " + groupList.get(currentGroup).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void previousGroup()
|
||||||
|
{
|
||||||
|
--currentGroup;
|
||||||
|
if(currentGroup < 0)
|
||||||
|
currentGroup = groupList.size() - 1;
|
||||||
|
|
||||||
|
Ln.i("KeyToTouchMap: Current group " + groupList.get(currentGroup).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TouchEvent lookup(int keyCode)
|
||||||
|
{
|
||||||
|
MappingGroup group = groupList.get(currentGroup);
|
||||||
|
return group.lookup(keyCode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(String inputFilePath)
|
||||||
|
{
|
||||||
|
MappingGroup noneGroup = groupList.get(0);
|
||||||
|
groupList.clear();
|
||||||
|
groupList.add(noneGroup);
|
||||||
|
currentGroup = 0;
|
||||||
|
try(FileInputStream input = new FileInputStream(inputFilePath))
|
||||||
|
{
|
||||||
|
XmlPullParser parser = Xml.newPullParser();
|
||||||
|
parser.setInput(input, null);
|
||||||
|
parser.nextTag();
|
||||||
|
|
||||||
|
while (parser.nextTag() != XmlPullParser.END_TAG) {
|
||||||
|
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(parser.getName().compareTo(MappingGroup.GROUP_TAG) != 0)
|
||||||
|
MappingGroup.skip(parser);
|
||||||
|
|
||||||
|
try {
|
||||||
|
groupList.add(new MappingGroup(parser));
|
||||||
|
}
|
||||||
|
catch (Exception parsingException)
|
||||||
|
{
|
||||||
|
Ln.e("Fail to parse MappingGroup", parsingException);
|
||||||
|
MappingGroup.skip(parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception parsingException)
|
||||||
|
{
|
||||||
|
Ln.e(String.format("Fail to parse %s", inputFilePath), parsingException);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.io.IOException;
|
||||||
public final class Server {
|
public final class Server {
|
||||||
|
|
||||||
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
||||||
|
private static final String KEY_TO_TOUCH_MAP_PATH = "/data/local/tmp/key-to-touch-map.xml";
|
||||||
|
|
||||||
private Server() {
|
private Server() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
|
@ -131,7 +132,12 @@ public final class Server {
|
||||||
try {
|
try {
|
||||||
new File(SERVER_PATH).delete();
|
new File(SERVER_PATH).delete();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Ln.e("Could not unlink server", e);
|
Ln.e(String.format("Could not unlink %s", SERVER_PATH), e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new File(KEY_TO_TOUCH_MAP_PATH).delete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Ln.e(String.format("Could not unlink %s", KEY_TO_TOUCH_MAP_PATH), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +163,12 @@ public final class Server {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if(new File(KEY_TO_TOUCH_MAP_PATH).exists())
|
||||||
|
KeyToTouchMap.instance.parse(KEY_TO_TOUCH_MAP_PATH);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
Options options = createOptions(args);
|
Options options = createOptions(args);
|
||||||
scrcpy(options);
|
scrcpy(options);
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.genymobile.scrcpy.key2touch;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
import com.genymobile.scrcpy.Point;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public final class MappingGroup {
|
||||||
|
|
||||||
|
public static final String GROUP_TAG = "Group";
|
||||||
|
private static final String MAPPING_TAG = "Mapping";
|
||||||
|
private static final String NAME_ATTR = "name";
|
||||||
|
private static final String KEY_ATTR = "key";
|
||||||
|
private static final String X_ATTR = "x";
|
||||||
|
private static final String Y_ATTR = "y";
|
||||||
|
private static final String REPEATING_ATTR = "repeating";
|
||||||
|
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
private HashMap<Integer, TouchEvent> map = new HashMap<Integer, TouchEvent>();
|
||||||
|
|
||||||
|
private boolean parseMapping(XmlPullParser parser)
|
||||||
|
{
|
||||||
|
if (parser.getName().compareTo(MAPPING_TAG) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String keyStr = parser.getAttributeValue(null, KEY_ATTR);
|
||||||
|
int key = -1;
|
||||||
|
if (keyStr != null && keyStr.toLowerCase().startsWith("\\u")) {
|
||||||
|
key = Integer.valueOf(keyStr.substring(2), 16);
|
||||||
|
} else
|
||||||
|
throw new NumberFormatException("The format is invalid: " + keyStr);
|
||||||
|
|
||||||
|
|
||||||
|
int x = Integer.valueOf(parser.getAttributeValue(null, X_ATTR));
|
||||||
|
int y = Integer.valueOf(parser.getAttributeValue(null, Y_ATTR));
|
||||||
|
boolean repeating = Boolean.parseBoolean(parser.getAttributeValue(null, REPEATING_ATTR));
|
||||||
|
Ln.d("key = " + Character.toChars(key)[0] + ", x = " + x + ", y = " + y + ", repeating = " + repeating);
|
||||||
|
map.put(key, new TouchEvent(new Point(x, y), repeating));
|
||||||
|
parser.nextTag();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception parsingException)
|
||||||
|
{
|
||||||
|
Ln.e(String.format("Error while parsing %s", this.name), parsingException);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingGroup(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingGroup(XmlPullParser parser) throws Exception
|
||||||
|
{
|
||||||
|
name = parser.getAttributeValue(null, NAME_ATTR);
|
||||||
|
while (parser.nextTag() != XmlPullParser.END_TAG) {
|
||||||
|
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!parseMapping(parser))
|
||||||
|
skip(parser);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public TouchEvent lookup(int keyCode)
|
||||||
|
{
|
||||||
|
return map.get(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void skip(XmlPullParser parser) throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
int depth = 1;
|
||||||
|
while (depth != 0) {
|
||||||
|
switch (parser.next()) {
|
||||||
|
case XmlPullParser.END_TAG:
|
||||||
|
Ln.w(String.format("MappingGroup skipping: %s", parser.getName()));
|
||||||
|
depth--;
|
||||||
|
break;
|
||||||
|
case XmlPullParser.START_TAG:
|
||||||
|
depth++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.genymobile.scrcpy.key2touch;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Point;
|
||||||
|
|
||||||
|
public final class TouchEvent {
|
||||||
|
|
||||||
|
public final Point point;
|
||||||
|
public final boolean repeating;
|
||||||
|
|
||||||
|
public static final int buttons = 1;
|
||||||
|
public static final float pressure = 1.0f;
|
||||||
|
public static final long pointerId = -2;
|
||||||
|
public static final long repeatingInterval = 100;
|
||||||
|
|
||||||
|
public TouchEvent(Point point, boolean repeating)
|
||||||
|
{
|
||||||
|
this.point = point;
|
||||||
|
this.repeating = repeating;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue