Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
532843d856 Always prefer text events by default
This improves text input, but it breaks the expected behavior in games
again: <https://github.com/Genymobile/scrcpy/issues/87>.

To get the previous behavior back, pass an explicit option:

    scrcpy --prefer-text-events=non-alpha
2019-11-06 23:25:15 +01:00
Romain Vimont
44791d6b40 Add --prefer-text-events option
Expose an option to configure how key/text events are forwarded to the
Android device.

Fixes <https://github.com/Genymobile/scrcpy/issues/650>
2019-11-06 23:20:51 +01:00
11 changed files with 92 additions and 59 deletions

View File

@@ -143,6 +143,7 @@ This is useful for example to mirror only one eye of the Oculus Go:
```bash ```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
scrcpy -c 1224:1440:0:0 # short version
``` ```
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
@@ -225,6 +226,7 @@ The window of app can always be above others by:
```bash ```bash
scrcpy --always-on-top scrcpy --always-on-top
scrcpy -T # short version
``` ```

View File

@@ -62,11 +62,14 @@ Set the TCP port the client listens on.
Default is 27183. Default is 27183.
.TP .TP
.B \-\-prefer\-text .BI \-\-prefer\-text\-events " mode
Inject alpha characters and space as text events instead of key events. Configure how key/text events are forwarded to the Android device.
This avoids issues when combining multiple keys to enter special characters, Possible \fImode\fRs are "always" (every text is sent as text), "non-alpha"
but breaks the expected behavior of alpha keys in games (typically WASD). (only letters are sent as a sequence of key events, other characters are sent
as text) and "never" (every text is sent as a sequence of key events).
Default is "always".
.TP .TP
.BI "\-\-push\-target " path .BI "\-\-push\-target " path

View File

@@ -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 text_events_pref pref) {
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,11 +94,15 @@ 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 (pref == PREFER_TEXT_EVENTS_ALWAYS) {
// do not forward alpha and space key events // never forward key events
return false; return false;
} }
// forward all supported key events
SDL_assert(pref == PREFER_TEXT_EVENTS_NEVER ||
pref == PREFER_TEXT_EVENTS_NON_ALPHA);
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) { if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false; return false;
} }

View File

@@ -2,10 +2,12 @@
#define CONVERT_H #define CONVERT_H
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#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 +17,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 text_events_pref pref);
enum android_motionevent_buttons enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state); convert_mouse_buttons(uint32_t state);

View File

@@ -214,7 +214,12 @@ 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) { if (im->text_events_pref == PREFER_TEXT_EVENTS_NEVER) {
// ignore all text events (key events will be injected instead)
return;
}
if (im->text_events_pref == PREFER_TEXT_EVENTS_NON_ALPHA) {
char c = event->text[0]; char c = event->text[0];
if (isalpha(c) || c == ' ') { if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0'); SDL_assert(event->text[1] == '\0');
@@ -238,7 +243,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 text_events_pref pref) {
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 +252,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)) { pref)) {
return false; return false;
} }
@@ -398,7 +403,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->text_events_pref)) {
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'"); LOGW("Could not request 'inject keycode'");
} }

View File

@@ -10,11 +10,17 @@
#include "video_buffer.h" #include "video_buffer.h"
#include "screen.h" #include "screen.h"
enum text_events_pref {
PREFER_TEXT_EVENTS_ALWAYS,
PREFER_TEXT_EVENTS_NON_ALPHA,
PREFER_TEXT_EVENTS_NEVER,
};
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 text_events_pref text_events_pref;
}; };
void void

View File

