Compare commits
2 Commits
server_deb
...
keep_exist
Author | SHA1 | Date | |
---|---|---|---|
6bd32b8eed | |||
d73de2da3f |
@ -77,7 +77,6 @@ _scrcpy() {
|
|||||||
--rotation=
|
--rotation=
|
||||||
-s --serial=
|
-s --serial=
|
||||||
-S --turn-screen-off
|
-S --turn-screen-off
|
||||||
--screen-off-timeout=
|
|
||||||
--shortcut-mod=
|
--shortcut-mod=
|
||||||
--start-app=
|
--start-app=
|
||||||
-t --show-touches
|
-t --show-touches
|
||||||
|
@ -80,7 +80,6 @@ arguments=(
|
|||||||
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
||||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||||
'--screen-off-timeout=[Set the screen off timeout in seconds]'
|
|
||||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||||
'--start-app=[Start an Android app]'
|
'--start-app=[Start an Android app]'
|
||||||
{-t,--show-touches}'[Show physical touches]'
|
{-t,--show-touches}'[Show physical touches]'
|
||||||
|
@ -167,6 +167,9 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
|||||||
# run a server debugger and wait for a client to be attached
|
# run a server debugger and wait for a client to be attached
|
||||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||||
|
|
||||||
|
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
||||||
|
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
|
||||||
|
|
||||||
# enable V4L2 support (linux only)
|
# enable V4L2 support (linux only)
|
||||||
conf.set('HAVE_V4L2', v4l2_support)
|
conf.set('HAVE_V4L2', v4l2_support)
|
||||||
|
|
||||||
|
@ -671,10 +671,6 @@ Pause or re-pause display
|
|||||||
.B MOD+Shift+z
|
.B MOD+Shift+z
|
||||||
Unpause display
|
Unpause display
|
||||||
|
|
||||||
.TP
|
|
||||||
.B MOD+Shift+r
|
|
||||||
Reset video capture/encoding
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B MOD+g
|
.B MOD+g
|
||||||
Resize window to 1:1 (pixel\-perfect)
|
Resize window to 1:1 (pixel\-perfect)
|
||||||
|
@ -288,7 +288,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
|||||||
|
|
||||||
// Enable compensation when the difference exceeds +/- 4ms.
|
// Enable compensation when the difference exceeds +/- 4ms.
|
||||||
// Disable compensation when the difference is lower than +/- 1ms.
|
// Disable compensation when the difference is lower than +/- 1ms.
|
||||||
int threshold = ar->compensation_active
|
int threshold = ar->compensation != 0
|
||||||
? ar->sample_rate / 1000 /* 1ms */
|
? ar->sample_rate / 1000 /* 1ms */
|
||||||
: ar->sample_rate * 4 / 1000; /* 4ms */
|
: ar->sample_rate * 4 / 1000; /* 4ms */
|
||||||
|
|
||||||
@ -309,12 +309,14 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
|||||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||||
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
||||||
|
|
||||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
if (diff != ar->compensation) {
|
||||||
if (ret < 0) {
|
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||||
LOGW("Resampling compensation failed: %d", ret);
|
if (ret < 0) {
|
||||||
// not fatal
|
LOGW("Resampling compensation failed: %d", ret);
|
||||||
} else {
|
// not fatal
|
||||||
ar->compensation_active = diff != 0;
|
} else {
|
||||||
|
ar->compensation = diff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +392,7 @@ sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
|||||||
atomic_init(&ar->played, false);
|
atomic_init(&ar->played, false);
|
||||||
atomic_init(&ar->received, false);
|
atomic_init(&ar->received, false);
|
||||||
atomic_init(&ar->underflow, 0);
|
atomic_init(&ar->underflow, 0);
|
||||||
ar->compensation_active = false;
|
ar->compensation = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ struct sc_audio_regulator {
|
|||||||
// Number of silence samples inserted since the last received packet
|
// Number of silence samples inserted since the last received packet
|
||||||
atomic_uint_least32_t underflow;
|
atomic_uint_least32_t underflow;
|
||||||
|
|
||||||
// Non-zero compensation applied (only used by the receiver thread)
|
// Current applied compensation value (only used by the receiver thread)
|
||||||
bool compensation_active;
|
int compensation;
|
||||||
|
|
||||||
// Set to true the first time a sample is received
|
// Set to true the first time a sample is received
|
||||||
atomic_bool received;
|
atomic_bool received;
|
||||||
|
@ -106,7 +106,6 @@ enum {
|
|||||||
OPT_NEW_DISPLAY,
|
OPT_NEW_DISPLAY,
|
||||||
OPT_LIST_APPS,
|
OPT_LIST_APPS,
|
||||||
OPT_START_APP,
|
OPT_START_APP,
|
||||||
OPT_SCREEN_OFF_TIMEOUT,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -794,13 +793,6 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "turn-screen-off",
|
.longopt = "turn-screen-off",
|
||||||
.text = "Turn the device screen off immediately.",
|
.text = "Turn the device screen off immediately.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_SCREEN_OFF_TIMEOUT,
|
|
||||||
.longopt = "screen-off-timeout",
|
|
||||||
.argdesc = "seconds",
|
|
||||||
.text = "Set the screen off timeout while scrcpy is running (restore "
|
|
||||||
"the initial value on exit).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_SHORTCUT_MOD,
|
.longopt_id = OPT_SHORTCUT_MOD,
|
||||||
.longopt = "shortcut-mod",
|
.longopt = "shortcut-mod",
|
||||||
@ -1030,10 +1022,6 @@ static const struct sc_shortcut shortcuts[] = {
|
|||||||
.shortcuts = { "MOD+Shift+z" },
|
.shortcuts = { "MOD+Shift+z" },
|
||||||
.text = "Unpause display",
|
.text = "Unpause display",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortcuts = { "MOD+Shift+r" },
|
|
||||||
.text = "Reset video capture/encoding",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortcuts = { "MOD+g" },
|
.shortcuts = { "MOD+g" },
|
||||||
.text = "Resize window to 1:1 (pixel-perfect)",
|
.text = "Resize window to 1:1 (pixel-perfect)",
|
||||||
@ -2163,20 +2151,6 @@ parse_time_limit(const char *s, sc_tick *tick) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_screen_off_timeout(const char *s, sc_tick *tick) {
|
|
||||||
long value;
|
|
||||||
// value in seconds, but must fit in 31 bits in milliseconds
|
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF / 1000,
|
|
||||||
"screen off timeout");
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*tick = SC_TICK_FROM_SEC(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
||||||
if (!s || !strcmp(s, "true")) {
|
if (!s || !strcmp(s, "true")) {
|
||||||
@ -2752,12 +2726,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_START_APP:
|
case OPT_START_APP:
|
||||||
opts->start_app = optarg;
|
opts->start_app = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_SCREEN_OFF_TIMEOUT:
|
|
||||||
if (!parse_screen_off_timeout(optarg,
|
|
||||||
&opts->screen_off_timeout)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
@ -181,7 +181,6 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
|
||||||
// no additional data
|
// no additional data
|
||||||
return 1;
|
return 1;
|
||||||
default:
|
default:
|
||||||
@ -305,9 +304,6 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
case SC_CONTROL_MSG_TYPE_START_APP:
|
case SC_CONTROL_MSG_TYPE_START_APP:
|
||||||
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
|
||||||
LOG_CMSG("reset video");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||||
break;
|
break;
|
||||||
|
@ -42,7 +42,6 @@ enum sc_control_msg_type {
|
|||||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
SC_CONTROL_MSG_TYPE_START_APP,
|
SC_CONTROL_MSG_TYPE_START_APP,
|
||||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_copy_key {
|
enum sc_copy_key {
|
||||||
|
@ -284,18 +284,6 @@ open_hard_keyboard_settings(struct sc_input_manager *im) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
reset_video(struct sc_input_manager *im) {
|
|
||||||
assert(im->controller);
|
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO;
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
|
||||||
LOGW("Could not request reset video");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
apply_orientation_transform(struct sc_input_manager *im,
|
apply_orientation_transform(struct sc_input_manager *im,
|
||||||
enum sc_orientation transform) {
|
enum sc_orientation transform) {
|
||||||
@ -533,12 +521,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && !repeat && down && !paused) {
|
if (control && !shift && !repeat && down && !paused) {
|
||||||
if (shift) {
|
rotate_device(im);
|
||||||
reset_video(im);
|
|
||||||
} else {
|
|
||||||
rotate_device(im);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_k:
|
case SDLK_k:
|
||||||
|
@ -62,7 +62,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.audio_buffer = -1, // depends on the audio format,
|
.audio_buffer = -1, // depends on the audio format,
|
||||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||||
.time_limit = 0,
|
.time_limit = 0,
|
||||||
.screen_off_timeout = -1,
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
.v4l2_device = NULL,
|
.v4l2_device = NULL,
|
||||||
.v4l2_buffer = 0,
|
.v4l2_buffer = 0,
|
||||||
|
@ -265,7 +265,6 @@ struct scrcpy_options {
|
|||||||
sc_tick audio_buffer;
|
sc_tick audio_buffer;
|
||||||
sc_tick audio_output_buffer;
|
sc_tick audio_output_buffer;
|
||||||
sc_tick time_limit;
|
sc_tick time_limit;
|
||||||
sc_tick screen_off_timeout;
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
const char *v4l2_device;
|
const char *v4l2_device;
|
||||||
sc_tick v4l2_buffer;
|
sc_tick v4l2_buffer;
|
||||||
|
@ -428,7 +428,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.video_bit_rate = options->video_bit_rate,
|
.video_bit_rate = options->video_bit_rate,
|
||||||
.audio_bit_rate = options->audio_bit_rate,
|
.audio_bit_rate = options->audio_bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.screen_off_timeout = options->screen_off_timeout,
|
|
||||||
.lock_video_orientation = options->lock_video_orientation,
|
.lock_video_orientation = options->lock_video_orientation,
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
|
@ -183,27 +183,6 @@ validate_string(const char *s) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
|
||||||
get_device_sdk_version(struct sc_server *server) {
|
|
||||||
struct sc_intr *intr = &server->intr;
|
|
||||||
|
|
||||||
char *sdk_version =
|
|
||||||
sc_adb_getprop(intr, server->serial, "ro.build.version.sdk",
|
|
||||||
SC_ADB_SILENT);
|
|
||||||
if (!sdk_version) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long value;
|
|
||||||
bool ok = sc_str_parse_integer(sdk_version, &value);
|
|
||||||
free(sdk_version);
|
|
||||||
if (!ok || value < 0 || value > 0xFFFF) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sc_pid
|
static sc_pid
|
||||||
execute_server(struct sc_server *server,
|
execute_server(struct sc_server *server,
|
||||||
const struct sc_server_params *params) {
|
const struct sc_server_params *params) {
|
||||||
@ -222,26 +201,18 @@ execute_server(struct sc_server *server,
|
|||||||
cmd[count++] = "app_process";
|
cmd[count++] = "app_process";
|
||||||
|
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
uint16_t sdk_version = get_device_sdk_version(server);
|
|
||||||
if (!sdk_version) {
|
|
||||||
LOGE("Could not determine SDK version");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define SERVER_DEBUGGER_PORT "5005"
|
# define SERVER_DEBUGGER_PORT "5005"
|
||||||
const char *dbg;
|
cmd[count++] =
|
||||||
if (sdk_version < 28) {
|
# ifdef SERVER_DEBUGGER_METHOD_NEW
|
||||||
// Android < 9
|
/* Android 9 and above */
|
||||||
dbg = "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
|
||||||
SERVER_DEBUGGER_PORT;
|
"server=y,address="
|
||||||
} else {
|
# else
|
||||||
// Android >= 9
|
/* Android 8 and below */
|
||||||
dbg = "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,"
|
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||||
"suspend=y,server=y,address=" SERVER_DEBUGGER_PORT;
|
# endif
|
||||||
}
|
SERVER_DEBUGGER_PORT;
|
||||||
cmd[count++] = dbg;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cmd[count++] = "/"; // unused
|
cmd[count++] = "/"; // unused
|
||||||
cmd[count++] = "com.genymobile.scrcpy.Server";
|
cmd[count++] = "com.genymobile.scrcpy.Server";
|
||||||
cmd[count++] = SCRCPY_VERSION;
|
cmd[count++] = SCRCPY_VERSION;
|
||||||
@ -349,11 +320,6 @@ execute_server(struct sc_server *server,
|
|||||||
if (params->stay_awake) {
|
if (params->stay_awake) {
|
||||||
ADD_PARAM("stay_awake=true");
|
ADD_PARAM("stay_awake=true");
|
||||||
}
|
}
|
||||||
if (params->screen_off_timeout != -1) {
|
|
||||||
assert(params->screen_off_timeout >= 0);
|
|
||||||
uint64_t ms = SC_TICK_TO_MS(params->screen_off_timeout);
|
|
||||||
ADD_PARAM("screen_off_timeout=%" PRIu64, ms);
|
|
||||||
}
|
|
||||||
if (params->video_codec_options) {
|
if (params->video_codec_options) {
|
||||||
VALIDATE_STRING(params->video_codec_options);
|
VALIDATE_STRING(params->video_codec_options);
|
||||||
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||||
|
@ -45,7 +45,6 @@ struct sc_server_params {
|
|||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
const char *max_fps; // float to be parsed by the server
|
const char *max_fps; // float to be parsed by the server
|
||||||
sc_tick screen_off_timeout;
|
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
bool control;
|
bool control;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
|
@ -407,21 +407,6 @@ static void test_serialize_open_hard_keyboard(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_reset_video(void) {
|
|
||||||
struct sc_control_msg msg = {
|
|
||||||
.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
|
||||||
assert(size == 1);
|
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
|
||||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@ -444,6 +429,5 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_uhid_input();
|
test_serialize_uhid_input();
|
||||||
test_serialize_uhid_destroy();
|
test_serialize_uhid_destroy();
|
||||||
test_serialize_open_hard_keyboard();
|
test_serialize_open_hard_keyboard();
|
||||||
test_serialize_reset_video();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -71,31 +71,6 @@ adb shell cmd display power-on 0
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Screen off timeout
|
|
||||||
|
|
||||||
The Android screen automatically turns off after some delay.
|
|
||||||
|
|
||||||
To change this delay while scrcpy is running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
|
||||||
```
|
|
||||||
|
|
||||||
The initial value is restored on exit.
|
|
||||||
|
|
||||||
It is possible to change this setting manually:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# get the current screen_off_timeout value
|
|
||||||
adb shell settings get system screen_off_timeout
|
|
||||||
# set a new value (in milliseconds)
|
|
||||||
adb shell settings put system screen_off_timeout 30000
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
|
||||||
argument is in seconds.
|
|
||||||
|
|
||||||
|
|
||||||
## Show touches
|
## Show touches
|
||||||
|
|
||||||
For presentations, it may be useful to show physical touches (on the physical
|
For presentations, it may be useful to show physical touches (on the physical
|
||||||
|
@ -30,7 +30,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
||||||
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
||||||
| Reset video capture/encoding | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>r</kbd>
|
|
||||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
|
@ -3,5 +3,6 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
|
|||||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||||
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
||||||
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
||||||
|
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
|
||||||
option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported')
|
option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported')
|
||||||
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported')
|
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported')
|
||||||
|
@ -50,11 +50,6 @@ cd "$SERVER_DIR/src/main/aidl"
|
|||||||
android/content/IOnPrimaryClipChangedListener.aidl
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
||||||
|
|
||||||
# Fake sources to expose hidden Android types to the project
|
|
||||||
FAKE_SRC=( \
|
|
||||||
android/content/*java \
|
|
||||||
)
|
|
||||||
|
|
||||||
SRC=( \
|
SRC=( \
|
||||||
com/genymobile/scrcpy/*.java \
|
com/genymobile/scrcpy/*.java \
|
||||||
com/genymobile/scrcpy/audio/*.java \
|
com/genymobile/scrcpy/audio/*.java \
|
||||||
@ -73,11 +68,10 @@ done
|
|||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
cd ../java
|
cd ../java
|
||||||
javac -encoding UTF-8 -bootclasspath "$ANDROID_JAR" \
|
javac -bootclasspath "$ANDROID_JAR" \
|
||||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||||
-d "$CLASSES_DIR" \
|
-d "$CLASSES_DIR" \
|
||||||
-source 1.8 -target 1.8 \
|
-source 1.8 -target 1.8 \
|
||||||
${FAKE_SRC[@]} \
|
|
||||||
${SRC[@]}
|
${SRC[@]}
|
||||||
|
|
||||||
echo "Dexing..."
|
echo "Dexing..."
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package android.content;
|
|
||||||
|
|
||||||
public interface IContentProvider {
|
|
||||||
// android.content.IContentProvider is hidden, this is a fake one to expose the type to the project
|
|
||||||
}
|
|
@ -5,8 +5,6 @@ import com.genymobile.scrcpy.util.Ln;
|
|||||||
import com.genymobile.scrcpy.util.Settings;
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
import com.genymobile.scrcpy.util.SettingsException;
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
|
|
||||||
import android.os.BatteryManager;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -18,132 +16,59 @@ import java.io.OutputStream;
|
|||||||
*/
|
*/
|
||||||
public final class CleanUp {
|
public final class CleanUp {
|
||||||
|
|
||||||
// Dynamic options
|
private static final int MSG_TYPE_MASK = 0b11;
|
||||||
private static final int PENDING_CHANGE_DISPLAY_POWER = 1 << 0;
|
private static final int MSG_TYPE_RESTORE_STAY_ON = 0;
|
||||||
private int pendingChanges;
|
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1;
|
||||||
private boolean pendingRestoreDisplayPower;
|
private static final int MSG_TYPE_RESTORE_DISPLAY_POWER = 2;
|
||||||
|
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;
|
||||||
|
|
||||||
private Thread thread;
|
private static final int MSG_PARAM_SHIFT = 2;
|
||||||
|
|
||||||
private CleanUp(Options options) {
|
private final OutputStream out;
|
||||||
thread = new Thread(() -> runCleanUp(options), "cleanup");
|
|
||||||
thread.start();
|
public CleanUp(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CleanUp start(Options options) {
|
public static CleanUp configure(int displayId) throws IOException {
|
||||||
return new CleanUp(options);
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)};
|
||||||
}
|
|
||||||
|
|
||||||
public void interrupt() {
|
|
||||||
thread.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void join() throws InterruptedException {
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runCleanUp(Options options) {
|
|
||||||
boolean disableShowTouches = false;
|
|
||||||
if (options.getShowTouches()) {
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
|
||||||
// If "show touches" was disabled, it must be disabled back on clean up
|
|
||||||
disableShowTouches = !"1".equals(oldValue);
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"show_touches\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int restoreStayOn = -1;
|
|
||||||
if (options.getStayAwake()) {
|
|
||||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
|
||||||
try {
|
|
||||||
int currentStayOn = Integer.parseInt(oldValue);
|
|
||||||
// Restore only if the current value is different
|
|
||||||
if (currentStayOn != stayOn) {
|
|
||||||
restoreStayOn = currentStayOn;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int restoreScreenOffTimeout = -1;
|
|
||||||
int screenOffTimeout = options.getScreenOffTimeout();
|
|
||||||
if (screenOffTimeout != -1) {
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(screenOffTimeout));
|
|
||||||
try {
|
|
||||||
int currentScreenOffTimeout = Integer.parseInt(oldValue);
|
|
||||||
// Restore only if the current value is different
|
|
||||||
if (currentScreenOffTimeout != screenOffTimeout) {
|
|
||||||
restoreScreenOffTimeout = currentScreenOffTimeout;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"screen_off_timeout\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean powerOffScreen = options.getPowerOffScreenOnClose();
|
|
||||||
int displayId = options.getDisplayId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
} catch (IOException e) {
|
|
||||||
Ln.e("Clean up I/O exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
String[] cmd = {
|
|
||||||
"app_process",
|
|
||||||
"/",
|
|
||||||
CleanUp.class.getName(),
|
|
||||||
String.valueOf(displayId),
|
|
||||||
String.valueOf(restoreStayOn),
|
|
||||||
String.valueOf(disableShowTouches),
|
|
||||||
String.valueOf(powerOffScreen),
|
|
||||||
String.valueOf(restoreScreenOffTimeout),
|
|
||||||
};
|
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
||||||
Process process = builder.start();
|
Process process = builder.start();
|
||||||
OutputStream out = process.getOutputStream();
|
return new CleanUp(process.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
private boolean sendMessage(int type, int param) {
|
||||||
int localPendingChanges;
|
assert (type & ~MSG_TYPE_MASK) == 0;
|
||||||
boolean localPendingRestoreDisplayPower;
|
int msg = type | param << MSG_PARAM_SHIFT;
|
||||||
synchronized (this) {
|
try {
|
||||||
while (pendingChanges == 0) {
|
out.write(msg);
|
||||||
wait();
|
out.flush();
|
||||||
}
|
return true;
|
||||||
localPendingChanges = pendingChanges;
|
} catch (IOException e) {
|
||||||
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
|
Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e);
|
||||||
pendingChanges = 0;
|
return false;
|
||||||
}
|
|
||||||
if ((localPendingChanges & PENDING_CHANGE_DISPLAY_POWER) != 0) {
|
|
||||||
out.write(localPendingRestoreDisplayPower ? 1 : 0);
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setRestoreDisplayPower(boolean restoreDisplayPower) {
|
public boolean setRestoreStayOn(int restoreValue) {
|
||||||
pendingRestoreDisplayPower = restoreDisplayPower;
|
// Restore the value (between 0 and 7), -1 to not restore
|
||||||
pendingChanges |= PENDING_CHANGE_DISPLAY_POWER;
|
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
|
||||||
notify();
|
assert restoreValue >= -1 && restoreValue <= 7;
|
||||||
|
return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setDisableShowTouches(boolean disableOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setRestoreDisplayPower(boolean restoreOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_RESTORE_DISPLAY_POWER, restoreOnExit ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unlinkSelf() {
|
public static void unlinkSelf() {
|
||||||
@ -158,21 +83,35 @@ public final class CleanUp {
|
|||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
|
|
||||||
int displayId = Integer.parseInt(args[0]);
|
int displayId = Integer.parseInt(args[0]);
|
||||||
int restoreStayOn = Integer.parseInt(args[1]);
|
|
||||||
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
|
|
||||||
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
|
|
||||||
int restoreScreenOffTimeout = Integer.parseInt(args[4]);
|
|
||||||
|
|
||||||
// Dynamic option
|
int restoreStayOn = -1;
|
||||||
|
boolean disableShowTouches = false;
|
||||||
boolean restoreDisplayPower = false;
|
boolean restoreDisplayPower = false;
|
||||||
|
boolean powerOffScreen = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for the server to die
|
// Wait for the server to die
|
||||||
int msg;
|
int msg;
|
||||||
while ((msg = System.in.read()) != -1) {
|
while ((msg = System.in.read()) != -1) {
|
||||||
// Only restore display power
|
int type = msg & MSG_TYPE_MASK;
|
||||||
assert msg == 0 || msg == 1;
|
int param = msg >> MSG_PARAM_SHIFT;
|
||||||
restoreDisplayPower = msg != 0;
|
switch (type) {
|
||||||
|
case MSG_TYPE_RESTORE_STAY_ON:
|
||||||
|
restoreStayOn = param > 7 ? -1 : param;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_DISABLE_SHOW_TOUCHES:
|
||||||
|
disableShowTouches = param != 0;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_RESTORE_DISPLAY_POWER:
|
||||||
|
restoreDisplayPower = param != 0;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_POWER_OFF_SCREEN:
|
||||||
|
powerOffScreen = param != 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unexpected msg type: " + type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Expected when the server is dead
|
// Expected when the server is dead
|
||||||
@ -198,16 +137,7 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoreScreenOffTimeout != -1) {
|
if (Device.isScreenOn() && displayId != Device.DISPLAY_ID_NONE) {
|
||||||
Ln.i("Restoring \"screen off timeout\"");
|
|
||||||
try {
|
|
||||||
Settings.putValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(restoreScreenOffTimeout));
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not restore \"screen_off_timeout\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) {
|
|
||||||
if (powerOffScreen) {
|
if (powerOffScreen) {
|
||||||
Ln.i("Power off screen");
|
Ln.i("Power off screen");
|
||||||
Device.powerOffScreen(displayId);
|
Device.powerOffScreen(displayId);
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.AttributionSource;
|
import android.content.AttributionSource;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.content.IContentProvider;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
public final class FakeContext extends ContextWrapper {
|
public final class FakeContext extends ContextWrapper {
|
||||||
@ -22,38 +17,6 @@ public final class FakeContext extends ContextWrapper {
|
|||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ContentResolver contentResolver = new ContentResolver(this) {
|
|
||||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
protected IContentProvider acquireProvider(Context c, String name) {
|
|
||||||
return ServiceManager.getActivityManager().getContentProviderExternal(name, new Binder());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public boolean releaseProvider(IContentProvider icp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
protected IContentProvider acquireUnstableProvider(Context c, String name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public boolean releaseUnstableProvider(IContentProvider icp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public void unstableProviderDied(IContentProvider icp) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private FakeContext() {
|
private FakeContext() {
|
||||||
super(Workarounds.getSystemContext());
|
super(Workarounds.getSystemContext());
|
||||||
}
|
}
|
||||||
@ -86,9 +49,4 @@ public final class FakeContext extends ContextWrapper {
|
|||||||
public Context getApplicationContext() {
|
public Context getApplicationContext() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentResolver getContentResolver() {
|
|
||||||
return contentResolver;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ public class Options {
|
|||||||
private boolean cameraHighSpeed;
|
private boolean cameraHighSpeed;
|
||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private int screenOffTimeout = -1;
|
|
||||||
private List<CodecOption> videoCodecOptions;
|
private List<CodecOption> videoCodecOptions;
|
||||||
private List<CodecOption> audioCodecOptions;
|
private List<CodecOption> audioCodecOptions;
|
||||||
|
|
||||||
@ -175,10 +174,6 @@ public class Options {
|
|||||||
return stayAwake;
|
return stayAwake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getScreenOffTimeout() {
|
|
||||||
return screenOffTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CodecOption> getVideoCodecOptions() {
|
public List<CodecOption> getVideoCodecOptions() {
|
||||||
return videoCodecOptions;
|
return videoCodecOptions;
|
||||||
}
|
}
|
||||||
@ -368,12 +363,6 @@ public class Options {
|
|||||||
case "stay_awake":
|
case "stay_awake":
|
||||||
options.stayAwake = Boolean.parseBoolean(value);
|
options.stayAwake = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
case "screen_off_timeout":
|
|
||||||
options.screenOffTimeout = Integer.parseInt(value);
|
|
||||||
if (options.screenOffTimeout < -1) {
|
|
||||||
throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "video_codec_options":
|
case "video_codec_options":
|
||||||
options.videoCodecOptions = CodecOption.parse(value);
|
options.videoCodecOptions = CodecOption.parse(value);
|
||||||
break;
|
break;
|
||||||
@ -479,11 +468,6 @@ public class Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.newDisplay != null) {
|
|
||||||
assert options.displayId == 0 : "Must not set both displayId and newDisplay";
|
|
||||||
options.displayId = Device.DISPLAY_ID_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import com.genymobile.scrcpy.device.NewDisplay;
|
|||||||
import com.genymobile.scrcpy.device.Streamer;
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
import com.genymobile.scrcpy.video.CameraCapture;
|
import com.genymobile.scrcpy.video.CameraCapture;
|
||||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||||
@ -23,6 +25,7 @@ import com.genymobile.scrcpy.video.SurfaceCapture;
|
|||||||
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||||
import com.genymobile.scrcpy.video.VideoSource;
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -73,6 +76,51 @@ public final class Server {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void initAndCleanUp(Options options, CleanUp cleanUp) {
|
||||||
|
// This method is called from its own thread, so it may only configure cleanup actions which are NOT dynamic (i.e. they are configured once
|
||||||
|
// and for all, they cannot be changed from another thread)
|
||||||
|
|
||||||
|
if (options.getShowTouches()) {
|
||||||
|
try {
|
||||||
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||||
|
// If "show touches" was disabled, it must be disabled back on clean up
|
||||||
|
if (!"1".equals(oldValue)) {
|
||||||
|
if (!cleanUp.setDisableShowTouches(true)) {
|
||||||
|
Ln.e("Could not disable show touch on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SettingsException e) {
|
||||||
|
Ln.e("Could not change \"show_touches\"", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getStayAwake()) {
|
||||||
|
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||||
|
try {
|
||||||
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||||
|
try {
|
||||||
|
int restoreStayOn = Integer.parseInt(oldValue);
|
||||||
|
if (restoreStayOn != stayOn) {
|
||||||
|
// Restore only if the current value is different
|
||||||
|
if (!cleanUp.setRestoreStayOn(restoreStayOn)) {
|
||||||
|
Ln.e("Could not restore stay on on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
} catch (SettingsException e) {
|
||||||
|
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getPowerOffScreenOnClose()) {
|
||||||
|
if (!cleanUp.setPowerOffScreen(true)) {
|
||||||
|
Ln.e("Could not power off screen on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||||
Ln.e("Camera mirroring is not supported before Android 12");
|
Ln.e("Camera mirroring is not supported before Android 12");
|
||||||
@ -102,9 +150,14 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CleanUp cleanUp = null;
|
CleanUp cleanUp = null;
|
||||||
|
Thread initThread = null;
|
||||||
|
|
||||||
|
NewDisplay newDisplay = options.getNewDisplay();
|
||||||
|
int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE;
|
||||||
|
|
||||||
if (options.getCleanup()) {
|
if (options.getCleanup()) {
|
||||||
cleanUp = CleanUp.start(options);
|
cleanUp = CleanUp.configure(displayId);
|
||||||
|
initThread = startInitThread(options, cleanUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int scid = options.getScid();
|
int scid = options.getScid();
|
||||||
@ -128,7 +181,7 @@ public final class Server {
|
|||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
ControlChannel controlChannel = connection.getControlChannel();
|
ControlChannel controlChannel = connection.getControlChannel();
|
||||||
controller = new Controller(controlChannel, cleanUp, options);
|
controller = new Controller(displayId, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||||
asyncProcessors.add(controller);
|
asyncProcessors.add(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +200,8 @@ public final class Server {
|
|||||||
if (audioCodec == AudioCodec.RAW) {
|
if (audioCodec == AudioCodec.RAW) {
|
||||||
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
||||||
} else {
|
} else {
|
||||||
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options);
|
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
|
||||||
|
options.getAudioEncoder());
|
||||||
}
|
}
|
||||||
asyncProcessors.add(audioRecorder);
|
asyncProcessors.add(audioRecorder);
|
||||||
}
|
}
|
||||||
@ -157,22 +211,20 @@ public final class Server {
|
|||||||
options.getSendFrameMeta());
|
options.getSendFrameMeta());
|
||||||
SurfaceCapture surfaceCapture;
|
SurfaceCapture surfaceCapture;
|
||||||
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
||||||
NewDisplay newDisplay = options.getNewDisplay();
|
|
||||||
if (newDisplay != null) {
|
if (newDisplay != null) {
|
||||||
surfaceCapture = new NewDisplayCapture(controller, options);
|
surfaceCapture = new NewDisplayCapture(controller, newDisplay, options.getMaxSize());
|
||||||
} else {
|
} else {
|
||||||
assert options.getDisplayId() != Device.DISPLAY_ID_NONE;
|
assert displayId != Device.DISPLAY_ID_NONE;
|
||||||
surfaceCapture = new ScreenCapture(controller, options);
|
surfaceCapture = new ScreenCapture(controller, displayId, options.getMaxSize(), options.getCrop(),
|
||||||
|
options.getLockVideoOrientation());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
surfaceCapture = new CameraCapture(options);
|
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||||
|
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
||||||
}
|
}
|
||||||
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options);
|
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
|
||||||
|
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
|
||||||
asyncProcessors.add(surfaceEncoder);
|
asyncProcessors.add(surfaceEncoder);
|
||||||
|
|
||||||
if (controller != null) {
|
|
||||||
controller.setSurfaceCapture(surfaceCapture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Completion completion = new Completion(asyncProcessors.size());
|
Completion completion = new Completion(asyncProcessors.size());
|
||||||
@ -184,8 +236,8 @@ public final class Server {
|
|||||||
|
|
||||||
completion.await();
|
completion.await();
|
||||||
} finally {
|
} finally {
|
||||||
if (cleanUp != null) {
|
if (initThread != null) {
|
||||||
cleanUp.interrupt();
|
initThread.interrupt();
|
||||||
}
|
}
|
||||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||||
asyncProcessor.stop();
|
asyncProcessor.stop();
|
||||||
@ -194,8 +246,8 @@ public final class Server {
|
|||||||
connection.shutdown();
|
connection.shutdown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cleanUp != null) {
|
if (initThread != null) {
|
||||||
cleanUp.join();
|
initThread.join();
|
||||||
}
|
}
|
||||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||||
asyncProcessor.join();
|
asyncProcessor.join();
|
||||||
@ -208,6 +260,12 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Thread startInitThread(final Options options, final CleanUp cleanUp) {
|
||||||
|
Thread thread = new Thread(() -> initAndCleanUp(options, cleanUp), "init-cleanup");
|
||||||
|
thread.start();
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
int status = 0;
|
int status = 0;
|
||||||
try {
|
try {
|
||||||
|
@ -2,7 +2,6 @@ package com.genymobile.scrcpy.audio;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.AsyncProcessor;
|
import com.genymobile.scrcpy.AsyncProcessor;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Streamer;
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.util.Codec;
|
import com.genymobile.scrcpy.util.Codec;
|
||||||
@ -68,12 +67,12 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
|
|
||||||
private boolean ended;
|
private boolean ended;
|
||||||
|
|
||||||
public AudioEncoder(AudioCapture capture, Streamer streamer, Options options) {
|
public AudioEncoder(AudioCapture capture, Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
|
||||||
this.capture = capture;
|
this.capture = capture;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
this.bitRate = options.getAudioBitRate();
|
this.bitRate = bitRate;
|
||||||
this.codecOptions = options.getAudioCodecOptions();
|
this.codecOptions = codecOptions;
|
||||||
this.encoderName = options.getAudioEncoder();
|
this.encoderName = encoderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
|
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
|
||||||
|
@ -24,7 +24,6 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_UHID_DESTROY = 14;
|
public static final int TYPE_UHID_DESTROY = 14;
|
||||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
||||||
public static final int TYPE_START_APP = 16;
|
public static final int TYPE_START_APP = 16;
|
||||||
public static final int TYPE_RESET_VIDEO = 17;
|
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = 0;
|
public static final long SEQUENCE_INVALID = 0;
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
case ControlMessage.TYPE_RESET_VIDEO:
|
|
||||||
return ControlMessage.createEmpty(type);
|
return ControlMessage.createEmpty(type);
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
return parseUhidCreate();
|
return parseUhidCreate();
|
||||||
|
@ -3,14 +3,12 @@ package com.genymobile.scrcpy.control;
|
|||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.AsyncProcessor;
|
import com.genymobile.scrcpy.AsyncProcessor;
|
||||||
import com.genymobile.scrcpy.CleanUp;
|
import com.genymobile.scrcpy.CleanUp;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.Device;
|
import com.genymobile.scrcpy.device.Device;
|
||||||
import com.genymobile.scrcpy.device.DeviceApp;
|
import com.genymobile.scrcpy.device.DeviceApp;
|
||||||
import com.genymobile.scrcpy.device.Point;
|
import com.genymobile.scrcpy.device.Point;
|
||||||
import com.genymobile.scrcpy.device.Position;
|
import com.genymobile.scrcpy.device.Position;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
|
||||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
@ -95,15 +93,12 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
|
|
||||||
private boolean keepDisplayPowerOff;
|
private boolean keepDisplayPowerOff;
|
||||||
|
|
||||||
// Used for resetting video encoding on RESET_VIDEO message
|
public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
|
||||||
private SurfaceCapture surfaceCapture;
|
this.displayId = displayId;
|
||||||
|
|
||||||
public Controller(ControlChannel controlChannel, CleanUp cleanUp, Options options) {
|
|
||||||
this.displayId = options.getDisplayId();
|
|
||||||
this.controlChannel = controlChannel;
|
this.controlChannel = controlChannel;
|
||||||
this.cleanUp = cleanUp;
|
this.cleanUp = cleanUp;
|
||||||
this.clipboardAutosync = options.getClipboardAutosync();
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
this.powerOn = options.getPowerOn();
|
this.powerOn = powerOn;
|
||||||
initPointers();
|
initPointers();
|
||||||
sender = new DeviceMessageSender(controlChannel);
|
sender = new DeviceMessageSender(controlChannel);
|
||||||
|
|
||||||
@ -148,10 +143,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
|
||||||
this.surfaceCapture = surfaceCapture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UhidManager getUhidManager() {
|
private UhidManager getUhidManager() {
|
||||||
if (uhidManager == null) {
|
if (uhidManager == null) {
|
||||||
uhidManager = new UhidManager(sender);
|
uhidManager = new UhidManager(sender);
|
||||||
@ -175,7 +166,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
|
|
||||||
private void control() throws IOException {
|
private void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (powerOn && displayId == 0 && !Device.isScreenOn(displayId)) {
|
if (powerOn && displayId == 0 && !Device.isScreenOn()) {
|
||||||
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
@ -302,9 +293,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
case ControlMessage.TYPE_START_APP:
|
case ControlMessage.TYPE_START_APP:
|
||||||
startAppAsync(msg.getText());
|
startAppAsync(msg.getText());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_RESET_VIDEO:
|
|
||||||
resetVideo();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
@ -502,7 +490,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn(int action) {
|
private boolean pressBackOrTurnScreenOn(int action) {
|
||||||
if (displayId == Device.DISPLAY_ID_NONE || Device.isScreenOn(displayId)) {
|
if (Device.isScreenOn()) {
|
||||||
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,11 +680,4 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetVideo() {
|
|
||||||
if (surfaceCapture != null) {
|
|
||||||
Ln.i("Video capture reset");
|
|
||||||
surfaceCapture.requestInvalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -82,9 +82,8 @@ public final class Device {
|
|||||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenOn(int displayId) {
|
public static boolean isScreenOn() {
|
||||||
assert displayId != DISPLAY_ID_NONE;
|
return ServiceManager.getPowerManager().isScreenOn();
|
||||||
return ServiceManager.getPowerManager().isScreenOn(displayId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expandNotificationPanel() {
|
public static void expandNotificationPanel() {
|
||||||
@ -182,7 +181,7 @@ public final class Device {
|
|||||||
public static boolean powerOffScreen(int displayId) {
|
public static boolean powerOffScreen(int displayId) {
|
||||||
assert displayId != DISPLAY_ID_NONE;
|
assert displayId != DISPLAY_ID_NONE;
|
||||||
|
|
||||||
if (!isScreenOn(displayId)) {
|
if (!isScreenOn()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||||
|
@ -236,7 +236,7 @@ public final class LogUtils {
|
|||||||
} else {
|
} else {
|
||||||
builder.append("\n ").append(String.format("%" + column + "s", " "));
|
builder.append("\n ").append(String.format("%" + column + "s", " "));
|
||||||
}
|
}
|
||||||
builder.append(" ").append(app.getPackageName());
|
builder.append(" [").append(app.getPackageName()).append(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package com.genymobile.scrcpy.video;
|
package com.genymobile.scrcpy.video;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.HandlerExecutor;
|
import com.genymobile.scrcpy.util.HandlerExecutor;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@ -59,18 +56,19 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||||
|
|
||||||
public CameraCapture(Options options) {
|
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps,
|
||||||
this.explicitCameraId = options.getCameraId();
|
boolean highSpeed) {
|
||||||
this.cameraFacing = options.getCameraFacing();
|
this.explicitCameraId = explicitCameraId;
|
||||||
this.explicitSize = options.getCameraSize();
|
this.cameraFacing = cameraFacing;
|
||||||
this.maxSize = options.getMaxSize();
|
this.explicitSize = explicitSize;
|
||||||
this.aspectRatio = options.getCameraAspectRatio();
|
this.maxSize = maxSize;
|
||||||
this.fps = options.getCameraFps();
|
this.aspectRatio = aspectRatio;
|
||||||
this.highSpeed = options.getCameraHighSpeed();
|
this.fps = fps;
|
||||||
|
this.highSpeed = highSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() throws ConfigurationException, IOException {
|
public void init() throws IOException {
|
||||||
cameraThread = new HandlerThread("camera");
|
cameraThread = new HandlerThread("camera");
|
||||||
cameraThread.start();
|
cameraThread.start();
|
||||||
cameraHandler = new Handler(cameraThread.getLooper());
|
cameraHandler = new Handler(cameraThread.getLooper());
|
||||||
@ -79,7 +77,12 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
try {
|
try {
|
||||||
cameraId = selectCamera(explicitCameraId, cameraFacing);
|
cameraId = selectCamera(explicitCameraId, cameraFacing);
|
||||||
if (cameraId == null) {
|
if (cameraId == null) {
|
||||||
throw new ConfigurationException("No matching camera found");
|
throw new IOException("No matching camera found");
|
||||||
|
}
|
||||||
|
|
||||||
|
size = selectSize(cameraId, explicitSize, maxSize, aspectRatio, highSpeed);
|
||||||
|
if (size == null) {
|
||||||
|
throw new IOException("Could not select camera size");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ln.i("Using camera '" + cameraId + "'");
|
Ln.i("Using camera '" + cameraId + "'");
|
||||||
@ -89,30 +92,14 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException {
|
||||||
public void prepare() throws IOException {
|
|
||||||
try {
|
|
||||||
size = selectSize(cameraId, explicitSize, maxSize, aspectRatio, highSpeed);
|
|
||||||
if (size == null) {
|
|
||||||
throw new IOException("Could not select camera size");
|
|
||||||
}
|
|
||||||
} catch (CameraAccessException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException, ConfigurationException {
|
|
||||||
CameraManager cameraManager = ServiceManager.getCameraManager();
|
|
||||||
|
|
||||||
String[] cameraIds = cameraManager.getCameraIdList();
|
|
||||||
if (explicitCameraId != null) {
|
if (explicitCameraId != null) {
|
||||||
if (!Arrays.asList(cameraIds).contains(explicitCameraId)) {
|
|
||||||
Ln.e("Camera with id " + explicitCameraId + " not found\n" + LogUtils.buildCameraListMessage(false));
|
|
||||||
throw new ConfigurationException("Camera id not found");
|
|
||||||
}
|
|
||||||
return explicitCameraId;
|
return explicitCameraId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraManager cameraManager = ServiceManager.getCameraManager();
|
||||||
|
|
||||||
|
String[] cameraIds = cameraManager.getCameraIdList();
|
||||||
if (cameraFacing == null) {
|
if (cameraFacing == null) {
|
||||||
// Use the first one
|
// Use the first one
|
||||||
return cameraIds.length > 0 ? cameraIds[0] : null;
|
return cameraIds.length > 0 ? cameraIds[0] : null;
|
||||||
@ -245,7 +232,13 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
return true;
|
try {
|
||||||
|
size = selectSize(cameraId, null, maxSize, aspectRatio, highSpeed);
|
||||||
|
return size != null;
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
|
Ln.w("Could not select camera size", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@ -263,7 +256,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
public void onDisconnected(CameraDevice camera) {
|
public void onDisconnected(CameraDevice camera) {
|
||||||
Ln.w("Camera disconnected");
|
Ln.w("Camera disconnected");
|
||||||
disconnected.set(true);
|
disconnected.set(true);
|
||||||
invalidate();
|
requestReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -362,9 +355,4 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return disconnected.get();
|
return disconnected.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestInvalidate() {
|
|
||||||
// do nothing (the user could not request a reset anyway for now, since there is no controller for camera mirroring)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package com.genymobile.scrcpy.video;
|
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class CaptureReset implements SurfaceCapture.CaptureListener {
|
|
||||||
|
|
||||||
private final AtomicBoolean reset = new AtomicBoolean();
|
|
||||||
|
|
||||||
// Current instance of MediaCodec to "interrupt" on reset
|
|
||||||
private MediaCodec runningMediaCodec;
|
|
||||||
|
|
||||||
public boolean consumeReset() {
|
|
||||||
return reset.getAndSet(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void reset() {
|
|
||||||
reset.set(true);
|
|
||||||
if (runningMediaCodec != null) {
|
|
||||||
runningMediaCodec.signalEndOfInputStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setRunningMediaCodec(MediaCodec runningMediaCodec) {
|
|
||||||
this.runningMediaCodec = runningMediaCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInvalidated() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package com.genymobile.scrcpy.video;
|
package com.genymobile.scrcpy.video;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.control.PositionMapper;
|
import com.genymobile.scrcpy.control.PositionMapper;
|
||||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||||
import com.genymobile.scrcpy.device.NewDisplay;
|
import com.genymobile.scrcpy.device.NewDisplay;
|
||||||
@ -15,13 +14,9 @@ import android.hardware.display.VirtualDisplay;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NewDisplayCapture extends SurfaceCapture {
|
public class NewDisplayCapture extends SurfaceCapture {
|
||||||
|
|
||||||
// Internal fields copied from android.hardware.display.DisplayManager
|
// Internal fields copied from android.hardware.display.DisplayManager
|
||||||
private static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
|
|
||||||
private static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
|
|
||||||
private static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;
|
private static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;
|
||||||
private static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
|
private static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
|
||||||
private static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
|
private static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
|
||||||
@ -44,15 +39,14 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
private Size size;
|
private Size size;
|
||||||
private int dpi;
|
private int dpi;
|
||||||
|
|
||||||
public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) {
|
public NewDisplayCapture(VirtualDisplayListener vdListener, NewDisplay newDisplay, int maxSize) {
|
||||||
this.vdListener = vdListener;
|
this.vdListener = vdListener;
|
||||||
this.newDisplay = options.getNewDisplay();
|
this.newDisplay = newDisplay;
|
||||||
assert newDisplay != null;
|
this.maxSize = maxSize;
|
||||||
this.maxSize = options.getMaxSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
public void init() {
|
||||||
size = newDisplay.getSize();
|
size = newDisplay.getSize();
|
||||||
dpi = newDisplay.getDpi();
|
dpi = newDisplay.getDpi();
|
||||||
if (size == null || dpi == 0) {
|
if (size == null || dpi == 0) {
|
||||||
@ -78,11 +72,17 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startNew(Surface surface) {
|
@Override
|
||||||
|
public void start(Surface surface) {
|
||||||
|
if (virtualDisplay != null) {
|
||||||
|
virtualDisplay.release();
|
||||||
|
virtualDisplay = null;
|
||||||
|
}
|
||||||
|
|
||||||
int virtualDisplayId;
|
int virtualDisplayId;
|
||||||
try {
|
try {
|
||||||
int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC
|
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
|
||||||
| VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
||||||
| VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
|
| VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
|
||||||
| VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
|
| VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
|
||||||
| VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
|
| VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
|
||||||
@ -93,8 +93,8 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
|
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
|
||||||
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
|
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
|
||||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtualDisplay = ServiceManager.getDisplayManager()
|
virtualDisplay = ServiceManager.getDisplayManager()
|
||||||
@ -107,21 +107,13 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vdListener != null) {
|
if (vdListener != null) {
|
||||||
|
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||||
Rect contentRect = new Rect(0, 0, size.getWidth(), size.getHeight());
|
Rect contentRect = new Rect(0, 0, size.getWidth(), size.getHeight());
|
||||||
PositionMapper positionMapper = new PositionMapper(size, contentRect, 0);
|
PositionMapper positionMapper = new PositionMapper(size, contentRect, 0);
|
||||||
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Surface surface) throws IOException {
|
|
||||||
if (virtualDisplay == null) {
|
|
||||||
startNew(surface);
|
|
||||||
} else {
|
|
||||||
virtualDisplay.setSurface(surface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
if (virtualDisplay != null) {
|
if (virtualDisplay != null) {
|
||||||
@ -151,9 +143,4 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
int num = size.getMax();
|
int num = size.getMax();
|
||||||
return initialDpi * num / den;
|
return initialDpi * num / den;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestInvalidate() {
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package com.genymobile.scrcpy.video;
|
package com.genymobile.scrcpy.video;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.control.PositionMapper;
|
import com.genymobile.scrcpy.control.PositionMapper;
|
||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Device;
|
|
||||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
@ -50,13 +48,12 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
private IRotationWatcher rotationWatcher;
|
private IRotationWatcher rotationWatcher;
|
||||||
private IDisplayFoldListener displayFoldListener;
|
private IDisplayFoldListener displayFoldListener;
|
||||||
|
|
||||||
public ScreenCapture(VirtualDisplayListener vdListener, Options options) {
|
public ScreenCapture(VirtualDisplayListener vdListener, int displayId, int maxSize, Rect crop, int lockVideoOrientation) {
|
||||||
this.vdListener = vdListener;
|
this.vdListener = vdListener;
|
||||||
this.displayId = options.getDisplayId();
|
this.displayId = displayId;
|
||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
this.maxSize = maxSize;
|
||||||
this.maxSize = options.getMaxSize();
|
this.crop = crop;
|
||||||
this.crop = options.getCrop();
|
this.lockVideoOrientation = lockVideoOrientation;
|
||||||
this.lockVideoOrientation = options.getLockVideoOrientation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -88,7 +85,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
Ln.v("ScreenCapture: requestReset(): " + getSessionDisplaySize() + " -> (unknown)");
|
Ln.v("ScreenCapture: requestReset(): " + getSessionDisplaySize() + " -> (unknown)");
|
||||||
}
|
}
|
||||||
setSessionDisplaySize(null);
|
setSessionDisplaySize(null);
|
||||||
invalidate();
|
requestReset();
|
||||||
} else {
|
} else {
|
||||||
Size size = di.getSize();
|
Size size = di.getSize();
|
||||||
|
|
||||||
@ -105,7 +102,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
||||||
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
||||||
setSessionDisplaySize(size);
|
setSessionDisplaySize(size);
|
||||||
invalidate();
|
requestReset();
|
||||||
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||||
Ln.v("ScreenCapture: Size not changed (" + size + "): do not requestReset()");
|
Ln.v("ScreenCapture: Size not changed (" + size + "): do not requestReset()");
|
||||||
}
|
}
|
||||||
@ -132,21 +129,19 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Surface surface) {
|
public void start(Surface surface) {
|
||||||
if (display != null) {
|
|
||||||
SurfaceControl.destroyDisplay(display);
|
|
||||||
display = null;
|
|
||||||
}
|
|
||||||
if (virtualDisplay != null) {
|
|
||||||
virtualDisplay.release();
|
|
||||||
virtualDisplay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int virtualDisplayId;
|
int virtualDisplayId;
|
||||||
PositionMapper positionMapper;
|
PositionMapper positionMapper;
|
||||||
try {
|
try {
|
||||||
Size videoSize = screenInfo.getVideoSize();
|
Size videoSize = screenInfo.getVideoSize();
|
||||||
virtualDisplay = ServiceManager.getDisplayManager()
|
if (virtualDisplay == null) {
|
||||||
.createVirtualDisplay("scrcpy", videoSize.getWidth(), videoSize.getHeight(), displayId, surface);
|
virtualDisplay = ServiceManager.getDisplayManager()
|
||||||
|
.createVirtualDisplay("scrcpy", videoSize.getWidth(), videoSize.getHeight(), displayId, surface);
|
||||||
|
} else {
|
||||||
|
// density doesn't matter since this virtual display is only used for mirroring
|
||||||
|
virtualDisplay.setSurface(surface);
|
||||||
|
virtualDisplay.resize(videoSize.getWidth(), videoSize.getHeight(), 1);
|
||||||
|
|
||||||
|
}
|
||||||
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||||
Rect contentRect = new Rect(0, 0, videoSize.getWidth(), videoSize.getHeight());
|
Rect contentRect = new Rect(0, 0, videoSize.getWidth(), videoSize.getHeight());
|
||||||
// The position are relative to the virtual display, not the original display
|
// The position are relative to the virtual display, not the original display
|
||||||
@ -154,7 +149,9 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
Ln.d("Display: using DisplayManager API");
|
Ln.d("Display: using DisplayManager API");
|
||||||
} catch (Exception displayManagerException) {
|
} catch (Exception displayManagerException) {
|
||||||
try {
|
try {
|
||||||
display = createDisplay();
|
if (display == null) {
|
||||||
|
display = createDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
Rect contentRect = screenInfo.getContentRect();
|
Rect contentRect = screenInfo.getContentRect();
|
||||||
|
|
||||||
@ -249,7 +246,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||||
Ln.v("ScreenCapture: onRotationChanged(" + rotation + ")");
|
Ln.v("ScreenCapture: onRotationChanged(" + rotation + ")");
|
||||||
}
|
}
|
||||||
invalidate();
|
requestReset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
||||||
@ -275,7 +272,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
// Ignore events related to other display ids
|
// Ignore events related to other display ids
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
invalidate();
|
requestReset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener);
|
ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener);
|
||||||
@ -294,9 +291,4 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestInvalidate() {
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,37 +6,36 @@ import com.genymobile.scrcpy.device.Size;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A video source which can be rendered on a Surface for encoding.
|
* A video source which can be rendered on a Surface for encoding.
|
||||||
*/
|
*/
|
||||||
public abstract class SurfaceCapture {
|
public abstract class SurfaceCapture {
|
||||||
|
|
||||||
public interface CaptureListener {
|
private final AtomicBoolean resetCapture = new AtomicBoolean();
|
||||||
void onInvalidated();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CaptureListener listener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify the listener that the capture has been invalidated (for example, because its size changed).
|
* Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on
|
||||||
|
* device rotation for example).
|
||||||
*/
|
*/
|
||||||
protected void invalidate() {
|
protected void requestReset() {
|
||||||
listener.onInvalidated();
|
resetCapture.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume the reset request (intended to be called by the encoder).
|
||||||
|
*
|
||||||
|
* @return {@code true} if a reset request was pending, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean consumeReset() {
|
||||||
|
return resetCapture.getAndSet(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once before the first capture starts.
|
* Called once before the first capture starts.
|
||||||
*/
|
*/
|
||||||
public final void init(CaptureListener listener) throws ConfigurationException, IOException {
|
public abstract void init() throws ConfigurationException, IOException;
|
||||||
this.listener = listener;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once before the first capture starts.
|
|
||||||
*/
|
|
||||||
protected abstract void init() throws ConfigurationException, IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the last capture ends (if and only if {@link #init()} has been called).
|
* Called after the last capture ends (if and only if {@link #init()} has been called).
|
||||||
@ -46,7 +45,7 @@ public abstract class SurfaceCapture {
|
|||||||
/**
|
/**
|
||||||
* Called once before each capture starts, before {@link #getSize()}.
|
* Called once before each capture starts, before {@link #getSize()}.
|
||||||
*/
|
*/
|
||||||
public void prepare() throws ConfigurationException, IOException {
|
public void prepare() throws ConfigurationException {
|
||||||
// empty by default
|
// empty by default
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,11 +78,4 @@ public abstract class SurfaceCapture {
|
|||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually request to invalidate (typically a user request).
|
|
||||||
* <p>
|
|
||||||
* The capture implementation is free to ignore the request and do nothing.
|
|
||||||
*/
|
|
||||||
public abstract void requestInvalidate();
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package com.genymobile.scrcpy.video;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.AsyncProcessor;
|
import com.genymobile.scrcpy.AsyncProcessor;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.device.Streamer;
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
@ -50,16 +49,15 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
private Thread thread;
|
private Thread thread;
|
||||||
private final AtomicBoolean stopped = new AtomicBoolean();
|
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||||
|
|
||||||
private final CaptureReset reset = new CaptureReset();
|
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List<CodecOption> codecOptions,
|
||||||
|
String encoderName, boolean downsizeOnError) {
|
||||||
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, Options options) {
|
|
||||||
this.capture = capture;
|
this.capture = capture;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
this.videoBitRate = options.getVideoBitRate();
|
this.videoBitRate = videoBitRate;
|
||||||
this.maxFps = options.getMaxFps();
|
this.maxFps = maxFps;
|
||||||
this.codecOptions = options.getVideoCodecOptions();
|
this.codecOptions = codecOptions;
|
||||||
this.encoderName = options.getVideoEncoder();
|
this.encoderName = encoderName;
|
||||||
this.downsizeOnError = options.getDownsizeOnError();
|
this.downsizeOnError = downsizeOnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamCapture() throws IOException, ConfigurationException {
|
private void streamCapture() throws IOException, ConfigurationException {
|
||||||
@ -67,14 +65,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||||
|
|
||||||
capture.init(reset);
|
capture.init();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean alive;
|
boolean alive;
|
||||||
boolean headerWritten = false;
|
boolean headerWritten = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
capture.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||||
capture.prepare();
|
capture.prepare();
|
||||||
Size size = capture.getSize();
|
Size size = capture.getSize();
|
||||||
if (!headerWritten) {
|
if (!headerWritten) {
|
||||||
@ -94,21 +92,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
|
|
||||||
mediaCodec.start();
|
mediaCodec.start();
|
||||||
|
|
||||||
// Set the MediaCodec instance to "interrupt" (by signaling an EOS) on reset
|
alive = encode(mediaCodec, streamer);
|
||||||
reset.setRunningMediaCodec(mediaCodec);
|
|
||||||
|
|
||||||
if (stopped.get()) {
|
|
||||||
alive = false;
|
|
||||||
} else {
|
|
||||||
boolean resetRequested = reset.consumeReset();
|
|
||||||
if (!resetRequested) {
|
|
||||||
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
|
|
||||||
encode(mediaCodec, streamer);
|
|
||||||
}
|
|
||||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
|
||||||
alive = !stopped.get() && !capture.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||||
mediaCodec.stop();
|
mediaCodec.stop();
|
||||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
@ -119,7 +103,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
Ln.i("Retrying...");
|
Ln.i("Retrying...");
|
||||||
alive = true;
|
alive = true;
|
||||||
} finally {
|
} finally {
|
||||||
reset.setRunningMediaCodec(null);
|
|
||||||
mediaCodec.reset();
|
mediaCodec.reset();
|
||||||
if (surface != null) {
|
if (surface != null) {
|
||||||
surface.release();
|
surface.release();
|
||||||
@ -180,16 +163,25 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encode(MediaCodec codec, Streamer streamer) throws IOException {
|
private boolean encode(MediaCodec codec, Streamer streamer) throws IOException {
|
||||||
|
boolean eof = false;
|
||||||
|
boolean alive = true;
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
boolean eos;
|
while (!capture.consumeReset() && !eof) {
|
||||||
do {
|
if (stopped.get()) {
|
||||||
|
alive = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
||||||
try {
|
try {
|
||||||
eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
if (capture.consumeReset()) {
|
||||||
// On EOS, there might be data or not, depending on bufferInfo.size
|
// must restart encoding with new size
|
||||||
if (outputBufferId >= 0 && bufferInfo.size > 0) {
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||||
|
if (outputBufferId >= 0) {
|
||||||
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
|
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
|
||||||
|
|
||||||
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
||||||
@ -206,7 +198,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
codec.releaseOutputBuffer(outputBufferId, false);
|
codec.releaseOutputBuffer(outputBufferId, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!eos);
|
}
|
||||||
|
|
||||||
|
if (capture.isClosed()) {
|
||||||
|
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||||
|
alive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !eof && alive;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||||
@ -299,7 +298,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||||||
public void stop() {
|
public void stop() {
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
stopped.set(true);
|
stopped.set(true);
|
||||||
reset.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import com.genymobile.scrcpy.util.Ln;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.IContentProvider;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -65,7 +64,7 @@ public final class ActivityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(AndroidVersions.API_29_ANDROID_10)
|
@TargetApi(AndroidVersions.API_29_ANDROID_10)
|
||||||
public IContentProvider getContentProviderExternal(String name, IBinder token) {
|
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||||
try {
|
try {
|
||||||
Method method = getGetContentProviderExternalMethod();
|
Method method = getGetContentProviderExternalMethod();
|
||||||
Object[] args;
|
Object[] args;
|
||||||
@ -84,7 +83,11 @@ public final class ActivityManager {
|
|||||||
// IContentProvider provider = providerHolder.provider;
|
// IContentProvider provider = providerHolder.provider;
|
||||||
Field providerField = providerHolder.getClass().getDeclaredField("provider");
|
Field providerField = providerHolder.getClass().getDeclaredField("provider");
|
||||||
providerField.setAccessible(true);
|
providerField.setAccessible(true);
|
||||||
return (IContentProvider) providerField.get(providerHolder);
|
Object provider = providerField.get(providerHolder);
|
||||||
|
if (provider == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ContentProvider(this, provider, name, token);
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
return null;
|
return null;
|
||||||
@ -101,12 +104,7 @@ public final class ActivityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ContentProvider createSettingsProvider() {
|
public ContentProvider createSettingsProvider() {
|
||||||
IBinder token = new Binder();
|
return getContentProviderExternal("settings", new Binder());
|
||||||
IContentProvider provider = getContentProviderExternal("settings", token);
|
|
||||||
if (provider == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new ContentProvider(this, provider, "settings", token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method getStartActivityAsUserMethod() throws NoSuchMethodException, ClassNotFoundException {
|
private Method getStartActivityAsUserMethod() throws NoSuchMethodException, ClassNotFoundException {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@ -23,22 +21,14 @@ public final class PowerManager {
|
|||||||
|
|
||||||
private Method getIsScreenOnMethod() throws NoSuchMethodException {
|
private Method getIsScreenOnMethod() throws NoSuchMethodException {
|
||||||
if (isScreenOnMethod == null) {
|
if (isScreenOnMethod == null) {
|
||||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
||||||
isScreenOnMethod = manager.getClass().getMethod("isDisplayInteractive", int.class);
|
|
||||||
} else {
|
|
||||||
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return isScreenOnMethod;
|
return isScreenOnMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isScreenOn(int displayId) {
|
public boolean isScreenOn() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Method method = getIsScreenOnMethod();
|
Method method = getIsScreenOnMethod();
|
||||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
|
||||||
return (boolean) method.invoke(manager, displayId);
|
|
||||||
}
|
|
||||||
return (boolean) method.invoke(manager);
|
return (boolean) method.invoke(manager);
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
|
Reference in New Issue
Block a user