mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 07:14:29 +01:00
Compare commits
8 Commits
windows-pr
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
323549e82f | ||
|
|
90d11810e3 | ||
|
|
1f19ec4aec | ||
|
|
8b0206f7be | ||
|
|
f91fa56593 | ||
|
|
23710e04c1 | ||
|
|
51cbd9a5bc | ||
|
|
82e102e036 |
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
scrcpy.exe --pause-on-exit=if-error %*
|
||||
@@ -39,5 +39,11 @@ sc_adb_device_get_type(const char *serial) {
|
||||
return SC_ADB_DEVICE_TYPE_TCPIP;
|
||||
}
|
||||
|
||||
// TCP/IP devices provided by mDNS contain "adb-tls-connect"
|
||||
// <https://github.com/Genymobile/scrcpy/issues/6248>
|
||||
if (strstr(serial, "adb-tls-connect")) {
|
||||
return SC_ADB_DEVICE_TYPE_TCPIP;
|
||||
}
|
||||
|
||||
return SC_ADB_DEVICE_TYPE_USB;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,49 @@
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static size_t
|
||||
rstrip_len(const char *s, size_t len) {
|
||||
size_t i = len;
|
||||
|
||||
// Ignore trailing whitespaces
|
||||
while (i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) {
|
||||
--i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
locate_last_token(const char *s, size_t len, size_t *start, size_t *end) {
|
||||
size_t i = rstrip_len(s, len);
|
||||
*end = i; // excluded
|
||||
|
||||
// The token contains non-whitespace chars
|
||||
while (i > 0 && (s[i-1] != ' ' && s[i-1] != '\t')) {
|
||||
--i;
|
||||
}
|
||||
|
||||
*start = i; // included
|
||||
}
|
||||
|
||||
static bool
|
||||
is_device_state(const char *s) {
|
||||
// <https://android.googlesource.com/platform/packages/modules/adb/+/1cf2f017d312f73b3dc53bda85ef2610e35a80e9/adb.cpp#144>
|
||||
// "device", "unauthorized" and "offline" are the most common states, so
|
||||
// check them first.
|
||||
return !strcmp(s, "device")
|
||||
|| !strcmp(s, "unauthorized")
|
||||
|| !strcmp(s, "offline")
|
||||
|| !strcmp(s, "bootloader")
|
||||
|| !strcmp(s, "host")
|
||||
|| !strcmp(s, "recovery")
|
||||
|| !strcmp(s, "rescue")
|
||||
|| !strcmp(s, "sideload")
|
||||
|| !strcmp(s, "authorizing")
|
||||
|| !strcmp(s, "connecting")
|
||||
|| !strcmp(s, "detached");
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
// One device line looks like:
|
||||
@@ -25,64 +68,54 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *s = line; // cursor in the line
|
||||
size_t len = strlen(line);
|
||||
|
||||
// After the serial:
|
||||
// - "adb devices" writes a single '\t'
|
||||
// - "adb devices -l" writes multiple spaces
|
||||
// For flexibility, accept both.
|
||||
size_t serial_len = strcspn(s, " \t");
|
||||
size_t start;
|
||||
size_t end;
|
||||
|
||||
// The serial (the first token) may contain spaces, which are also token
|
||||
// separators. To avoid ambiguity, parse the string backwards:
|
||||
// - first, parse all the trailing values until the device state,
|
||||
// identified using a list of well-known values;
|
||||
// - finally, treat the remaining leading token as the device serial.
|
||||
//
|
||||
// Refs:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/6248>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/3537>
|
||||
const char *state;
|
||||
const char *model = NULL;
|
||||
for (;;) {
|
||||
locate_last_token(line, len, &start, &end);
|
||||
if (start == end) {
|
||||
// No more tokens, unexpected
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *token = &line[start];
|
||||
line[end] = '\0';
|
||||
|
||||
if (!strncmp("model:", token, sizeof("model:") - 1)) {
|
||||
model = &token[sizeof("model:") - 1];
|
||||
// We only need the model
|
||||
} else if (is_device_state(token)) {
|
||||
state = token;
|
||||
// The device state is the item immediately after the device serial
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove the trailing parts already handled
|
||||
len = start;
|
||||
}
|
||||
|
||||
assert(state);
|
||||
|
||||
size_t serial_len = rstrip_len(line, start);
|
||||
if (!serial_len) {
|
||||
// empty serial
|
||||
return false;
|
||||
}
|
||||
bool eol = s[serial_len] == '\0';
|
||||
if (eol) {
|
||||
// serial alone is unexpected
|
||||
return false;
|
||||
}
|
||||
s[serial_len] = '\0';
|
||||
char *serial = s;
|
||||
s += serial_len + 1;
|
||||
// After the serial, there might be several spaces
|
||||
s += strspn(s, " \t"); // consume all separators
|
||||
|
||||
size_t state_len = strcspn(s, " ");
|
||||
if (!state_len) {
|
||||
// empty state
|
||||
return false;
|
||||
}
|
||||
eol = s[state_len] == '\0';
|
||||
s[state_len] = '\0';
|
||||
char *state = s;
|
||||
|
||||
char *model = NULL;
|
||||
if (!eol) {
|
||||
s += state_len + 1;
|
||||
|
||||
// Iterate over all properties "key:value key:value ..."
|
||||
for (;;) {
|
||||
size_t token_len = strcspn(s, " ");
|
||||
if (!token_len) {
|
||||
break;
|
||||
}
|
||||
eol = s[token_len] == '\0';
|
||||
s[token_len] = '\0';
|
||||
char *token = s;
|
||||
|
||||
if (!strncmp("model:", token, sizeof("model:") - 1)) {
|
||||
model = &token[sizeof("model:") - 1];
|
||||
// We only need the model
|
||||
break;
|
||||
}
|
||||
|
||||
if (eol) {
|
||||
break;
|
||||
} else {
|
||||
s+= token_len + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
char *serial = line;
|
||||
line[serial_len] = '\0';
|
||||
|
||||
device->serial = strdup(serial);
|
||||
if (!device->serial) {
|
||||
|
||||
@@ -3371,7 +3371,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
|
||||
}
|
||||
if (arg[15] != '=') {
|
||||
// Invalid parameter, ignore
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
const char *value = &arg[16];
|
||||
if (!strcmp(value, "true")) {
|
||||
@@ -3380,14 +3380,44 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
|
||||
if (!strcmp(value, "if-error")) {
|
||||
return SC_PAUSE_ON_EXIT_IF_ERROR;
|
||||
}
|
||||
// Set to false, including when the value is invalid
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
if (!strcmp(value, "false")) {
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
}
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Attempt to detect whether the user launched scrcpy by double-clicking
|
||||
* scrcpy.exe in Windows Explorer.
|
||||
*
|
||||
* If so, the console should remain open on error.
|
||||
*/
|
||||
static bool
|
||||
scrcpy_launched_by_double_click(void) {
|
||||
// No console window
|
||||
if (GetConsoleWindow() == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must be interactive
|
||||
if (!_isatty(_fileno(stdin)) || !_isatty(_fileno(stdout))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check how many processes share the console
|
||||
DWORD dummy;
|
||||
DWORD count = GetConsoleProcessList(&dummy, 1);
|
||||
|
||||
// Only this process attached, assume it was started by double-clicking
|
||||
return count == 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
struct sc_getopt_adapter adapter;
|
||||
@@ -3401,11 +3431,22 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
|
||||
sc_getopt_adapter_destroy(&adapter);
|
||||
|
||||
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_FALSE) {
|
||||
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
|
||||
// Check if "--pause-on-exit" is present in the arguments list, because
|
||||
// it must be taken into account even if command line parsing failed
|
||||
args->pause_on_exit = sc_get_pause_on_exit(argc, argv);
|
||||
}
|
||||
|
||||
if (args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
|
||||
args->pause_on_exit = SC_PAUSE_ON_EXIT_FALSE;
|
||||
#ifdef _WIN32
|
||||
if (scrcpy_launched_by_double_click()) {
|
||||
args->pause_on_exit = SC_PAUSE_ON_EXIT_IF_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
assert(args->pause_on_exit != SC_PAUSE_ON_EXIT_UNDEFINED);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "options.h"
|
||||
|
||||
enum sc_pause_on_exit {
|
||||
SC_PAUSE_ON_EXIT_UNDEFINED,
|
||||
SC_PAUSE_ON_EXIT_TRUE,
|
||||
SC_PAUSE_ON_EXIT_FALSE,
|
||||
SC_PAUSE_ON_EXIT_IF_ERROR,
|
||||
|
||||
@@ -39,7 +39,7 @@ main_scrcpy(int argc, char *argv[]) {
|
||||
.opts = scrcpy_options_default,
|
||||
.help = false,
|
||||
.version = false,
|
||||
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
|
||||
.pause_on_exit = SC_PAUSE_ON_EXIT_UNDEFINED,
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -67,7 +67,8 @@ sc_packet_source_sinks_push_session(struct sc_packet_source *source,
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->push_session(sink, session)) {
|
||||
if (sink->ops->push_session
|
||||
&& !sink->ops->push_session(sink, session)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,45 @@ static void test_adb_devices_spaces(void) {
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_adb_devices_serial_with_spaces(void) {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp device "
|
||||
"product:blazer model:Pixel_10_Pro device:blazer transport_id:3\n";
|
||||
|
||||
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
|
||||
bool ok = sc_adb_parse_devices(output, &vec);
|
||||
assert(ok);
|
||||
assert(vec.size == 1);
|
||||
|
||||
struct sc_adb_device *device = &vec.data[0];
|
||||
assert(!strcmp("adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp",
|
||||
device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("Pixel_10_Pro", device->model));
|
||||
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_adb_devices_with_devpath_without_colon(void) {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"12345678 device 3-1 product:manet model:23117RK66C "
|
||||
"device:manet transport_id:2\n";
|
||||
|
||||
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
|
||||
bool ok = sc_adb_parse_devices(output, &vec);
|
||||
assert(ok);
|
||||
assert(vec.size == 1);
|
||||
|
||||
struct sc_adb_device *device = &vec.data[0];
|
||||
assert(!strcmp("12345678", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("23117RK66C", device->model));
|
||||
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_get_ip_single_line(void) {
|
||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||
"192.168.12.34\r\r\n";
|
||||
@@ -265,6 +304,8 @@ int main(int argc, char *argv[]) {
|
||||
test_adb_devices_without_header();
|
||||
test_adb_devices_corrupted();
|
||||
test_adb_devices_spaces();
|
||||
test_adb_devices_serial_with_spaces();
|
||||
test_adb_devices_with_devpath_without_colon();
|
||||
|
||||
test_get_ip_single_line();
|
||||
test_get_ip_single_line_without_eol();
|
||||
|
||||
@@ -72,18 +72,6 @@ Documentation for command line arguments is available:
|
||||
- `scrcpy --help`
|
||||
- on [github](/README.md)
|
||||
|
||||
To start scrcpy directly without opening a terminal, double-click on one of
|
||||
these files:
|
||||
- `scrcpy-console.bat`: start with a terminal open (it will close when scrcpy
|
||||
terminates, unless an error occurs);
|
||||
- `scrcpy-noconsole.vbs`: start without a terminal (but you won't see any error
|
||||
message).
|
||||
|
||||
_Avoid double-clicking on `scrcpy.exe` directly: on error, the terminal would
|
||||
close immediately and you won't have time to read any error message (this
|
||||
executable is intended to be run from the terminal). Use `scrcpy-console.bat`
|
||||
instead._
|
||||
|
||||
If you plan to always use the same arguments, create a file `myscrcpy.bat`
|
||||
(enable [show file extensions] to avoid confusion) containing your command, For
|
||||
example:
|
||||
@@ -92,9 +80,17 @@ example:
|
||||
scrcpy --prefer-text --turn-screen-off --stay-awake
|
||||
```
|
||||
|
||||
Add `--pause-on-exit=if-error` if you want the console to remain open when
|
||||
scrcpy fails:
|
||||
|
||||
```bash
|
||||
scrcpy --prefer-text --turn-screen-off --stay-awake --pause-on-exit=if-error
|
||||
```
|
||||
|
||||
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
|
||||
|
||||
Then just double-click on that file.
|
||||
Then just double-click on that file to run it.
|
||||
|
||||
You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
|
||||
to add some arguments.
|
||||
To start scrcpy without opening a terminal, double-click `scrcpy-noconsole.vbs`
|
||||
(note that errors won't be shown). To pass arguments, edit (a copy of)
|
||||
`scrcpy-noconsole.vbs` add and the desired arguments.
|
||||
|
||||
@@ -22,9 +22,12 @@ app/deps/libusb.sh linux native static
|
||||
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/linux-native-static"
|
||||
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-linux"
|
||||
|
||||
# Never fall back to system libs
|
||||
unset PKG_CONFIG_PATH
|
||||
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
|
||||
|
||||
rm -rf "$LINUX_BUILD_DIR"
|
||||
meson setup "$LINUX_BUILD_DIR" \
|
||||
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
|
||||
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
|
||||
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
|
||||
--buildtype=release \
|
||||
|
||||
@@ -22,9 +22,12 @@ app/deps/libusb.sh macos native static
|
||||
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/macos-native-static"
|
||||
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-macos"
|
||||
|
||||
# Never fall back to system libs
|
||||
unset PKG_CONFIG_PATH
|
||||
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
|
||||
|
||||
rm -rf "$MACOS_BUILD_DIR"
|
||||
meson setup "$MACOS_BUILD_DIR" \
|
||||
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
|
||||
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
|
||||
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
|
||||
--buildtype=release \
|
||||
|
||||
@@ -29,9 +29,12 @@ app/deps/libusb.sh $WINXX cross shared
|
||||
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/$WINXX-cross-shared"
|
||||
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-windows"
|
||||
|
||||
# Never fall back to system libs
|
||||
unset PKG_CONFIG_PATH
|
||||
export PKG_CONFIG_LIBDIR="$DEPS_INSTALL_DIR/lib/pkgconfig"
|
||||
|
||||
rm -rf "$WINXX_BUILD_DIR"
|
||||
meson setup "$WINXX_BUILD_DIR" \
|
||||
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
|
||||
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
|
||||
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
|
||||
--cross-file=cross_$WINXX.txt \
|
||||
@@ -45,7 +48,6 @@ ninja -C "$WINXX_BUILD_DIR"
|
||||
# Group intermediate outputs into a 'dist' directory
|
||||
mkdir -p "$WINXX_BUILD_DIR/dist"
|
||||
cp "$WINXX_BUILD_DIR"/app/scrcpy.exe "$WINXX_BUILD_DIR/dist/"
|
||||
cp app/data/scrcpy-console.bat "$WINXX_BUILD_DIR/dist/"
|
||||
cp app/data/scrcpy-noconsole.vbs "$WINXX_BUILD_DIR/dist/"
|
||||
cp app/data/icon.png "$WINXX_BUILD_DIR/dist/"
|
||||
cp app/data/open_a_terminal_here.bat "$WINXX_BUILD_DIR/dist/"
|
||||
|
||||
@@ -266,14 +266,14 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
outputThread.start();
|
||||
|
||||
waitEnded();
|
||||
} catch (ConfigurationException e) {
|
||||
// Notify the error to make scrcpy exit
|
||||
streamer.writeDisableStream(true);
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
} catch (AudioCaptureException e) {
|
||||
// Notify the client that the audio could not be captured
|
||||
streamer.writeDisableStream(false);
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Notify the error to make scrcpy exit
|
||||
streamer.writeDisableStream(true);
|
||||
throw e;
|
||||
} finally {
|
||||
// Cleanup everything (either at the end or on error at any step of the initialization)
|
||||
if (mediaCodecThread != null) {
|
||||
|
||||
@@ -264,6 +264,10 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
|
||||
// display the very first frame, and recover from bad quality when no new frames
|
||||
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
|
||||
// real-time priority
|
||||
format.setInteger(MediaFormat.KEY_PRIORITY, 0);
|
||||
// output 1 frame as soon as 1 frame is queued
|
||||
format.setInteger(MediaFormat.KEY_LATENCY, 1);
|
||||
if (maxFps > 0) {
|
||||
// The key existed privately before Android 10:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/625f0aad9f7a259b6881006ad8710adce57d1384%5E%21/>
|
||||
|
||||
Reference in New Issue
Block a user