Compare commits

...

9 Commits

Author SHA1 Message Date
Yu-Chen Lin
f24d488ca2 Check client and server mismatch
Send client version as first parameter and check it at server start.

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-11-11 14:43:16 +01:00
Romain Vimont
6996cbf5d3 Log device disconnection
If scrcpy closes due to socket disconnection, log a warning.
2019-11-09 21:17:03 +01:00
olbb
e282100d0b Call Looper.prepareMainLooper() to avoid exception
Some devices internally create a Handler when creating an input Surface,
causing an exception:

> Surface: java.lang.RuntimeException: Can't create handler inside
> thread that has not called Looper.prepare()

As a workaround, call Looper.prepareMainLooper() beforehand.

Fixes:
 - <https://github.com/Genymobile/scrcpy/issues/240>
 - <https://github.com/Genymobile/scrcpy/issues/921>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-11-09 20:31:57 +01:00
Romain Vimont
b08a98324d Fix segfault on empty file recorded
Write the file trailer only if the file header have been written, to
avoid a segfault in libav.

Fixes <https://github.com/Genymobile/scrcpy/issues/918>.
2019-11-07 21:58:57 +01:00
Romain Vimont
c916af0984 Add --prefer-text option
Expose an option to configure how key/text events are forwarded to the
Android device.

Enabling the option avoids issues when combining multiple keys to enter
special characters, but breaks the expected behavior of alpha keys in
games (typically WASD).

Fixes <https://github.com/Genymobile/scrcpy/issues/650>
2019-11-07 19:01:35 +01:00
Romain Vimont
ff061b4f30 Deprecate short options for advanced features
The short options will be removed in the future (and may be reused for
other features).
2019-11-07 10:01:59 +01:00
Romain Vimont
157c60feb4 Fix indentation 2019-11-07 09:48:48 +01:00
Romain Vimont
2d90e1befd Fix include recorder.h 2019-11-06 22:22:46 +01:00
Romain Vimont
0e301ddf19 Factorize scrcpy options and command-line args
Do not duplicate all scrcpy options fields in the structure storing the
parsed command-line arguments.
2019-11-06 22:06:54 +01:00
14 changed files with 159 additions and 106 deletions

View File

@@ -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
``` ```

View File

@@ -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".

View File

@@ -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;
} }

View File

@@ -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);

View File

