Compare commits
2 Commits
meizu
...
textevents
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532843d856 | ||
|
|
44791d6b40 |
@@ -143,6 +143,7 @@ This is useful for example to mirror only one eye of the Oculus Go:
|
||||
|
||||
```bash
|
||||
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.
|
||||
@@ -225,6 +226,7 @@ The window of app can always be above others by:
|
||||
|
||||
```bash
|
||||
scrcpy --always-on-top
|
||||
scrcpy -T # short version
|
||||
```
|
||||
|
||||
|
||||
|
||||
11
app/scrcpy.1
11
app/scrcpy.1
@@ -62,11 +62,14 @@ Set the TCP port the client listens on.
|
||||
Default is 27183.
|
||||
|
||||
.TP
|
||||
.B \-\-prefer\-text
|
||||
Inject alpha characters and space as text events instead of key events.
|
||||
.BI \-\-prefer\-text\-events " mode
|
||||
Configure how key/text events are forwarded to the Android device.
|
||||
|
||||
This avoids issues when combining multiple keys to enter special characters,
|
||||
but breaks the expected behavior of alpha keys in games (typically WASD).
|
||||
Possible \fImode\fRs are "always" (every text is sent as text), "non-alpha"
|
||||
(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
|
||||
.BI "\-\-push\-target " path
|
||||
|
||||
@@ -76,7 +76,7 @@ convert_meta_state(SDL_Keymod mod) {
|
||||
|
||||
bool
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
bool prefer_text) {
|
||||
enum text_events_pref pref) {
|
||||
switch (from) {
|
||||
MAP(SDLK_RETURN, AKEYCODE_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);
|
||||
}
|
||||
|
||||
if (prefer_text) {
|
||||
// do not forward alpha and space key events
|
||||
if (pref == PREFER_TEXT_EVENTS_ALWAYS) {
|
||||
// never forward key events
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#define CONVERT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "control_msg.h"
|
||||
#include "input_manager.h"
|
||||
|
||||
bool
|
||||
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
|
||||
@@ -15,7 +17,7 @@ convert_meta_state(SDL_Keymod mod);
|
||||
|
||||
bool
|
||||
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
|
||||
bool prefer_text);
|
||||
enum text_events_pref pref);
|
||||
|
||||
enum android_motionevent_buttons
|
||||
convert_mouse_buttons(uint32_t state);
|
||||
|
||||
@@ -214,7 +214,12 @@ clipboard_paste(struct controller *controller) {
|
||||
void
|
||||
input_manager_process_text_input(struct input_manager *im,
|
||||
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];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
SDL_assert(event->text[1] == '\0');
|
||||
@@ -238,7 +243,7 @@ input_manager_process_text_input(struct input_manager *im,
|
||||
|
||||
static bool
|
||||
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;
|
||||
|
||||
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;
|
||||
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
|
||||
prefer_text)) {
|
||||
pref)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -398,7 +403,7 @@ input_manager_process_key(struct input_manager *im,
|
||||
}
|
||||
|
||||
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)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
|
||||
@@ -10,11 +10,17 @@
|
||||
#include "video_buffer.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 controller *controller;
|
||||
struct video_buffer *video_buffer;
|
||||
struct screen *screen;
|
||||
bool prefer_text;
|
||||
enum text_events_pref text_events_pref;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "log.h"
|
||||
#include "input_manager.h"
|
||||
#include "recorder.h"
|
||||
|
||||
struct args {
|
||||
@@ -67,12 +68,17 @@ static void usage(const char *arg0) {
|
||||
" Set the TCP port the client listens on.\n"
|
||||
" Default is %d.\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"
|
||||
" --prefer-text-events mode\n"
|
||||
" Configure how key/text events are forwarded to the Android\n"
|
||||
" device.\n"
|
||||
" Possible values are:\n"
|
||||
" always:\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"
|
||||
" --push-target path\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;
|
||||
}
|
||||
|
||||
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_WINDOW_TITLE 1001
|
||||
#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
|
||||
#define OPT_PREFER_TEXT_EVENTS 1003
|
||||
|
||||
static bool
|
||||
parse_args(struct args *args, int argc, char *argv[]) {
|
||||
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'},
|
||||
{"crop", required_argument, NULL, OPT_CROP},
|
||||
{"crop", required_argument, NULL, 'c'},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"max-size", required_argument, NULL, 'm'},
|
||||
{"no-control", no_argument, NULL, 'n'},
|
||||
{"no-display", no_argument, NULL, 'N'},
|
||||
{"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-format", required_argument, NULL, OPT_RECORD_FORMAT},
|
||||
{"record-format", required_argument, NULL, 'F'},
|
||||
{"render-expired-frames", no_argument, NULL,
|
||||
OPT_RENDER_EXPIRED_FRAMES},
|
||||
OPT_RENDER_EXPIRED_FRAMES},
|
||||
{"serial", required_argument, NULL, 's'},
|
||||
{"show-touches", no_argument, NULL, 't'},
|
||||
{"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'},
|
||||
{"window-title", required_argument, NULL,
|
||||
OPT_WINDOW_TITLE},
|
||||
OPT_WINDOW_TITLE},
|
||||
{NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
@@ -348,18 +376,12 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
LOGW("Deprecated option -c. Use --crop instead.");
|
||||
// fall through
|
||||
case OPT_CROP:
|
||||
opts->crop = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
opts->fullscreen = true;
|
||||
break;
|
||||
case 'F':
|
||||
LOGW("Deprecated option -F. Use --record-format instead.");
|
||||
// fall through
|
||||
case OPT_RECORD_FORMAT:
|
||||
if (!parse_record_format(optarg, &opts->record_format)) {
|
||||
return false;
|
||||
}
|
||||
@@ -396,9 +418,6 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
opts->show_touches = true;
|
||||
break;
|
||||
case 'T':
|
||||
LOGW("Deprecated option -T. Use --always-on-top instead.");
|
||||
// fall through
|
||||
case OPT_ALWAYS_ON_TOP:
|
||||
opts->always_on_top = true;
|
||||
break;
|
||||
case 'v':
|
||||
@@ -413,8 +432,11 @@ parse_args(struct args *args, int argc, char *argv[]) {
|
||||
case OPT_PUSH_TARGET:
|
||||
opts->push_target = optarg;
|
||||
break;
|
||||
case OPT_PREFER_TEXT:
|
||||
opts->prefer_text = true;
|
||||
case OPT_PREFER_TEXT_EVENTS:
|
||||
if (!parse_prefer_text_events(optarg,
|
||||
&opts->text_events_pref)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
|
||||
@@ -174,14 +174,9 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||
|
||||
void
|
||||
recorder_close(struct recorder *recorder) {
|
||||
if (recorder->header_written) {
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
recorder->failed = true;
|
||||
}
|
||||
} else {
|
||||
// the recorded file is empty
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
recorder->failed = true;
|
||||
}
|
||||
avio_close(recorder->ctx->pb);
|
||||
|
||||
@@ -42,7 +42,7 @@ static struct input_manager input_manager = {
|
||||
.controller = &controller,
|
||||
.video_buffer = &video_buffer,
|
||||
.screen = &screen,
|
||||
.prefer_text = false, // initialized later
|
||||
.text_events_pref = 0, // initialized later
|
||||
};
|
||||
|
||||
// init SDL and set appropriate hints
|
||||
@@ -144,7 +144,12 @@ handle_event(SDL_Event *event, bool control) {
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
screen_handle_window_event(&screen, &event->window);
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
screen_render(&screen);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_TEXTINPUT:
|
||||
if (!control) {
|
||||
@@ -213,7 +218,6 @@ event_loop(bool display, bool control) {
|
||||
case EVENT_RESULT_STOPPED_BY_USER:
|
||||
return true;
|
||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
||||
LOGW("Device disconnected");
|
||||
return false;
|
||||
case EVENT_RESULT_CONTINUE:
|
||||
break;
|
||||
@@ -411,7 +415,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
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);
|
||||
LOGD("quit...");
|
||||
|
||||
@@ -15,6 +15,7 @@ struct scrcpy_options {
|
||||
const char *window_title;
|
||||
const char *push_target;
|
||||
enum recorder_format record_format;
|
||||
enum text_events_pref text_events_pref;
|
||||
uint16_t port;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
@@ -25,7 +26,6 @@ struct scrcpy_options {
|
||||
bool display;
|
||||
bool turn_screen_off;
|
||||
bool render_expired_frames;
|
||||
bool prefer_text;
|
||||
};
|
||||
|
||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||
@@ -35,6 +35,7 @@ struct scrcpy_options {
|
||||
.window_title = NULL, \
|
||||
.push_target = NULL, \
|
||||
.record_format = RECORDER_FORMAT_AUTO, \
|
||||
.text_events_pref = PREFER_TEXT_EVENTS_ALWAYS, \
|
||||
.port = DEFAULT_LOCAL_PORT, \
|
||||
.max_size = DEFAULT_LOCAL_PORT, \
|
||||
.bit_rate = DEFAULT_BIT_RATE, \
|
||||
@@ -45,7 +46,6 @@ struct scrcpy_options {
|
||||
.display = true, \
|
||||
.turn_screen_off = false, \
|
||||
.render_expired_frames = false, \
|
||||
.prefer_text = false, \
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
106
app/src/screen.c
106
app/src/screen.c
@@ -30,28 +30,23 @@ get_window_size(SDL_Window *window) {
|
||||
// get the windowed window size
|
||||
static struct size
|
||||
get_windowed_window_size(const struct screen *screen) {
|
||||
if (screen->fullscreen || screen->maximized) {
|
||||
if (screen->fullscreen) {
|
||||
return screen->windowed_window_size;
|
||||
}
|
||||
return get_window_size(screen->window);
|
||||
}
|
||||
|
||||
// apply the windowed window size if fullscreen and maximized are disabled
|
||||
static void
|
||||
apply_windowed_size(struct screen *screen) {
|
||||
if (!screen->fullscreen && !screen->maximized) {
|
||||
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
||||
screen->windowed_window_size.height);
|
||||
}
|
||||
}
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static void
|
||||
set_window_size(struct screen *screen, struct size new_size) {
|
||||
// setting the window size during fullscreen is implementation defined,
|
||||
// so apply the resize only after fullscreen is disabled
|
||||
screen->windowed_window_size = new_size;
|
||||
apply_windowed_size(screen);
|
||||
if (screen->fullscreen) {
|
||||
// SDL_SetWindowSize will be called when fullscreen will be disabled
|
||||
screen->windowed_window_size = new_size;
|
||||
} else {
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
}
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
@@ -199,8 +194,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
return false;
|
||||
}
|
||||
|
||||
screen->windowed_window_size = window_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -294,6 +287,10 @@ screen_render(struct screen *screen) {
|
||||
|
||||
void
|
||||
screen_switch_fullscreen(struct screen *screen) {
|
||||
if (!screen->fullscreen) {
|
||||
// going to fullscreen, store the current windowed window size
|
||||
screen->windowed_window_size = get_window_size(screen->window);
|
||||
}
|
||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
@@ -301,7 +298,11 @@ screen_switch_fullscreen(struct screen *screen) {
|
||||
}
|
||||
|
||||
screen->fullscreen = !screen->fullscreen;
|
||||
apply_windowed_size(screen);
|
||||
if (!screen->fullscreen) {
|
||||
// fullscreen disabled, restore expected windowed window size
|
||||
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
|
||||
screen->windowed_window_size.height);
|
||||
}
|
||||
|
||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||
screen_render(screen);
|
||||
@@ -309,75 +310,20 @@ screen_switch_fullscreen(struct screen *screen) {
|
||||
|
||||
void
|
||||
screen_resize_to_fit(struct screen *screen) {
|
||||
if (screen->fullscreen) {
|
||||
return;
|
||||
if (!screen->fullscreen) {
|
||||
struct size optimal_size = get_optimal_window_size(screen,
|
||||
screen->frame_size);
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||
optimal_size.height);
|
||||
LOGD("Resized to optimal size");
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct size optimal_size =
|
||||
get_optimal_window_size(screen, screen->frame_size);
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||
LOGD("Resized to optimal size");
|
||||
}
|
||||
|
||||
void
|
||||
screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
if (screen->fullscreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
||||
screen->frame_size.height);
|
||||
LOGD("Resized to pixel-perfect");
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_window_event(struct screen *screen,
|
||||
const SDL_WindowEvent *event) {
|
||||
switch (event->event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
screen_render(screen);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
if (!screen->fullscreen && !screen->maximized) {
|
||||
// Backup the previous size: if we receive the MAXIMIZED event,
|
||||
// then the new size must be ignored (it's the maximized size).
|
||||
// We could not rely on the window flags due to race conditions
|
||||
// (they could be updated asynchronously, at least on X11).
|
||||
screen->windowed_window_size_backup =
|
||||
screen->windowed_window_size;
|
||||
|
||||
// Save the windowed size, so that it is available once the
|
||||
// window is maximized or fullscreen is enabled.
|
||||
screen->windowed_window_size = get_window_size(screen->window);
|
||||
}
|
||||
screen_render(screen);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
// The backup size must be non-nul.
|
||||
SDL_assert(screen->windowed_window_size_backup.width);
|
||||
SDL_assert(screen->windowed_window_size_backup.height);
|
||||
// Revert the last size, it was updated while screen was maximized.
|
||||
screen->windowed_window_size = screen->windowed_window_size_backup;
|
||||
#ifdef DEBUG
|
||||
// Reset the backup to invalid values to detect unexpected usage
|
||||
screen->windowed_window_size_backup.width = 0;
|
||||
screen->windowed_window_size_backup.height = 0;
|
||||
#endif
|
||||
screen->maximized = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
screen->maximized = false;
|
||||
apply_windowed_size(screen);
|
||||
break;
|
||||
if (!screen->fullscreen) {
|
||||
SDL_SetWindowSize(screen->window, screen->frame_size.width,
|
||||
screen->frame_size.height);
|
||||
LOGD("Resized to pixel-perfect");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,37 +15,28 @@ struct screen {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
struct size frame_size;
|
||||
// The window size the last time it was not maximized or fullscreen.
|
||||
//used only in fullscreen mode to know the windowed window size
|
||||
struct size windowed_window_size;
|
||||
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
|
||||
// able to revert the size to its non-maximized value.
|
||||
struct size windowed_window_size_backup;
|
||||
bool has_frame;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool no_window;
|
||||
};
|
||||
|
||||
#define SCREEN_INITIALIZER { \
|
||||
.window = NULL, \
|
||||
.renderer = NULL, \
|
||||
.texture = NULL, \
|
||||
.frame_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
#define SCREEN_INITIALIZER { \
|
||||
.window = NULL, \
|
||||
.renderer = NULL, \
|
||||
.texture = NULL, \
|
||||
.frame_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.windowed_window_size = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.windowed_window_size_backup = { \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.has_frame = false, \
|
||||
.fullscreen = false, \
|
||||
.maximized = false, \
|
||||
.no_window = false, \
|
||||
.width = 0, \
|
||||
.height = 0, \
|
||||
}, \
|
||||
.has_frame = false, \
|
||||
.fullscreen = false, \
|
||||
.no_window = false, \
|
||||
}
|
||||
|
||||
// initialize default values
|
||||
@@ -85,8 +76,4 @@ screen_resize_to_fit(struct screen *screen);
|
||||
void
|
||||
screen_resize_to_pixel_perfect(struct screen *screen);
|
||||
|
||||
// react to window events
|
||||
void
|
||||
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -131,7 +131,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
#endif
|
||||
"/", // unused
|
||||
"com.genymobile.scrcpy.Server",
|
||||
SCRCPY_VERSION,
|
||||
max_size_string,
|
||||
bit_rate_string,
|
||||
server->tunnel_forward ? "true" : "false",
|
||||
|
||||
@@ -54,9 +54,6 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
}
|
||||
|
||||
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||
Workarounds.prepareMainLooper();
|
||||
Workarounds.fillAppInfo();
|
||||
|
||||
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
||||
device.setRotationListener(this);
|
||||
boolean alive;
|
||||
|
||||
@@ -67,39 +67,29 @@ public final class Server {
|
||||
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
private static Options createOptions(String... args) {
|
||||
if (args.length < 1) {
|
||||
throw new IllegalArgumentException("Missing client version");
|
||||
}
|
||||
|
||||
String clientVersion = args[0];
|
||||
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
|
||||
throw new IllegalArgumentException("The server version (" + clientVersion + ") does not match the client "
|
||||
+ "(" + BuildConfig.VERSION_NAME + ")");
|
||||
}
|
||||
|
||||
if (args.length != 7) {
|
||||
throw new IllegalArgumentException("Expecting 7 parameters");
|
||||
if (args.length != 6) {
|
||||
throw new IllegalArgumentException("Expecting 6 parameters");
|
||||
}
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8
|
||||
int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8
|
||||
options.setMaxSize(maxSize);
|
||||
|
||||
int bitRate = Integer.parseInt(args[2]);
|
||||
int bitRate = Integer.parseInt(args[1]);
|
||||
options.setBitRate(bitRate);
|
||||
|
||||
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
||||
boolean tunnelForward = Boolean.parseBoolean(args[3]);
|
||||
boolean tunnelForward = Boolean.parseBoolean(args[2]);
|
||||
options.setTunnelForward(tunnelForward);
|
||||
|
||||
Rect crop = parseCrop(args[4]);
|
||||
Rect crop = parseCrop(args[3]);
|
||||
options.setCrop(crop);
|
||||
|
||||
boolean sendFrameMeta = Boolean.parseBoolean(args[5]);
|
||||
boolean sendFrameMeta = Boolean.parseBoolean(args[4]);
|
||||
options.setSendFrameMeta(sendFrameMeta);
|
||||
|
||||
boolean control = Boolean.parseBoolean(args[6]);
|
||||
boolean control = Boolean.parseBoolean(args[5]);
|
||||
options.setControl(control);
|
||||
|
||||
return options;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public final class Workarounds {
|
||||
private Workarounds() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void prepareMainLooper() {
|
||||
// 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>
|
||||
//
|
||||
// Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException:
|
||||
// "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue'
|
||||
// on a null object reference"
|
||||
// <https://github.com/Genymobile/scrcpy/issues/921>
|
||||
Looper.prepareMainLooper();
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
public static void fillAppInfo() {
|
||||
try {
|
||||
// ActivityThread activityThread = new ActivityThread();
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
||||
activityThreadConstructor.setAccessible(true);
|
||||
Object activityThread = activityThreadConstructor.newInstance();
|
||||
|
||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
||||
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
||||
sCurrentActivityThreadField.setAccessible(true);
|
||||
sCurrentActivityThreadField.set(null, activityThread);
|
||||
|
||||
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
||||
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
||||
Constructor<?> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
|
||||
appBindDataConstructor.setAccessible(true);
|
||||
Object appBindData = appBindDataConstructor.newInstance();
|
||||
|
||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.packageName = "com.genymobile.scrcpy";
|
||||
|
||||
// appBindData.appInfo = applicationInfo;
|
||||
Field appInfo = appBindDataClass.getDeclaredField("appInfo");
|
||||
appInfo.setAccessible(true);
|
||||
appInfo.set(appBindData, applicationInfo);
|
||||
|
||||
// activityThread.mBoundApplication = appBindData;
|
||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
mBoundApplicationField.setAccessible(true);
|
||||
mBoundApplicationField.set(activityThread, appBindData);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.w("Could not fill app info: " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user