Compare commits

..

2 Commits

21 changed files with 139 additions and 275 deletions

17
FAQ.md
View File

@ -7,7 +7,7 @@ Here are the common reported problems and their status.
If you encounter any error, the first step is to upgrade to the latest version. If you encounter any error, the first step is to upgrade to the latest version.
## `adb` issues ## `adb` and USB issues
`scrcpy` execute `adb` commands to initialize the connection with the device. If `scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work. `adb` fails, then scrcpy will not work.
@ -133,6 +133,21 @@ Try with another USB cable or plug it into another USB port. See [#281] and
[#283]: https://github.com/Genymobile/scrcpy/issues/283 [#283]: https://github.com/Genymobile/scrcpy/issues/283
## HID/OTG issues on Windows
On Windows, if `scrcpy --otg` (or `--hid-keyboard`/`--hid-mouse`) results in:
> ERROR: Could not find any USB device
(or if only unrelated USB devices are detected), there might be drivers issues.
Please read [#3654], in particular [this comment][#3654-comment1] and [the next
one][#3654-comment2].
[#3654]: https://github.com/Genymobile/scrcpy/issues/3654
[#3654-comment1]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369278232
[#3654-comment2]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369295011
## Control issues ## Control issues

View File

@ -37,7 +37,6 @@ src = [
'src/util/net_intr.c', 'src/util/net_intr.c',
'src/util/process.c', 'src/util/process.c',
'src/util/process_intr.c', 'src/util/process_intr.c',
'src/util/rand.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
'src/util/str.c', 'src/util/str.c',
'src/util/term.c', 'src/util/term.c',
@ -70,12 +69,12 @@ else
endif endif
endif endif
v4l2_support = get_option('v4l2') and host_machine.system() == 'linux' v4l2_support = get_option('v4l2').enabled() and host_machine.system() == 'linux'
if v4l2_support if v4l2_support
src += [ 'src/v4l2_sink.c' ] src += [ 'src/v4l2_sink.c' ]
endif endif
usb_support = get_option('usb') usb_support = get_option('usb').enabled()
if usb_support if usb_support
src += [ src += [
'src/usb/aoa_hid.c', 'src/usb/aoa_hid.c',
@ -171,8 +170,6 @@ check_functions = [
'strdup', 'strdup',
'asprintf', 'asprintf',
'vasprintf', 'vasprintf',
'nrand48',
'jrand48',
] ]
foreach f : check_functions foreach f : check_functions

View File

@ -7,6 +7,8 @@
#include "util/net_intr.h" #include "util/net_intr.h"
#include "util/process_intr.h" #include "util/process_intr.h"
#define SC_SOCKET_NAME "scrcpy"
static bool static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) { listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1); return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
@ -15,11 +17,10 @@ listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
static bool static bool
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel, enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial, struct sc_intr *intr, const char *serial,
const char *device_socket_name,
struct sc_port_range port_range) { struct sc_port_range port_range) {
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (!sc_adb_reverse(intr, serial, device_socket_name, port, if (!sc_adb_reverse(intr, serial, SC_SOCKET_NAME, port,
SC_ADB_NO_STDOUT)) { SC_ADB_NO_STDOUT)) {
// the command itself failed, it will fail on any port // the command itself failed, it will fail on any port
return false; return false;
@ -51,7 +52,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
} }
// failure, disable tunnel and try another port // failure, disable tunnel and try another port
if (!sc_adb_reverse_remove(intr, serial, device_socket_name, if (!sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT)) { SC_ADB_NO_STDOUT)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port); LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
} }
@ -77,13 +78,12 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
static bool static bool
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel, enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial, struct sc_intr *intr, const char *serial,
const char *device_socket_name,
struct sc_port_range port_range) { struct sc_port_range port_range) {
tunnel->forward = true; tunnel->forward = true;
uint16_t port = port_range.first; uint16_t port = port_range.first;
for (;;) { for (;;) {
if (sc_adb_forward(intr, serial, port, device_socket_name, if (sc_adb_forward(intr, serial, port, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT)) { SC_ADB_NO_STDOUT)) {
// success // success
tunnel->local_port = port; tunnel->local_port = port;
@ -123,14 +123,13 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
bool bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name, const char *serial, struct sc_port_range port_range,
struct sc_port_range port_range, bool force_adb_forward) { bool force_adb_forward) {
assert(!tunnel->enabled); assert(!tunnel->enabled);
if (!force_adb_forward) { if (!force_adb_forward) {
// Attempt to use "adb reverse" // Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
device_socket_name, port_range)) {
return true; return true;
} }
@ -140,13 +139,12 @@ sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
LOGW("'adb reverse' failed, fallback to 'adb forward'"); LOGW("'adb reverse' failed, fallback to 'adb forward'");
} }
return enable_tunnel_forward_any_port(tunnel, intr, serial, return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
device_socket_name, port_range);
} }
bool bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name) { const char *serial) {
assert(tunnel->enabled); assert(tunnel->enabled);
bool ret; bool ret;
@ -154,7 +152,7 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
ret = sc_adb_forward_remove(intr, serial, tunnel->local_port, ret = sc_adb_forward_remove(intr, serial, tunnel->local_port,
SC_ADB_NO_STDOUT); SC_ADB_NO_STDOUT);
} else { } else {
ret = sc_adb_reverse_remove(intr, serial, device_socket_name, ret = sc_adb_reverse_remove(intr, serial, SC_SOCKET_NAME,
SC_ADB_NO_STDOUT); SC_ADB_NO_STDOUT);
assert(tunnel->server_socket != SC_SOCKET_NONE); assert(tunnel->server_socket != SC_SOCKET_NONE);

View File

@ -34,14 +34,14 @@ sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
*/ */
bool bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name, const char *serial, struct sc_port_range port_range,
struct sc_port_range port_range, bool force_adb_forward); bool force_adb_forward);
/** /**
* Close the tunnel * Close the tunnel
*/ */
bool bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr, sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, const char *device_socket_name); const char *serial);
#endif #endif

View File

@ -51,47 +51,3 @@ int vasprintf(char **strp, const char *fmt, va_list ap) {
return len; return len;
} }
#endif #endif
#if !defined(HAVE_NRAND48) || !defined(HAVE_JRAND48)
#define SC_RAND48_MASK UINT64_C(0xFFFFFFFFFFFF) // 48 bits
#define SC_RAND48_A UINT64_C(0x5DEECE66D)
#define SC_RAND48_C 0xB
static inline uint64_t rand_iter48(uint64_t x) {
assert((x & ~SC_RAND48_MASK) == 0);
return (x * SC_RAND48_A + SC_RAND48_C) & SC_RAND48_MASK;
}
static uint64_t rand_iter48_xsubi(unsigned short xsubi[3]) {
uint64_t x = ((uint64_t) xsubi[0] << 32)
| ((uint64_t) xsubi[1] << 16)
| xsubi[2];
x = rand_iter48(x);
xsubi[0] = (x >> 32) & 0XFFFF;
xsubi[1] = (x >> 16) & 0XFFFF;
xsubi[2] = x & 0XFFFF;
return x;
}
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]) {
// range [0, 2^31)
return rand_iter48_xsubi(xsubi) >> 17;
}
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]) {
// range [-2^31, 2^31)
union {
uint32_t u;
int32_t i;
} v;
v.u = rand_iter48_xsubi(xsubi) >> 16;
return v.i;
}
#endif
#endif

View File

@ -59,12 +59,4 @@ int asprintf(char **strp, const char *fmt, ...);
int vasprintf(char **strp, const char *fmt, va_list ap); int vasprintf(char **strp, const char *fmt, va_list ap);
#endif #endif
#ifndef HAVE_NRAND48
long nrand48(unsigned short xsubi[3]);
#endif
#ifndef HAVE_JRAND48
long jrand48(unsigned short xsubi[3]);
#endif
#endif #endif

View File

@ -32,7 +32,6 @@
#include "util/acksync.h" #include "util/acksync.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/rand.h"
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
# include "v4l2_sink.h" # include "v4l2_sink.h"
#endif #endif
@ -266,14 +265,6 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
// event // event
} }
static uint32_t
scrcpy_generate_uid() {
struct sc_rand rand;
sc_rand_init(&rand);
// Only use 31 bits to avoid issues with signed values on the Java-side
return sc_rand_u32(&rand) & 0x7FFFFFFF;
}
enum scrcpy_exit_code enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
@ -307,10 +298,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_acksync *acksync = NULL; struct sc_acksync *acksync = NULL;
uint32_t uid = scrcpy_generate_uid();
struct sc_server_params params = { struct sc_server_params params = {
.uid = uid,
.req_serial = options->serial, .req_serial = options->serial,
.select_usb = options->select_usb, .select_usb = options->select_usb,
.select_tcpip = options->select_tcpip, .select_tcpip = options->select_tcpip,

View File

@ -20,7 +20,6 @@
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_ADB_PORT_DEFAULT 5555 #define SC_ADB_PORT_DEFAULT 5555
#define SC_SOCKET_NAME_PREFIX "scrcpy_"
static char * static char *
get_server_path(void) { get_server_path(void) {
@ -198,7 +197,6 @@ execute_server(struct sc_server *server,
cmd[count++] = p; \ cmd[count++] = p; \
} }
ADD_PARAM("uid=%08x", params->uid);
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate); ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
@ -366,7 +364,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
} }
server->serial = NULL; server->serial = NULL;
server->device_socket_name = NULL;
server->stopped = false; server->stopped = false;
server->video_socket = SC_SOCKET_NONE; server->video_socket = SC_SOCKET_NONE;
@ -466,8 +463,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
} }
// we don't need the adb tunnel anymore // we don't need the adb tunnel anymore
sc_adb_tunnel_close(tunnel, &server->intr, serial, sc_adb_tunnel_close(tunnel, &server->intr, serial);
server->device_socket_name);
// The sockets will be closed on stop if device_read_info() fails // The sockets will be closed on stop if device_read_info() fails
bool ok = device_read_info(&server->intr, video_socket, info); bool ok = device_read_info(&server->intr, video_socket, info);
@ -498,8 +494,7 @@ fail:
if (tunnel->enabled) { if (tunnel->enabled) {
// Always leave this function with tunnel disabled // Always leave this function with tunnel disabled
sc_adb_tunnel_close(tunnel, &server->intr, serial, sc_adb_tunnel_close(tunnel, &server->intr, serial);
server->device_socket_name);
} }
return false; return false;
@ -769,23 +764,13 @@ run_server(void *data) {
assert(serial); assert(serial);
LOGD("Device serial: %s", serial); LOGD("Device serial: %s", serial);
int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x",
params->uid);
if (r == -1) {
LOG_OOM();
goto error_connection_failed;
}
assert(r == sizeof(SC_SOCKET_NAME_PREFIX) - 1 + 8);
assert(server->device_socket_name);
ok = push_server(&server->intr, serial); ok = push_server(&server->intr, serial);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial, ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, serial,
server->device_socket_name, params->port_range, params->port_range, params->force_adb_forward);
params->force_adb_forward);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
@ -793,8 +778,7 @@ run_server(void *data) {
// server will connect to our server socket // server will connect to our server socket
sc_pid pid = execute_server(server, params); sc_pid pid = execute_server(server, params);
if (pid == SC_PROCESS_NONE) { if (pid == SC_PROCESS_NONE) {
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial, sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
server->device_socket_name);
goto error_connection_failed; goto error_connection_failed;
} }
@ -806,8 +790,7 @@ run_server(void *data) {
if (!ok) { if (!ok) {
sc_process_terminate(pid); sc_process_terminate(pid);
sc_process_wait(pid, true); // ignore exit code sc_process_wait(pid, true); // ignore exit code
sc_adb_tunnel_close(&server->tunnel, &server->intr, serial, sc_adb_tunnel_close(&server->tunnel, &server->intr, serial);
server->device_socket_name);
goto error_connection_failed; goto error_connection_failed;
} }
@ -901,7 +884,6 @@ sc_server_destroy(struct sc_server *server) {
} }
free(server->serial); free(server->serial);
free(server->device_socket_name);
sc_server_params_destroy(&server->params); sc_server_params_destroy(&server->params);
sc_intr_destroy(&server->intr); sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped); sc_cond_destroy(&server->cond_stopped);

View File

@ -22,7 +22,6 @@ struct sc_server_info {
}; };
struct sc_server_params { struct sc_server_params {
uint32_t uid;
const char *req_serial; const char *req_serial;
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
@ -55,7 +54,6 @@ struct sc_server {
// The internal allocated strings are copies owned by the server // The internal allocated strings are copies owned by the server
struct sc_server_params params; struct sc_server_params params;
char *serial; char *serial;
char *device_socket_name;
sc_thread thread; sc_thread thread;
struct sc_server_info info; // initialized once connected struct sc_server_info info; // initialized once connected

View File

@ -1,24 +0,0 @@
#include "rand.h"
#include <stdlib.h>
#include "tick.h"
void sc_rand_init(struct sc_rand *rand) {
sc_tick seed = sc_tick_now(); // microsecond precision
rand->xsubi[0] = (seed >> 32) & 0xFFFF;
rand->xsubi[1] = (seed >> 16) & 0xFFFF;
rand->xsubi[2] = seed & 0xFFFF;
}
uint32_t sc_rand_u32(struct sc_rand *rand) {
// jrand returns a value in range [-2^31, 2^31]
// conversion from signed to unsigned is well-defined to wrap-around
return jrand48(rand->xsubi);
}
uint64_t sc_rand_u64(struct sc_rand *rand) {
uint32_t msb = sc_rand_u32(rand);
uint32_t lsb = sc_rand_u32(rand);
return ((uint64_t) msb << 32) | lsb;
}

View File

@ -1,16 +0,0 @@
#ifndef SC_RAND_H
#define SC_RAND_H
#include "common.h"
#include <inttypes.h>
struct sc_rand {
unsigned short xsubi[3];
};
void sc_rand_init(struct sc_rand *rand);
uint32_t sc_rand_u32(struct sc_rand *rand);
uint64_t sc_rand_u64(struct sc_rand *rand);
#endif

View File

@ -4,5 +4,5 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser
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('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: 'feature', value: 'enabled', description: 'Enable V4L2 feature when supported')
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported') option('usb', type: 'feature', value: 'enabled', description: 'Enable HID/OTG features when supported')

View File

@ -20,21 +20,18 @@ BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes" CLASSES_DIR="$BUILD_DIR/classes"
GEN_DIR="$BUILD_DIR/gen"
SERVER_DIR=$(dirname "$0") SERVER_DIR=$(dirname "$0")
SERVER_BINARY=scrcpy-server SERVER_BINARY=scrcpy-server
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar"
echo "Platform: android-$PLATFORM" echo "Platform: android-$PLATFORM"
echo "Build-tools: $BUILD_TOOLS" echo "Build-tools: $BUILD_TOOLS"
echo "Build dir: $BUILD_DIR" echo "Build dir: $BUILD_DIR"
rm -rf "$CLASSES_DIR" "$GEN_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
mkdir -p "$CLASSES_DIR" mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
mkdir -p "$GEN_DIR/com/genymobile/scrcpy"
<< EOF cat > "$GEN_DIR/com/genymobile/scrcpy/BuildConfig.java" << EOF cat > "$CLASSES_DIR/com/genymobile/scrcpy/BuildConfig.java"
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
public final class BuildConfig { public final class BuildConfig {
@ -45,15 +42,13 @@ EOF
echo "Generating java from aidl..." echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl" cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl "$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \ "$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \
android/content/IOnPrimaryClipChangedListener.aidl android/content/IOnPrimaryClipChangedListener.aidl
echo "Compiling java sources..." echo "Compiling java sources..."
cd ../java cd ../java
javac -bootclasspath "$ANDROID_JAR" \ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
-cp "$LAMBDA_JAR:$GEN_DIR" \
-d "$CLASSES_DIR" \
-source 1.8 -target 1.8 \ -source 1.8 -target 1.8 \
com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java com/genymobile/scrcpy/wrappers/*.java
@ -73,7 +68,7 @@ then
echo "Archiving..." echo "Archiving..."
cd "$BUILD_DIR" cd "$BUILD_DIR"
jar cvf "$SERVER_BINARY" classes.dex jar cvf "$SERVER_BINARY" classes.dex
rm -rf classes.dex rm -rf classes.dex classes
else else
# use d8 # use d8
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \ "$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
@ -85,8 +80,7 @@ else
cd "$BUILD_DIR" cd "$BUILD_DIR"
mv classes.zip "$SERVER_BINARY" mv classes.zip "$SERVER_BINARY"
rm -rf classes
fi fi
rm -rf "$GEN_DIR" "$CLASSES_DIR"
echo "Server generated in $BUILD_DIR/$SERVER_BINARY" echo "Server generated in $BUILD_DIR/$SERVER_BINARY"

View File

@ -4,8 +4,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class CodecOption { public class CodecOption {
private final String key; private String key;
private final Object value; private Object value;
public CodecOption(String key, Object value) { public CodecOption(String key, Object value) {
this.key = key; this.key = key;

View File

@ -75,7 +75,7 @@ public class Controller {
SystemClock.sleep(500); SystemClock.sleep(500);
} }
while (!Thread.currentThread().isInterrupted()) { while (true) {
handleEvent(); handleEvent();
} }
} }
@ -258,9 +258,12 @@ public class Controller {
* Schedule a call to set power mode to off after a small delay. * Schedule a call to set power mode to off after a small delay.
*/ */
private static void schedulePowerModeOff() { private static void schedulePowerModeOff() {
EXECUTOR.schedule(() -> { EXECUTOR.schedule(new Runnable() {
Ln.i("Forcing screen off"); @Override
Device.setScreenPowerMode(Device.POWER_MODE_OFF); public void run() {
Ln.i("Forcing screen off");
Device.setScreenPowerMode(Device.POWER_MODE_OFF);
}
}, 200, TimeUnit.MILLISECONDS); }, 200, TimeUnit.MILLISECONDS);
} }

View File

@ -15,7 +15,7 @@ public final class DesktopConnection implements Closeable {
private static final int DEVICE_NAME_FIELD_LENGTH = 64; private static final int DEVICE_NAME_FIELD_LENGTH = 64;
private static final String SOCKET_NAME_PREFIX = "scrcpy"; private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket videoSocket; private final LocalSocket videoSocket;
private final FileDescriptor videoFd; private final FileDescriptor videoFd;
@ -46,22 +46,12 @@ public final class DesktopConnection implements Closeable {
return localSocket; return localSocket;
} }
private static String getSocketName(int uid) { public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
if (uid == -1) {
// If no UID is set, use "scrcpy" to simplify using scrcpy-server alone
return SOCKET_NAME_PREFIX;
}
return SOCKET_NAME_PREFIX + String.format("_%08x", uid);
}
public static DesktopConnection open(int uid, boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
String socketName = getSocketName(uid);
LocalSocket videoSocket; LocalSocket videoSocket;
LocalSocket controlSocket = null; LocalSocket controlSocket = null;
if (tunnelForward) { if (tunnelForward) {
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
videoSocket = localServerSocket.accept(); videoSocket = localServerSocket.accept();
if (sendDummyByte) { if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error // send one byte so the client may read() to detect a connection error
@ -75,12 +65,14 @@ public final class DesktopConnection implements Closeable {
throw e; throw e;
} }
} }
} finally {
localServerSocket.close();
} }
} else { } else {
videoSocket = connect(socketName); videoSocket = connect(SOCKET_NAME);
if (control) { if (control) {
try { try {
controlSocket = connect(socketName); controlSocket = connect(SOCKET_NAME);
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
videoSocket.close(); videoSocket.close();
throw e; throw e;

View File

@ -25,7 +25,7 @@ public final class DeviceMessageSender {
} }
public void loop() throws IOException, InterruptedException { public void loop() throws IOException, InterruptedException {
while (!Thread.currentThread().isInterrupted()) { while (true) {
String text; String text;
long sequence; long sequence;
synchronized (this) { synchronized (this) {

View File

@ -6,7 +6,6 @@ import java.util.List;
public class Options { public class Options {
private Ln.Level logLevel = Ln.Level.DEBUG; private Ln.Level logLevel = Ln.Level.DEBUG;
private int uid = -1; // 31-bit non-negative value, or -1
private int maxSize; private int maxSize;
private int bitRate = 8000000; private int bitRate = 8000000;
private int maxFps; private int maxFps;
@ -38,14 +37,6 @@ public class Options {
this.logLevel = logLevel; this.logLevel = logLevel;
} }
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public int getMaxSize() { public int getMaxSize() {
return maxSize; return maxSize;
} }

View File

@ -3,8 +3,8 @@ package com.genymobile.scrcpy;
import java.util.Objects; import java.util.Objects;
public class Position { public class Position {
private final Point point; private Point point;
private final Size screenSize; private Size screenSize;
public Position(Point point, Size screenSize) { public Position(Point point, Size screenSize) {
this.point = point; this.point = point;

View File

@ -9,7 +9,6 @@ import android.media.MediaCodecList;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@ -76,88 +75,63 @@ public class ScreenEncoder implements Device.RotationListener {
} }
private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {
MediaCodec codec = createCodec(encoderName);
MediaFormat format = createFormat(bitRate, maxFps, codecOptions); MediaFormat format = createFormat(bitRate, maxFps, codecOptions);
IBinder display = createDisplay();
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;
try { try {
do { do {
MediaCodec codec = createCodec(encoderName);
IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo(); ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
// include the locked video orientation // include the locked video orientation
Rect videoRect = screenInfo.getVideoSize().toRect(); Rect videoRect = screenInfo.getVideoSize().toRect();
format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width()); // does not include the locked video orientation
format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height()); Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setSize(format, videoRect.width(), videoRect.height());
Surface surface = null; Surface surface = null;
try { try {
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); configure(codec, format);
surface = codec.createInputSurface(); surface = codec.createInputSurface();
// does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start(); codec.start();
alive = encode(codec, fd); alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException // do not call stop() on exception, it would trigger an IllegalStateException
codec.stop(); codec.stop();
} catch (MediaCodec.CodecException e) {
Ln.e("Codec error: " + e.getMessage());
// <https://developer.android.com/reference/android/media/MediaCodec#error-handling>
// For simplicity, handle isTransient() like isRecoverable()
if (e.isRecoverable() || e.isTransient()) {
// Avoid busy-loop if too many errors are generated
SystemClock.sleep(50);
} else if (!prepareDownsizeRetry(device, screenInfo)) {
throw e;
}
alive = true;
} catch (IllegalStateException | IllegalArgumentException e) { } catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareDownsizeRetry(device, screenInfo)) { if (!downsizeOnError || firstFrameSent) {
// Fail immediately
throw e; throw e;
} }
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
if (newMaxSize == 0) {
// Definitively fail
throw e;
}
// Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "...");
device.setMaxSize(newMaxSize);
alive = true; alive = true;
} finally { } finally {
codec.reset(); destroyDisplay(display);
codec.release();
if (surface != null) { if (surface != null) {
surface.release(); surface.release();
} }
} }
} while (alive); } while (alive);
} finally { } finally {
codec.release();
device.setRotationListener(null); device.setRotationListener(null);
SurfaceControl.destroyDisplay(display);
} }
} }
private boolean prepareDownsizeRetry(Device device, ScreenInfo screenInfo) {
if (!downsizeOnError || firstFrameSent) {
Ln.i("#1 " + downsizeOnError + " " + firstFrameSent);
// Must fail immediately
return false;
}
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
Ln.i("newMaxSize = " + newMaxSize);
if (newMaxSize == 0) {
// Must definitively fail
return false;
}
// Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "...");
device.setMaxSize(newMaxSize);
return true;
}
private static int chooseMaxSizeFallback(Size failedSize) { private static int chooseMaxSizeFallback(Size failedSize) {
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight()); int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
for (int value : MAX_SIZE_FALLBACK) { for (int value : MAX_SIZE_FALLBACK) {
@ -304,6 +278,15 @@ public class ScreenEncoder implements Device.RotationListener {
return SurfaceControl.createDisplay("scrcpy", secure); return SurfaceControl.createDisplay("scrcpy", secure);
} }
private static void configure(MediaCodec codec, MediaFormat format) {
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
}
private static void setSize(MediaFormat format, int width, int height) {
format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
}
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) {
SurfaceControl.openTransaction(); SurfaceControl.openTransaction();
try { try {
@ -314,4 +297,8 @@ public class ScreenEncoder implements Device.RotationListener {
SurfaceControl.closeTransaction(); SurfaceControl.closeTransaction();
} }
} }
private static void destroyDisplay(IBinder display) {
SurfaceControl.destroyDisplay(display);
}
} }

View File

@ -66,12 +66,11 @@ public final class Server {
Thread initThread = startInitThread(options); Thread initThread = startInitThread(options);
int uid = options.getUid();
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
boolean control = options.getControl(); boolean control = options.getControl();
boolean sendDummyByte = options.getSendDummyByte(); boolean sendDummyByte = options.getSendDummyByte();
try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, control, sendDummyByte)) { try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
if (options.getSendDeviceMeta()) { if (options.getSendDeviceMeta()) {
Size videoSize = device.getScreenInfo().getVideoSize(); Size videoSize = device.getScreenInfo().getVideoSize();
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
@ -88,7 +87,12 @@ public final class Server {
controllerThread = startController(controller); controllerThread = startController(controller);
deviceMessageSenderThread = startDeviceMessageSender(controller.getSender()); deviceMessageSenderThread = startDeviceMessageSender(controller.getSender());
device.setClipboardListener(text -> controller.getSender().pushClipboardText(text)); device.setClipboardListener(new Device.ClipboardListener() {
@Override
public void onClipboardTextChanged(String text) {
controller.getSender().pushClipboardText(text);
}
});
} }
try { try {
@ -110,18 +114,26 @@ public final class Server {
} }
private static Thread startInitThread(final Options options) { private static Thread startInitThread(final Options options) {
Thread thread = new Thread(() -> initAndCleanUp(options)); Thread thread = new Thread(new Runnable() {
@Override
public void run() {
initAndCleanUp(options);
}
});
thread.start(); thread.start();
return thread; return thread;
} }
private static Thread startController(final Controller controller) { private static Thread startController(final Controller controller) {
Thread thread = new Thread(() -> { Thread thread = new Thread(new Runnable() {
try { @Override
controller.control(); public void run() {
} catch (IOException e) { try {
// this is expected on close controller.control();
Ln.d("Controller stopped"); } catch (IOException e) {
// this is expected on close
Ln.d("Controller stopped");
}
} }
}); });
thread.start(); thread.start();
@ -129,12 +141,15 @@ public final class Server {
} }
private static Thread startDeviceMessageSender(final DeviceMessageSender sender) { private static Thread startDeviceMessageSender(final DeviceMessageSender sender) {
Thread thread = new Thread(() -> { Thread thread = new Thread(new Runnable() {
try { @Override
sender.loop(); public void run() {
} catch (IOException | InterruptedException e) { try {
// this is expected on close sender.loop();
Ln.d("Device message sender stopped"); } catch (IOException | InterruptedException e) {
// this is expected on close
Ln.d("Device message sender stopped");
}
} }
}); });
thread.start(); thread.start();
@ -163,13 +178,6 @@ public final class Server {
String key = arg.substring(0, equalIndex); String key = arg.substring(0, equalIndex);
String value = arg.substring(equalIndex + 1); String value = arg.substring(equalIndex + 1);
switch (key) { switch (key) {
case "uid":
int uid = Integer.parseInt(value, 0x10);
if (uid < -1) {
throw new IllegalArgumentException("uid may not be negative (except -1 for 'none'): " + uid);
}
options.setUid(uid);
break;
case "log_level": case "log_level":
Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
options.setLogLevel(level); options.setLogLevel(level);
@ -311,9 +319,12 @@ public final class Server {
} }
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
Ln.e("Exception on thread " + t, e); @Override
suggestFix(e); public void uncaughtException(Thread t, Throwable e) {
Ln.e("Exception on thread " + t, e);
suggestFix(e);
}
}); });
Options options = createOptions(args); Options options = createOptions(args);