@@ -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) {
char c = event->text[0]; if (!im->prefer_text) {
if (isalpha(c) || c == ' ') { char c = event->text[0];
SDL_assert(event->text[1] == '\0'); if (isalpha(c) || c == ' ') {
// letters and space are handled as raw key event SDL_assert(event->text[1] == '\0');
return; // letters and space are handled as raw key event
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'");
} }

View File

@@ -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

View File

@@ -14,24 +14,9 @@
#include "recorder.h" #include "recorder.h"
struct args { struct args {
const char *serial; struct scrcpy_options opts;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
enum recorder_format record_format;
bool fullscreen;
bool no_control;
bool no_display;
bool help; bool help;
bool version; bool version;
bool show_touches;
uint16_t port;
uint16_t max_size;
uint32_t bit_rate;
bool always_on_top;
bool turn_screen_off;
bool render_expired_frames;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@@ -82,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"
@@ -312,50 +304,63 @@ 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},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
struct scrcpy_options *opts = &args->opts;
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options, while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options,
NULL)) != -1) { NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &opts->bit_rate)) {
return false; return false;
} }
break; break;
case 'c': case 'c':
args->crop = optarg; LOGW("Deprecated option -c. Use --crop instead.");
// fall through
case OPT_CROP:
opts->crop = optarg;
break; break;
case 'f': case 'f':
args->fullscreen = true; opts->fullscreen = true;
break; break;
case 'F': case 'F':
if (!parse_record_format(optarg, &args->record_format)) { LOGW("Deprecated option -F. Use --record-format instead.");
// fall through
case OPT_RECORD_FORMAT:
if (!parse_record_format(optarg, &opts->record_format)) {
return false; return false;
} }
break; break;
@@ -363,47 +368,53 @@ parse_args(struct args *args, int argc, char *argv[]) {
args->help = true; args->help = true;
break; break;
case 'm': case 'm':
if (!parse_max_size(optarg, &args->max_size)) { if (!parse_max_size(optarg, &opts->max_size)) {
return false; return false;
} }
break; break;
case 'n': case 'n':
args->no_control = true; opts->control = false;
break; break;
case 'N': case 'N':
args->no_display = true; opts->display = false;
break; break;
case 'p': case 'p':
if (!parse_port(optarg, &args->port)) { if (!parse_port(optarg, &opts->port)) {
return false; return false;
} }
break; break;
case 'r': case 'r':
args->record_filename = optarg; opts->record_filename = optarg;
break; break;
case 's': case 's':
args->serial = optarg; opts->serial = optarg;
break; break;
case 'S': case 'S':
args->turn_screen_off = true; opts->turn_screen_off = true;
break; break;
case 't': case 't':
args->show_touches = true; opts->show_touches = true;
break; break;
case 'T': case 'T':
args->always_on_top = true; LOGW("Deprecated option -T. Use --always-on-top instead.");
// fall through
case OPT_ALWAYS_ON_TOP:
opts->always_on_top = true;
break; break;
case 'v': case 'v':
args->version = true; args->version = true;
break; break;
case OPT_RENDER_EXPIRED_FRAMES: case OPT_RENDER_EXPIRED_FRAMES:
args->render_expired_frames = true; opts->render_expired_frames = true;
break; break;
case OPT_WINDOW_TITLE: case OPT_WINDOW_TITLE:
args->window_title = optarg; opts->window_title = optarg;
break; break;
case OPT_PUSH_TARGET: case OPT_PUSH_TARGET:
args->push_target = optarg; opts->push_target = optarg;
break;
case OPT_PREFER_TEXT:
opts->prefer_text = true;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
@@ -411,12 +422,12 @@ parse_args(struct args *args, int argc, char *argv[]) {
} }
} }
if (args->no_display && !args->record_filename) { if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)"); LOGE("-N/--no-display requires screen recording (-r/--record)");
return false; return false;
} }
if (args->no_display && args->fullscreen) { if (!opts->display && opts->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display"); LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false; return false;
} }
@@ -427,21 +438,21 @@ parse_args(struct args *args, int argc, char *argv[]) {
return false; return false;
} }
if (args->record_format && !args->record_filename) { if (opts->record_format && !opts->record_filename) {
LOGE("Record format specified without recording"); LOGE("Record format specified without recording");
return false; return false;
} }
if (args->record_filename && !args->record_format) { if (opts->record_filename && !opts->record_format) {
args->record_format = guess_record_format(args->record_filename); opts->record_format = guess_record_format(opts->record_filename);
if (!args->record_format) { if (!opts->record_format) {
LOGE("No format specified for \"%s\" (try with -F mkv)", LOGE("No format specified for \"%s\" (try with -F mkv)",
args->record_filename); opts->record_filename);
return false; return false;
} }
} }
if (args->no_control && args->turn_screen_off) { if (!opts->control && opts->turn_screen_off) {
LOGE("Could not request to turn screen off if control is disabled"); LOGE("Could not request to turn screen off if control is disabled");
return false; return false;
} }
@@ -458,24 +469,11 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL); setbuf(stderr, NULL);
#endif #endif
struct args args = { struct args args = {
.serial = NULL, .opts = SCRCPY_OPTIONS_DEFAULT,
.crop = NULL,
.record_filename = NULL,
.window_title = NULL,
.push_target = NULL,
.record_format = 0,
.help = false, .help = false,
.version = false, .version = false,
.show_touches = false,
.port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE,
.always_on_top = false,
.no_control = false,
.no_display = false,
.turn_screen_off = false,
.render_expired_frames = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
} }
@@ -504,25 +502,7 @@ main(int argc, char *argv[]) {
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif #endif
struct scrcpy_options options = { int res = scrcpy(&args.opts) ? 0 : 1;
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.record_filename = args.record_filename,
.window_title = args.window_title,
.push_target = args.push_target,
.record_format = args.record_format,
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
.fullscreen = args.fullscreen,
.always_on_top = args.always_on_top,
.control = !args.no_control,
.display = !args.no_display,
.turn_screen_off = args.turn_screen_off,
.render_expired_frames = args.render_expired_frames,
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure avformat_network_deinit(); // ignore failure

View File

@@ -174,9 +174,14 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
void void
recorder_close(struct recorder *recorder) { recorder_close(struct recorder *recorder) {
int ret = av_write_trailer(recorder->ctx); if (recorder->header_written) {
if (ret < 0) { int ret = av_write_trailer(recorder->ctx);
LOGE("Failed to write trailer to %s", recorder->filename); if (ret < 0) {
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

@@ -11,7 +11,8 @@
#include "queue.h" #include "queue.h"
enum recorder_format { enum recorder_format {
RECORDER_FORMAT_MP4 = 1, RECORDER_FORMAT_AUTO,
RECORDER_FORMAT_MP4,
RECORDER_FORMAT_MKV, RECORDER_FORMAT_MKV,
}; };

View File

@@ -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
@@ -217,6 +218,7 @@ event_loop(bool display, bool control) {
case EVENT_RESULT_STOPPED_BY_USER: case EVENT_RESULT_STOPPED_BY_USER:
return true; return true;
case EVENT_RESULT_STOPPED_BY_EOS: case EVENT_RESULT_STOPPED_BY_EOS:
LOGW("Device disconnected");
return false; return false;
case EVENT_RESULT_CONTINUE: case EVENT_RESULT_CONTINUE:
break; break;
@@ -414,6 +416,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...");

View File

@@ -3,9 +3,10 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <recorder.h>
#include "config.h" #include "config.h"
#include "input_manager.h"
#include "recorder.h"
struct scrcpy_options { struct scrcpy_options {
const char *serial; const char *serial;
@@ -24,8 +25,29 @@ 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 { \
.serial = NULL, \
.crop = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.record_format = RECORDER_FORMAT_AUTO, \
.port = DEFAULT_LOCAL_PORT, \
.max_size = DEFAULT_LOCAL_PORT, \
.bit_rate = DEFAULT_BIT_RATE, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
.control = true, \
.display = true, \
.turn_screen_off = false, \
.render_expired_frames = false, \
.prefer_text = false, \
}
bool bool
scrcpy(const struct scrcpy_options *options); scrcpy(const struct scrcpy_options *options);

View File

@@ -131,6 +131,7 @@ execute_server(struct server *server, const struct server_params *params) {
#endif #endif
"/", // unused "/", // unused
"com.genymobile.scrcpy.Server", "com.genymobile.scrcpy.Server",
SCRCPY_VERSION,
max_size_string, max_size_string,
bit_rate_string, bit_rate_string,
server->tunnel_forward ? "true" : "false", server->tunnel_forward ? "true" : "false",

View File

@@ -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,16 @@ 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>
//
// 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();
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;

View File

@@ -67,29 +67,39 @@ public final class Server {
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
if (args.length != 6) { if (args.length < 1) {
throw new IllegalArgumentException("Expecting 6 parameters"); 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");
} }
Options options = new Options(); Options options = new Options();
int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8 int maxSize = Integer.parseInt(args[1]) & ~7; // multiple of 8
options.setMaxSize(maxSize); options.setMaxSize(maxSize);
int bitRate = Integer.parseInt(args[1]); int bitRate = Integer.parseInt(args[2]);
options.setBitRate(bitRate); options.setBitRate(bitRate);
// use "adb forward" instead of "adb tunnel"? (so the server must listen) // use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[2]); boolean tunnelForward = Boolean.parseBoolean(args[3]);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
Rect crop = parseCrop(args[3]); Rect crop = parseCrop(args[4]);
options.setCrop(crop); options.setCrop(crop);
boolean sendFrameMeta = Boolean.parseBoolean(args[4]); boolean sendFrameMeta = Boolean.parseBoolean(args[5]);
options.setSendFrameMeta(sendFrameMeta); options.setSendFrameMeta(sendFrameMeta);
boolean control = Boolean.parseBoolean(args[5]); boolean control = Boolean.parseBoolean(args[6]);
options.setControl(control); options.setControl(control);
return options; return options;