@@ -11,6 +11,7 @@
#include "config.h" #include "config.h"
#include "compat.h" #include "compat.h"
#include "log.h" #include "log.h"
#include "input_manager.h"
#include "recorder.h" #include "recorder.h"
struct args { struct args {
@@ -67,12 +68,17 @@ static void usage(const char *arg0) {
" Set the TCP port the client listens on.\n" " Set the TCP port the client listens on.\n"
" Default is %d.\n" " Default is %d.\n"
"\n" "\n"
" --prefer-text\n" " --prefer-text-events mode\n"
" Inject alpha characters and space as text events instead of\n" " Configure how key/text events are forwarded to the Android\n"
" key events.\n" " device.\n"
" This avoids issues when combining multiple keys to enter a\n" " Possible values are:\n"
" special character, but breaks the expected behavior of alpha\n" " always:\n"
" keys in games (typically WASD).\n" " Every text is sent as text. (default)\n"
" non-alpha:\n"
" Only letters are sent as a sequence of key events, other\n"
" characters are sent as text.\n"
" never:\n"
" Every text is sent as a sequence of key events.\n"
"\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"
@@ -301,38 +307,60 @@ guess_record_format(const char *filename) {
return 0; return 0;
} }
static bool
parse_prefer_text_events(const char *optarg,
enum text_events_pref *pref) {
if (!strcmp(optarg, "always")) {
*pref = PREFER_TEXT_EVENTS_ALWAYS;
return true;
}
if (!strcmp(optarg, "non-alpha")) {
*pref = PREFER_TEXT_EVENTS_NON_ALPHA;
return true;
}
if (!strcmp(optarg, "never")) {
*pref = PREFER_TEXT_EVENTS_NEVER;
return true;
}
LOGE("Unsupported text events preference: %s"
"(expected 'always', 'non-alpha' or 'never')", optarg);
return false;
}
#define OPT_RENDER_EXPIRED_FRAMES 1000 #define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001 #define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002 #define OPT_PUSH_TARGET 1002
#define OPT_ALWAYS_ON_TOP 1003 #define OPT_PREFER_TEXT_EVENTS 1003
#define OPT_CROP 1004
#define OPT_RECORD_FORMAT 1005
#define OPT_PREFER_TEXT 1006
static bool static bool
parse_args(struct args *args, int argc, char *argv[]) { parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"always-on-top", no_argument, NULL, 'T'},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, OPT_CROP}, {"crop", required_argument, NULL, 'c'},
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'}, {"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'}, {"no-display", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"push-target", required_argument, NULL,
OPT_PUSH_TARGET},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, {"record-format", required_argument, NULL, 'F'},
{"render-expired-frames", no_argument, NULL, {"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES}, OPT_RENDER_EXPIRED_FRAMES},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},
{"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-events", required_argument, NULL,
OPT_PREFER_TEXT_EVENTS},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, {"window-title", required_argument, NULL,
OPT_WINDOW_TITLE}, OPT_WINDOW_TITLE},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
@@ -348,18 +376,12 @@ parse_args(struct args *args, int argc, char *argv[]) {
} }
break; break;
case 'c': case 'c':
LOGW("Deprecated option -c. Use --crop instead.");
// fall through
case OPT_CROP:
opts->crop = optarg; opts->crop = optarg;
break; break;
case 'f': case 'f':
opts->fullscreen = true; opts->fullscreen = true;
break; break;
case 'F': case 'F':
LOGW("Deprecated option -F. Use --record-format instead.");
// fall through
case OPT_RECORD_FORMAT:
if (!parse_record_format(optarg, &opts->record_format)) { if (!parse_record_format(optarg, &opts->record_format)) {
return false; return false;
} }
@@ -396,9 +418,6 @@ parse_args(struct args *args, int argc, char *argv[]) {
opts->show_touches = true; opts->show_touches = true;
break; break;
case 'T': case 'T':
LOGW("Deprecated option -T. Use --always-on-top instead.");
// fall through
case OPT_ALWAYS_ON_TOP:
opts->always_on_top = true; opts->always_on_top = true;
break; break;
case 'v': case 'v':
@@ -413,8 +432,11 @@ parse_args(struct args *args, int argc, char *argv[]) {
case OPT_PUSH_TARGET: case OPT_PUSH_TARGET:
opts->push_target = optarg; opts->push_target = optarg;
break; break;
case OPT_PREFER_TEXT: case OPT_PREFER_TEXT_EVENTS:
opts->prefer_text = true; if (!parse_prefer_text_events(optarg,
&opts->text_events_pref)) {
return false;
}
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr

View File

@@ -174,14 +174,9 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
void void
recorder_close(struct recorder *recorder) { recorder_close(struct recorder *recorder) {
if (recorder->header_written) { int ret = av_write_trailer(recorder->ctx);
int ret = av_write_trailer(recorder->ctx); if (ret < 0) {
if (ret < 0) { LOGE("Failed to write trailer to %s", recorder->filename);
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true; recorder->failed = true;
} }
avio_close(recorder->ctx->pb); avio_close(recorder->ctx->pb);

View File

@@ -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 .text_events_pref = 0, // initialized later
}; };
// init SDL and set appropriate hints // init SDL and set appropriate hints
@@ -415,7 +415,7 @@ scrcpy(const struct scrcpy_options *options) {
show_touches_waited = true; show_touches_waited = true;
} }
input_manager.prefer_text = options->prefer_text; input_manager.text_events_pref = options->text_events_pref;
ret = event_loop(options->display, options->control); ret = event_loop(options->display, options->control);
LOGD("quit..."); LOGD("quit...");

View File

@@ -15,6 +15,7 @@ struct scrcpy_options {
const char *window_title; const char *window_title;
const char *push_target; const char *push_target;
enum recorder_format record_format; enum recorder_format record_format;
enum text_events_pref text_events_pref;
uint16_t port; uint16_t port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
@@ -25,7 +26,6 @@ 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;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@@ -35,6 +35,7 @@ struct scrcpy_options {
.window_title = NULL, \ .window_title = NULL, \
.push_target = NULL, \ .push_target = NULL, \
.record_format = RECORDER_FORMAT_AUTO, \ .record_format = RECORDER_FORMAT_AUTO, \
.text_events_pref = PREFER_TEXT_EVENTS_ALWAYS, \
.port = DEFAULT_LOCAL_PORT, \ .port = DEFAULT_LOCAL_PORT, \
.max_size = DEFAULT_LOCAL_PORT, \ .max_size = DEFAULT_LOCAL_PORT, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
@@ -45,7 +46,6 @@ 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, \
} }
bool bool

View File

@@ -7,7 +7,6 @@ import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@@ -55,11 +54,6 @@ public class ScreenEncoder implements Device.RotationListener {
} }
public void streamScreen(Device device, FileDescriptor fd) throws IOException { public void streamScreen(Device device, FileDescriptor fd) throws IOException {
// Some devices internally create a Handler when creating an input Surface, causing an exception:
// "Can't create handler inside thread that has not called Looper.prepare()"
// <https://github.com/Genymobile/scrcpy/issues/240>
Looper.prepareMainLooper();
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;