Compare commits

...

13 Commits

Author SHA1 Message Date
Romain Vimont
c37d455fa2 wip 2021-05-02 18:27:13 +02:00
Romain Vimont
6e8df74c41 serial in server_params 2021-05-02 17:22:03 +02:00
Romain Vimont
52f5c6d4c1 server_params_copy 2021-05-02 17:22:03 +02:00
Romain Vimont
efb531943d reorder server server_params 2021-05-02 17:22:03 +02:00
Romain Vimont
4dcda82582 ARRAY_LEN 2021-05-02 17:22:03 +02:00
Romain Vimont
5369c4f754 Serialize clean-up configuration
This avoids to pass each option as individual parameter and parse them
manually (it's still "manual" in the Parcelable implementation).

Refs #824 <https://github.com/Genymobile/scrcpy/pull/824#issuecomment-780319422>

Reviewed-by: Yu-Chen Lin <npes87184@gmail.com>
2021-05-01 14:14:32 +02:00
Romain Vimont
233f8e6cc4 Rename keycode injection method
Make it explicit that it injects both "press" and "release" events.
2021-04-30 23:07:37 +02:00
Romain Vimont
9a7d351d67 Simplify non-static injectEvent() implementation
Just call the static version (having a displayId) from the non-static
version (using the displayId field).
2021-04-30 23:07:37 +02:00
Romain Vimont
d00ee640c0 Simplify Device.java
Remove useless intermediate method with a "mode" parameter (it's always
called with the same mode).

This also avoids the need for a specific injectEventOnDisplay() method,
since it does not conflict with another injectEvent() method anymore.
2021-04-30 23:07:29 +02:00
Romain Vimont
ae6ec7a194 Unref decoder AVFrame immediately
The frame can be unref immediately after it is pushed to the frame
sinks.

It was not really a memory leak because the frame was unref every time
by avcodec_receive_frame() (and freed on close), but a reference was
unnecessarily kept for too long.
2021-04-26 18:05:43 +02:00
Romain Vimont
84f17fdeab Fix v4l2 AVPacket memory leak on error
Unref v4l2 AVPacket even if writing failed.
2021-04-26 18:05:11 +02:00
Romain Vimont
1cde68a1fa Fix v4l2 AVFrame memory leak
Unref frame immediately once encoded.

Fixes #2279 <https://github.com/Genymobile/scrcpy/pull/2279>
2021-04-26 18:04:51 +02:00
Romain Vimont
45e7280148 Rename --v4l2_sink to --v4l2-sink
This was a rebase issue, the previous version in #2268 was correct.

Refs #2268 <https://github.com/Genymobile/scrcpy/pull/2268>
2021-04-26 17:59:35 +02:00
11 changed files with 272 additions and 105 deletions

View File

@@ -186,7 +186,7 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-v4l2_sink " /dev/videoN
.BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device.
It requires to lock the video orientation (see --lock-video-orientation).

View File

@@ -177,7 +177,7 @@ scrcpy_print_usage(const char *arg0) {
" It only shows physical touches (not clicks from scrcpy).\n"
"\n"
#ifdef HAVE_V4L2
" --v4l2_sink /dev/videoN\n"
" --v4l2-sink /dev/videoN\n"
" Output to v4l2loopback device.\n"
" It requires to lock the video orientation (see\n"
" --lock-video-orientation).\n"
@@ -726,7 +726,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'},
#ifdef HAVE_V4L2
{"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK},
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
#endif
{"verbosity", required_argument, NULL, 'V'},
{"version", no_argument, NULL, 'v'},
@@ -926,7 +926,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
#ifdef HAVE_V4L2
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)"
" or sink to v4l2loopback device (--v4l2_sink)");
" or sink to v4l2loopback device (--v4l2-sink)");
return false;
}

View File

@@ -111,6 +111,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
// A frame lost should not make the whole pipeline fail. The error, if
// any, is already logged.
(void) ok;
av_frame_unref(decoder->frame);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret);
return false;

View File

