Compare commits
5 Commits
textevents
...
mainlooper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da4c93582 | ||
|
|
b08a98324d | ||
|
|
c916af0984 | ||
|
|
ff061b4f30 | ||
|
|
157c60feb4 |
@@ -143,7 +143,6 @@ 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.
|
||||||
@@ -226,7 +225,6 @@ The window of app can always be above others by:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --always-on-top
|
scrcpy --always-on-top
|
||||||
scrcpy -T # short version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ Set the TCP port the client listens on.
|
|||||||
|
|
||||||
Default is 27183.
|
Default is 27183.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-prefer\-text
|
||||||
|
Inject alpha characters and space as text events instead of key events.
|
||||||
|
|
||||||
|
This avoids issues when combining multiple keys to enter special characters,
|
||||||
|
but breaks the expected behavior of alpha keys in games (typically WASD).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-push\-target " path
|
.BI "\-\-push\-target " path
|
||||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ 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) {
|
||||||
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);
|
||||||
@@ -92,6 +93,12 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
|
|||||||
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
|
||||||
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefer_text) {
|
||||||
|
// do not forward alpha and space key events
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ enum android_metastate
|
|||||||
convert_meta_state(SDL_Keymod mod);
|
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 android_motionevent_buttons
|
enum android_motionevent_buttons
|
||||||
convert_mouse_buttons(uint32_t state);
|
convert_mouse_buttons(uint32_t state);
|
||||||
|
|||||||
@@ -214,12 +214,15 @@ 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) {
|
||||||
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');
|
||||||
// letters and space are handled as raw key event
|
// letters and space are handled as raw key event
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = SDL_strdup(event->text);
|
msg.inject_text.text = SDL_strdup(event->text);
|
||||||
@@ -234,7 +237,8 @@ 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) {
|
||||||
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)) {
|
||||||
@@ -242,7 +246,8 @@ 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)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +398,7 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_input_key(event, &msg)) {
|
if (convert_input_key(event, &msg, im->prefer_text)) {
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'inject keycode'");
|
LOGW("Could not request 'inject keycode'");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ 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"
|
||||||
|
" Inject alpha characters and space as text events instead of\n"
|
||||||
|
" key events.\n"
|
||||||
|
" This avoids issues when combining multiple keys to enter a\n"
|
||||||
|
" special character, but breaks the expected behavior of alpha\n"
|
||||||
|
" keys in games (typically WASD).\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"
|
||||||
@@ -297,28 +304,32 @@ guess_record_format(const char *filename) {
|
|||||||
#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_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, 'T'},
|
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
|
||||||
{"bit-rate", required_argument, NULL, 'b'},
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
{"crop", required_argument, NULL, 'c'},
|
{"crop", required_argument, NULL, OPT_CROP},
|
||||||
{"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,
|
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||||
OPT_PUSH_TARGET},
|
|
||||||
{"record", required_argument, NULL, 'r'},
|
{"record", required_argument, NULL, 'r'},
|
||||||
{"record-format", required_argument, NULL, 'F'},
|
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
||||||
{"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},
|
||||||
{"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},
|
||||||
@@ -337,12 +348,18 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -379,6 +396,9 @@ 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':
|
||||||
@@ -393,6 +413,9 @@ 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:
|
||||||
|
opts->prefer_text = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -174,11 +174,16 @@ 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;
|
recorder->failed = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// the recorded file is empty
|
||||||
|
recorder->failed = true;
|
||||||
|
}
|
||||||
avio_close(recorder->ctx->pb);
|
avio_close(recorder->ctx->pb);
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +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
|
||||||
};
|
};
|
||||||
|
|
||||||
// init SDL and set appropriate hints
|
// init SDL and set appropriate hints
|
||||||
@@ -414,6 +415,8 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
show_touches_waited = true;
|
show_touches_waited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_manager.prefer_text = options->prefer_text;
|
||||||
|
|
||||||
ret = event_loop(options->display, options->control);
|
ret = event_loop(options->display, options->control);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "input_manager.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
|
|
||||||
struct scrcpy_options {
|
struct scrcpy_options {
|
||||||
@@ -24,6 +25,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
@@ -43,6 +45,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, \
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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;
|
||||||
@@ -54,6 +55,11 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user