@@ -243,10 +243,6 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
bool
scrcpy(const struct scrcpy_options *options) {
if (!server_init(&server)) {
return false;
}
bool ret = false;
bool server_started = false;
@@ -263,6 +259,7 @@ scrcpy(const struct scrcpy_options *options) {
bool record = !!options->record_filename;
struct server_params params = {
.serial = options->serial,
.log_level = options->log_level,
.crop = options->crop,
.port_range = options->port_range,
@@ -279,7 +276,12 @@ scrcpy(const struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
};
if (!server_start(&server, options->serial, &params)) {
if (!server_init(&server, &params)) {
return false;
}
if (!server_start(&server)) {
goto end;
}
@@ -311,7 +313,7 @@ scrcpy(const struct scrcpy_options *options) {
fps_counter_initialized = true;
if (options->control) {
if (!file_handler_init(&file_handler, server.serial,
if (!file_handler_init(&file_handler, options->serial,
options->push_target)) {
goto end;
}

View File

@@ -128,9 +128,10 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
static bool
disable_tunnel(struct server *server) {
if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port);
return disable_tunnel_forward(server->params.serial,
server->local_port);
}
return disable_tunnel_reverse(server->serial);
return disable_tunnel_reverse(server->params.serial);
}
static socket_t
@@ -144,7 +145,7 @@ enable_tunnel_reverse_any_port(struct server *server,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
if (!enable_tunnel_reverse(server->params.serial, port)) {
// the command itself failed, it will fail on any port
return false;
}
@@ -163,7 +164,7 @@ enable_tunnel_reverse_any_port(struct server *server,
}
// failure, disable tunnel and try another port
if (!disable_tunnel_reverse(server->serial)) {
if (!disable_tunnel_reverse(server->params.serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@@ -191,7 +192,7 @@ enable_tunnel_forward_any_port(struct server *server,
server->tunnel_forward = true;
uint16_t port = port_range.first;
for (;;) {
if (enable_tunnel_forward(server->serial, port)) {
if (enable_tunnel_forward(server->params.serial, port)) {
// success
server->local_port = port;
return true;
@@ -306,7 +307,7 @@ execute_server(struct server *server, const struct server_params *params) {
// Port: 5005
// Then click on "Debug"
#endif
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
return adb_execute(server->params.serial, cmd, ARRAY_LEN(cmd));
}
static socket_t
@@ -352,21 +353,75 @@ close_socket(socket_t socket) {
}
}
static void
server_params_destroy(struct server_params *params) {
// The server stores a copy of the params provided by the user
free((char *) params->crop);
free((char *) params->codec_options);
free((char *) params->encoder_name);
}
static bool
server_params_copy(struct server_params *dst, const struct server_params *src) {
// params reference user-allocated memory, so we must copy them to handle
// them from a separate thread
*dst = *src;
dst->crop = NULL;
dst->codec_options = NULL;
dst->encoder_name = NULL;
if (src->crop) {
dst->crop = strdup(src->crop);
if (!dst->crop) {
goto error;
}
}
if (src->codec_options) {
dst->codec_options = strdup(src->codec_options);
if (!dst->codec_options) {
goto error;
}
}
if (src->encoder_name) {
dst->encoder_name = strdup(src->encoder_name);
if (!dst->encoder_name) {
goto error;
}
}
return true;
error:
server_params_destroy(dst);
return false;
};
bool
server_init(struct server *server) {
server->serial = NULL;
server_init(struct server *server, const struct server_params *params) {
if (!server_params_copy(&server->params, params)) {
LOGE("Could not copy server params");
return false;
}
server->process = PROCESS_NONE;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
server_params_destroy(&server->params);
return false;
}
@@ -406,31 +461,41 @@ run_wait_server(void *data) {
return 0;
}
bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
if (serial) {
server->serial = strdup(serial);
if (!server->serial) {
return false;
}
}
static int
run_server(void *data) {
struct server *server = data;
if (!push_server(serial)) {
goto error1;
const struct server_params *params = &server->params;
const struct server_callbacks *cbs = &server->cbs;
void *userdata = server->userdata;
if (!push_server(params->serial)) {
cbs->on_connection_failed(server);
goto end;
}
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
goto error1;
cbs->on_connection_failed(server);
goto end;
}
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
goto error2;
cbs->on_connection_failed(server);
goto end;
}
process_wait(server->process, false); // ignore exit code
end:
return 0;
}
bool
server_start(struct server *server) {
// If the server process dies before connecting to the server socket, then
// the client will be stuck forever on accept(). To avoid the problem, we
// must be able to wake up the accept() call when the server dies. To keep
@@ -442,14 +507,14 @@ server_start(struct server *server, const char *serial,
if (!ok) {
process_terminate(server->process);
process_wait(server->process, true); // ignore exit code
goto error2;
goto error;
}
server->tunnel_enabled = true;
return true;
error2:
error:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
@@ -459,8 +524,7 @@ error2:
close_socket(server->server_socket);
}
disable_tunnel(server);
error1:
free(server->serial);
return false;
}
@@ -558,4 +622,5 @@ server_destroy(struct server *server) {
free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
server_params_destroy(&server->params);
}

View File

@@ -13,6 +13,25 @@
#include "util/net.h"
#include "util/thread.h"
struct server_params {
enum sc_log_level log_level;
const char *serial;
const char *crop;
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
bool control;
uint32_t display_id;
bool show_touches;
bool stay_awake;
bool force_adb_forward;
bool power_off_on_close;
};
struct server {
char *serial;
process_t process;
@@ -29,34 +48,29 @@ struct server {
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
// The internal allocated strings are copies owned by the server
struct server_params params;
const struct server_callbacks *cbs;
void *userdata;
};
struct server_params {
enum sc_log_level log_level;
const char *crop;
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
int8_t lock_video_orientation;
bool control;
uint32_t display_id;
bool show_touches;
bool stay_awake;
bool force_adb_forward;
bool power_off_on_close;
struct server_callbacks {
void (*on_connection_failed)(struct server *server);
void (*on_connected)(struct server *server, const char *name,
struct size size, void *userdata);
void (*on_disconnected)(struct server *server, void *userdata);
};
// init default values
// init server fields
bool
server_init(struct server *server);
server_init(struct server *server, const struct server_params *params);
// push, enable tunnel et start the server
bool
server_start(struct server *server, const char *serial,
const struct server_params *params);
server_start(struct server *server, const struct server_callbacks *cbs,
void *userdata);
// block until the communication with the server is established
bool

View File

@@ -92,11 +92,11 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
// A packet was received
bool ok = write_packet(vs, packet);
av_packet_unref(packet);
if (!ok) {
LOGW("Could not send packet to v4l2 sink");
return false;
}
av_packet_unref(packet);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive v4l2 video packet: %d", ret);
return false;
@@ -125,6 +125,7 @@ run_v4l2_sink(void *data) {
video_buffer_consume(&vs->vb, vs->frame);
bool ok = encode_and_write_frame(vs, vs->frame);
av_frame_unref(vs->frame);
if (!ok) {
LOGE("Could not send frame to v4l2 sink");
break;

View File

@@ -3,6 +3,10 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
import java.io.File;
import java.io.IOException;
@@ -15,25 +19,123 @@ public final class CleanUp {
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
// A simple struct to be passed from the main process to the cleanup process
public static class Config implements Parcelable {
public static final Creator<Config> CREATOR = new Creator<Config>() {
@Override
public Config createFromParcel(Parcel in) {
return new Config(in);
}
@Override
public Config[] newArray(int size) {
return new Config[size];
}
};
private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
private static final int FLAG_POWER_OFF_SCREEN = 4;
private int displayId;
// Restore the value (between 0 and 7), -1 to not restore
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
private int restoreStayOn = -1;
private boolean disableShowTouches;
private boolean restoreNormalPowerMode;
private boolean powerOffScreen;
public Config() {
// Default constructor, the fields are initialized by CleanUp.configure()
}
protected Config(Parcel in) {
displayId = in.readInt();
restoreStayOn = in.readInt();
byte options = in.readByte();
disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(displayId);
dest.writeInt(restoreStayOn);
byte options = 0;
if (disableShowTouches) {
options |= FLAG_DISABLE_SHOW_TOUCHES;
}
if (restoreNormalPowerMode) {
options |= FLAG_RESTORE_NORMAL_POWER_MODE;
}
if (powerOffScreen) {
options |= FLAG_POWER_OFF_SCREEN;
}
dest.writeByte(options);
}
private boolean hasWork() {
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
}
@Override
public int describeContents() {
return 0;
}
byte[] serialize() {
Parcel parcel = Parcel.obtain();
writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.recycle();
return bytes;
}
static Config deserialize(byte[] bytes) {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
return CREATOR.createFromParcel(parcel);
}
static Config fromBase64(String base64) {
byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
return deserialize(bytes);
}
String toBase64() {
byte[] bytes = serialize();
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
}
private CleanUp() {
// not instantiable
}
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId)
public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
if (needProcess) {
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId);
Config config = new Config();
config.displayId = displayId;
config.disableShowTouches = disableShowTouches;
config.restoreStayOn = restoreStayOn;
config.restoreNormalPowerMode = restoreNormalPowerMode;
config.powerOffScreen = powerOffScreen;
if (config.hasWork()) {
startProcess(config);
} else {
// There is no additional clean up to do when scrcpy dies
unlinkSelf();
}
}
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen,
int displayId) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)};
private static void startProcess(Config config) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH);
@@ -60,31 +162,27 @@ public final class CleanUp {
Ln.i("Cleaning up");
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int displayId = Integer.parseInt(args[4]);
Config config = Config.fromBase64(args[0]);
if (disableShowTouches || restoreStayOn != -1) {
if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
if (disableShowTouches) {
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
}
if (restoreStayOn != -1) {
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(restoreStayOn));
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
}
}
}
if (Device.isScreenOn()) {
if (powerOffScreen) {
if (config.powerOffScreen) {
Ln.i("Power off screen");
Device.powerOffScreen(displayId);
} else if (restoreNormalPowerMode) {
Device.powerOffScreen(config.displayId);
} else if (config.restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}

View File

@@ -55,7 +55,7 @@ public class Controller {
public void control() throws IOException {
// on start, power on the device
if (!Device.isScreenOn()) {
device.injectKeycode(KeyEvent.KEYCODE_POWER);
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
// dirty hack
// After POWER is injected, the device is powered on asynchronously.
@@ -273,7 +273,7 @@ public class Controller {
if (keepPowerModeOff) {
schedulePowerModeOff();
}
return device.injectKeycode(KeyEvent.KEYCODE_POWER);
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
}
private boolean setClipboard(String text, boolean paste) {
@@ -284,7 +284,7 @@ public class Controller {
// On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE);
}
return ok;

View File

@@ -164,54 +164,39 @@ public final class Device {
return supportsInputEvents;
}
public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) {
public static boolean injectEvent(InputEvent inputEvent, int displayId) {
if (!supportsInputEvents(displayId)) {
return false;
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
return false;
}
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
}
public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
return injectEvent(inputEvent, mode, displayId);
}
public static boolean injectEventOnDisplay(InputEvent event, int displayId) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId);
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
return injectEvent(event, displayId);
}
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEventOnDisplay(event, displayId);
return injectEvent(event, displayId);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
return injectKeyEvent(action, keyCode, repeat, metaState, displayId);
}
public static boolean injectKeycode(int keyCode, int displayId) {
public static boolean pressReleaseKeycode(int keyCode, int displayId) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
}
public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
public boolean pressReleaseKeycode(int keyCode) {
return pressReleaseKeycode(keyCode, displayId);
}
public static boolean isScreenOn() {
@@ -287,7 +272,7 @@ public final class Device {
if (!isScreenOn()) {
return true;
}
return injectKeycode(KeyEvent.KEYCODE_POWER, displayId);
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId);
}
/**

View File

@@ -50,7 +50,7 @@ public final class Server {
}
}
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId());
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
boolean tunnelForward = options.isTunnelForward();