Compare commits

..

1 Commits
master ... tmp

Author SHA1 Message Date
Romain Vimont
bed5f45cf8 Mention how to start a launcher in documentation 2024-12-04 13:06:32 +01:00
58 changed files with 231 additions and 461 deletions

View File

@ -188,7 +188,7 @@
identification within third-party archives. identification within third-party archives.
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2025 Romain Vimont Copyright (C) 2018-2024 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.** their name contains `scrcpy`.**
# scrcpy (v3.1) # scrcpy (v3.0)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@ -78,16 +78,6 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
- [macOS](doc/macos.md) - [macOS](doc/macos.md)
## Must-know tips
- [Reducing resolution](doc/video.md#size) may greatly improve performance
(`scrcpy -m1024`)
- [_Right-click_](doc/mouse.md#mouse-bindings) triggers `BACK`
- [_Middle-click_](doc/mouse.md#mouse-bindings) triggers `HOME`
- <kbd>Alt</kbd>+<kbd>f</kbd> toggles [fullscreen](doc/window.md#fullscreen)
- There are many other [shortcuts](doc/shortcuts.md)
## Usage examples ## Usage examples
There are a lot of options, [documented](#user-documentation) in separate pages. There are a lot of options, [documented](#user-documentation) in separate pages.
@ -210,7 +200,7 @@ work][donate]:
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2025 Romain Vimont Copyright (C) 2018-2024 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -57,7 +57,6 @@ _scrcpy() {
--no-mipmaps --no-mipmaps
--no-mouse-hover --no-mouse-hover
--no-power-on --no-power-on
--no-vd-destroy-content
--no-vd-system-decorations --no-vd-system-decorations
--no-video --no-video
--no-video-playback --no-video-playback

View File

@ -63,7 +63,6 @@ arguments=(
'--no-mipmaps[Disable the generation of mipmaps]' '--no-mipmaps[Disable the generation of mipmaps]'
'--no-mouse-hover[Do not forward mouse hover events]' '--no-mouse-hover[Do not forward mouse hover events]'
'--no-power-on[Do not power on the device on start]' '--no-power-on[Do not power on the device on start]'
'--no-vd-destroy-content[Disable virtual display "destroy content on removal" flag]'
'--no-vd-system-decorations[Disable virtual display system decorations flag]' '--no-vd-system-decorations[Disable virtual display system decorations flag]'
'--no-video[Disable video forwarding]' '--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]' '--no-video-playback[Disable video playback]'

View File

@ -1,68 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=1.5.0
FILENAME=dav1d-$VERSION.tar.gz
PROJECT_DIR=dav1d-$VERSION
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
if [[ -d "$DIRNAME" ]]
then
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
cd "$DIRNAME"
else
mkdir "$DIRNAME"
cd "$DIRNAME"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
--libdir=lib
-Denable_tests=false
-Denable_tools=false
# Always build dav1d statically
--default-library=static
)
if [[ "$BUILD_TYPE" == cross ]]
then
case "$HOST" in
win32)
conf+=(
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/i686-w64-mingw32.meson"
)
;;
win64)
conf+=(
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/x86_64-w64-mingw32.meson"
)
;;
*)
echo "Unsupported host: $HOST" >&2
exit 1
esac
fi
meson setup . "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
fi
ninja
ninja install

View File

@ -40,14 +40,16 @@ else
export LDFLAGS='-static-libgcc -static' export LDFLAGS='-static-libgcc -static'
elif [[ "$HOST" == "macos" ]] elif [[ "$HOST" == "macos" ]]
then then
export LDFLAGS="$LDFLAGS -L/opt/homebrew/opt/zlib/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/zlib/include"
export LDFLAGS="$LDFLAGS-L/opt/homebrew/opt/libiconv/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/libiconv/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig" export PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig"
fi fi
export PKG_CONFIG_PATH="$INSTALL_DIR/$DIRNAME/lib/pkgconfig:$PKG_CONFIG_PATH"
conf=( conf=(
--prefix="$INSTALL_DIR/$DIRNAME" --prefix="$INSTALL_DIR/$DIRNAME"
--pkg-config-flags="--static"
--extra-cflags="-O2 -fPIC" --extra-cflags="-O2 -fPIC"
--disable-programs --disable-programs
--disable-doc --disable-doc
@ -60,11 +62,9 @@ else
--disable-vaapi --disable-vaapi
--disable-vdpau --disable-vdpau
--enable-swresample --enable-swresample
--enable-libdav1d
--enable-decoder=h264 --enable-decoder=h264
--enable-decoder=hevc --enable-decoder=hevc
--enable-decoder=av1 --enable-decoder=av1
--enable-decoder=libdav1d
--enable-decoder=pcm_s16le --enable-decoder=pcm_s16le
--enable-decoder=opus --enable-decoder=opus
--enable-decoder=aac --enable-decoder=aac

View File

@ -5,10 +5,10 @@ cd "$DEPS_DIR"
. common . common
process_args "$@" process_args "$@"
VERSION=2.30.10 VERSION=2.30.9
FILENAME=SDL-$VERSION.tar.gz FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168 SHA256SUM=682a055004081e37d81a7d4ce546c3ee3ef2e0e6a675ed2651e430ccd14eb407
cd "$SOURCES_DIR" cd "$SOURCES_DIR"

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe" VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy" VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "3.1" VALUE "ProductVersion", "3.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -369,12 +369,6 @@ Do not forward mouse hover (mouse motion without any clicks) events.
.B \-\-no\-power\-on .B \-\-no\-power\-on
Do not power on the device on start. Do not power on the device on start.
.TP
.B \-\-no\-vd\-destroy\-content
Disable virtual display "destroy content on removal" flag.
With this option, when the virtual display is closed, the running apps are moved to the main display rather than being destroyed.
.TP .TP
.B \-\-no\-vd\-system\-decorations .B \-\-no\-vd\-system\-decorations
Disable virtual display system decorations flag. Disable virtual display system decorations flag.
@ -524,15 +518,13 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP .TP
.BI "\-\-tcpip\fR[=[+]\fIip\fR[:\fIport\fR]] .BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
Configure and connect the device over TCP/IP. Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting. If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
Prefix the address with a '+' to force a reconnection.
.TP .TP
.BI "\-\-time\-limit " seconds .BI "\-\-time\-limit " seconds
Set the maximum mirroring time, in seconds. Set the maximum mirroring time, in seconds.
@ -829,7 +821,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2018 Genymobile <https://www.genymobile.com> Copyright \(co 2018 Genymobile <https://www.genymobile.com>
Copyright \(co 2018\-2025 Romain Vimont <rom@rom1v.com> Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
Licensed under the Apache License, Version 2.0. Licensed under the Apache License, Version 2.0.

View File

@ -412,7 +412,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
// "adb connect" always returns successfully (with exit code 0), even in // "adb connect" always returns successfully (with exit code 0), even in
// case of failure. As a workaround, check if its output starts with // case of failure. As a workaround, check if its output starts with
// "connected" or "already connected". // "connected".
char buf[128]; char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
sc_pipe_close(pout); sc_pipe_close(pout);
@ -429,8 +429,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
assert((size_t) r < sizeof(buf)); assert((size_t) r < sizeof(buf));
buf[r] = '\0'; buf[r] = '\0';
ok = !strncmp("connected", buf, sizeof("connected") - 1) ok = !strncmp("connected", buf, sizeof("connected") - 1);
|| !strncmp("already connected", buf, sizeof("already connected") - 1);
if (!ok && !(flags & SC_ADB_NO_STDERR)) { if (!ok && !(flags & SC_ADB_NO_STDERR)) {
// "adb connect" also prints errors to stdout. Since we capture it, // "adb connect" also prints errors to stdout. Since we capture it,
// re-print the error to stderr. // re-print the error to stderr.

View File

@ -110,7 +110,6 @@ enum {
OPT_CAPTURE_ORIENTATION, OPT_CAPTURE_ORIENTATION,
OPT_ANGLE, OPT_ANGLE,
OPT_NO_VD_SYSTEM_DECORATIONS, OPT_NO_VD_SYSTEM_DECORATIONS,
OPT_NO_VD_DESTROY_CONTENT,
}; };
struct sc_option { struct sc_option {
@ -660,15 +659,6 @@ static const struct sc_option options[] = {
.longopt = "no-power-on", .longopt = "no-power-on",
.text = "Do not power on the device on start.", .text = "Do not power on the device on start.",
}, },
{
.longopt_id = OPT_NO_VD_DESTROY_CONTENT,
.longopt = "no-vd-destroy-content",
.text = "Disable virtual display \"destroy content on removal\" "
"flag.\n"
"With this option, when the virtual display is closed, the "
"running apps are moved to the main display rather than being "
"destroyed.",
},
{ {
.longopt_id = OPT_NO_VD_SYSTEM_DECORATIONS, .longopt_id = OPT_NO_VD_SYSTEM_DECORATIONS,
.longopt = "no-vd-system-decorations", .longopt = "no-vd-system-decorations",
@ -870,17 +860,16 @@ static const struct sc_option options[] = {
{ {
.longopt_id = OPT_TCPIP, .longopt_id = OPT_TCPIP,
.longopt = "tcpip", .longopt = "tcpip",
.argdesc = "[+]ip[:port]", .argdesc = "ip[:port]",
.optional_arg = true, .optional_arg = true,
.text = "Configure and connect the device over TCP/IP.\n" .text = "Configure and reconnect the device over TCP/IP.\n"
"If a destination address is provided, then scrcpy connects to " "If a destination address is provided, then scrcpy connects to "
"this address before starting. The device must listen on the " "this address before starting. The device must listen on the "
"given TCP port (default is 5555).\n" "given TCP port (default is 5555).\n"
"If no destination address is provided, then scrcpy attempts " "If no destination address is provided, then scrcpy attempts "
"to find the IP address of the current device (typically " "to find the IP address of the current device (typically "
"connected over USB), enables TCP/IP mode, then connects to " "connected over USB), enables TCP/IP mode, then connects to "
"this address before starting.\n" "this address before starting.",
"Prefix the address with a '+' to force a reconnection.",
}, },
{ {
.longopt_id = OPT_TIME_LIMIT, .longopt_id = OPT_TIME_LIMIT,
@ -2715,11 +2704,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_ANGLE: case OPT_ANGLE:
opts->angle = optarg; opts->angle = optarg;
break; break;
case OPT_NO_VD_DESTROY_CONTENT:
opts->vd_destroy_content = false;
break;
case OPT_NO_VD_SYSTEM_DECORATIONS: case OPT_NO_VD_SYSTEM_DECORATIONS:
opts->vd_system_decorations = false; opts->vd_system_decorations = optarg;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr

View File

@ -152,10 +152,8 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
return 2; return 2;
case SC_CONTROL_MSG_TYPE_UHID_CREATE: case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write16be(&buf[1], msg->uhid_create.id); sc_write16be(&buf[1], msg->uhid_create.id);
sc_write16be(&buf[3], msg->uhid_create.vendor_id);
sc_write16be(&buf[5], msg->uhid_create.product_id);
size_t index = 7; size_t index = 3;
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127); index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
sc_write16be(&buf[index], msg->uhid_create.report_desc_size); sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
@ -280,13 +278,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
// Quote only if name is not null // Quote only if name is not null
const char *name = msg->uhid_create.name; const char *name = msg->uhid_create.name;
const char *quote = name ? "\"" : ""; const char *quote = name ? "\"" : "";
LOG_CMSG("UHID create [%" PRIu16 "] %04" PRIx16 ":%04" PRIx16 LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
" name=%s%s%s report_desc_size=%" PRIu16, "report_desc_size=%" PRIu16, msg->uhid_create.id,
msg->uhid_create.id, quote, name, quote, msg->uhid_create.report_desc_size);
msg->uhid_create.vendor_id,
msg->uhid_create.product_id,
quote, name, quote,
msg->uhid_create.report_desc_size);
break; break;
} }
case SC_CONTROL_MSG_TYPE_UHID_INPUT: { case SC_CONTROL_MSG_TYPE_UHID_INPUT: {

View File

@ -94,8 +94,6 @@ struct sc_control_msg {
} set_display_power; } set_display_power;
struct { struct {
uint16_t id; uint16_t id;
uint16_t vendor_id;
uint16_t product_id;
const char *name; // pointer to static data const char *name; // pointer to static data
uint16_t report_desc_size; uint16_t report_desc_size;
const uint8_t *report_desc; // pointer to static data const uint8_t *report_desc; // pointer to static data

View File

@ -15,6 +15,7 @@ struct sc_hid_input {
struct sc_hid_open { struct sc_hid_open {
uint16_t hid_id; uint16_t hid_id;
const char *name; // pointer to static memory
const uint8_t *report_desc; // pointer to static memory const uint8_t *report_desc; // pointer to static memory
size_t report_desc_size; size_t report_desc_size;
}; };

View File

@ -52,10 +52,10 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x09, 0x30, 0x09, 0x30,
// Usage (Y) Left stick y // Usage (Y) Left stick y
0x09, 0x31, 0x09, 0x31,
// Usage (Rx) Right stick x // Usage (Z) Right stick x
0x09, 0x33, 0x09, 0x32,
// Usage (Ry) Right stick y // Usage (Rz) Right stick y
0x09, 0x34, 0x09, 0x35,
// Logical Minimum (0) // Logical Minimum (0)
0x15, 0x00, 0x15, 0x00,
// Logical Maximum (65535) // Logical Maximum (65535)
@ -65,15 +65,15 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x75, 0x10, 0x75, 0x10,
// Report Count (4) // Report Count (4)
0x95, 0x04, 0x95, 0x04,
// Input (Data, Variable, Absolute): 4x2 bytes (X, Y, Z, Rz) // Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
0x81, 0x02, 0x81, 0x02,
// Usage Page (Generic Desktop) // Usage Page (Simulation Controls)
0x05, 0x01, 0x05, 0x02,
// Usage (Z) // Usage (Brake)
0x09, 0x32, 0x09, 0xC5,
// Usage (Rz) // Usage (Accelerator)
0x09, 0x35, 0x09, 0xC4,
// Logical Minimum (0) // Logical Minimum (0)
0x15, 0x00, 0x15, 0x00,
// Logical Maximum (32767) // Logical Maximum (32767)
@ -82,7 +82,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x75, 0x10, 0x75, 0x10,
// Report Count (2) // Report Count (2)
0x95, 0x02, 0x95, 0x02,
// Input (Data, Variable, Absolute): 2x2 bytes (L2, R2) // Input (Data, Variable, Absolute): 2 bytes (L2, R2)
0x81, 0x02, 0x81, 0x02,
// Usage Page (Buttons) // Usage Page (Buttons)
@ -182,7 +182,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK * `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
* *
* +---------------+ * +---------------+
* byte 14: |0 0 0 0 . . . .| hat switch (dpad) position (0-8) * byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
* +---------------+ * +---------------+
* 9 possible positions and their values: * 9 possible positions and their values:
* 8 1 2 * 8 1 2
@ -191,19 +191,16 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
* (8 is top-left, 1 is top, 2 is top-right, etc.) * (8 is top-left, 1 is top, 2 is top-right, etc.)
*/ */
// [-32768 to 32767] -> [0 to 65535]
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
static void static void
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot, sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
uint32_t gamepad_id) { uint32_t gamepad_id) {
assert(gamepad_id != SC_GAMEPAD_ID_INVALID); assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
slot->gamepad_id = gamepad_id; slot->gamepad_id = gamepad_id;
slot->buttons = 0; slot->buttons = 0;
slot->axis_left_x = AXIS_RESCALE(0); slot->axis_left_x = 0;
slot->axis_left_y = AXIS_RESCALE(0); slot->axis_left_y = 0;
slot->axis_right_x = AXIS_RESCALE(0); slot->axis_right_x = 0;
slot->axis_right_y = AXIS_RESCALE(0); slot->axis_right_y = 0;
slot->axis_left_trigger = 0; slot->axis_left_trigger = 0;
slot->axis_right_trigger = 0; slot->axis_right_trigger = 0;
} }
@ -246,8 +243,14 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id); sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx); uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
hid_open->hid_id = hid_id; hid_open->hid_id = hid_id;
hid_open->name = name;
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC; hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC); hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
@ -420,6 +423,8 @@ sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx]; struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
// [-32768 to 32767] -> [0 to 65535]
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
switch (event->axis) { switch (event->axis) {
case SC_GAMEPAD_AXIS_LEFTX: case SC_GAMEPAD_AXIS_LEFTX:
slot->axis_left_x = AXIS_RESCALE(event->value); slot->axis_left_x = AXIS_RESCALE(event->value);

View File

@ -335,6 +335,7 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) { void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_KEYBOARD; hid_open->hid_id = SC_HID_ID_KEYBOARD;
hid_open->name = NULL; // No name specified after "scrcpy"
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC; hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC); hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
} }

View File

@ -190,6 +190,7 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) { void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_MOUSE; hid_open->hid_id = SC_HID_ID_MOUSE;
hid_open->name = NULL; // No name specified after "scrcpy"
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC; hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC); hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
} }

View File

@ -412,12 +412,18 @@ struct sc_touch_event {
float pressure; float pressure;
}; };
enum sc_gamepad_device_event_type {
SC_GAMEPAD_DEVICE_ADDED,
SC_GAMEPAD_DEVICE_REMOVED,
};
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>: // As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
// The ID value starts at 0 and increments from there. The value -1 is an // The ID value starts at 0 and increments from there. The value -1 is an
// invalid ID. // invalid ID.
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1) #define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
struct sc_gamepad_device_event { struct sc_gamepad_device_event {
enum sc_gamepad_device_event_type type;
uint32_t gamepad_id; uint32_t gamepad_id;
}; };
@ -497,6 +503,16 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
return buttons_state; return buttons_state;
} }
static inline enum sc_gamepad_device_event_type
sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
assert(type == SDL_CONTROLLERDEVICEADDED
|| type == SDL_CONTROLLERDEVICEREMOVED);
if (type == SDL_CONTROLLERDEVICEADDED) {
return SC_GAMEPAD_DEVICE_ADDED;
}
return SC_GAMEPAD_DEVICE_REMOVED;
}
static inline enum sc_gamepad_axis static inline enum sc_gamepad_axis
sc_gamepad_axis_from_sdl(uint8_t axis) { sc_gamepad_axis_from_sdl(uint8_t axis) {
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {

View File

@ -908,6 +908,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
static void static void
sc_input_manager_process_gamepad_device(struct sc_input_manager *im, sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
const SDL_ControllerDeviceEvent *event) { const SDL_ControllerDeviceEvent *event) {
SDL_JoystickID id;
if (event->type == SDL_CONTROLLERDEVICEADDED) { if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which); SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) { if (!gc) {
@ -922,12 +923,9 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
return; return;
} }
struct sc_gamepad_device_event evt = { id = SDL_JoystickInstanceID(joystick);
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
im->gp->ops->process_gamepad_added(im->gp, &evt);
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { } else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
SDL_JoystickID id = event->which; id = event->which;
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id); SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) { if (gc) {
@ -935,15 +933,16 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
} else { } else {
LOGW("Unknown gamepad device removed"); LOGW("Unknown gamepad device removed");
} }
struct sc_gamepad_device_event evt = {
.gamepad_id = id,
};
im->gp->ops->process_gamepad_removed(im->gp, &evt);
} else { } else {
// Nothing to do // Nothing to do
return; return;
} }
struct sc_gamepad_device_event evt = {
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
.gamepad_id = id,
};
im->gp->ops->process_gamepad_device(im->gp, &evt);
} }
static void static void

View File

@ -108,7 +108,6 @@ const struct scrcpy_options scrcpy_options_default = {
.new_display = NULL, .new_display = NULL,
.start_app = NULL, .start_app = NULL,
.angle = NULL, .angle = NULL,
.vd_destroy_content = true,
.vd_system_decorations = true, .vd_system_decorations = true,
}; };

View File

@ -310,7 +310,6 @@ struct scrcpy_options {
bool audio_dup; bool audio_dup;
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
const char *start_app; const char *start_app;
bool vd_destroy_content;
bool vd_system_decorations; bool vd_system_decorations;
}; };

View File

@ -458,7 +458,6 @@ scrcpy(struct scrcpy_options *options) {
.power_on = options->power_on, .power_on = options->power_on,
.kill_adb_on_close = options->kill_adb_on_close, .kill_adb_on_close = options->kill_adb_on_close,
.camera_high_speed = options->camera_high_speed, .camera_high_speed = options->camera_high_speed,
.vd_destroy_content = options->vd_destroy_content,
.vd_system_decorations = options->vd_system_decorations, .vd_system_decorations = options->vd_system_decorations,
.list = options->list, .list = options->list,
}; };

View File

@ -377,9 +377,6 @@ execute_server(struct sc_server *server,
VALIDATE_STRING(params->new_display); VALIDATE_STRING(params->new_display);
ADD_PARAM("new_display=%s", params->new_display); ADD_PARAM("new_display=%s", params->new_display);
} }
if (!params->vd_destroy_content) {
ADD_PARAM("vd_destroy_content=false");
}
if (!params->vd_system_decorations) { if (!params->vd_system_decorations) {
ADD_PARAM("vd_system_decorations=false"); ADD_PARAM("vd_system_decorations=false");
} }
@ -832,14 +829,11 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
} }
static bool static bool
sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port, sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
bool disconnect) {
struct sc_intr *intr = &server->intr; struct sc_intr *intr = &server->intr;
if (disconnect) {
// Error expected if not connected, do not report any error // Error expected if not connected, do not report any error
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT); sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
}
LOGI("Connecting to %s...", ip_port); LOGI("Connecting to %s...", ip_port);
@ -855,7 +849,7 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port,
static bool static bool
sc_server_configure_tcpip_known_address(struct sc_server *server, sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr, bool disconnect) { const char *addr) {
// Append ":5555" if no port is present // Append ":5555" if no port is present
bool contains_port = strchr(addr, ':'); bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) char *ip_port = contains_port ? strdup(addr)
@ -866,7 +860,7 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
} }
server->serial = ip_port; server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port, disconnect); return sc_server_connect_to_tcpip(server, ip_port);
} }
static bool static bool
@ -891,7 +885,7 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
} }
server->serial = ip_port; server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port, false); return sc_server_connect_to_tcpip(server, ip_port);
} }
static void static void
@ -978,13 +972,7 @@ run_server(void *data) {
sc_adb_device_destroy(&device); sc_adb_device_destroy(&device);
} }
} else { } else {
// If the user passed a '+' (--tcpip=+ip), then disconnect first ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
const char *tcpip_dst = params->tcpip_dst;
bool plus = tcpip_dst[0] == '+';
if (plus) {
++tcpip_dst;
}
ok = sc_server_configure_tcpip_known_address(server, tcpip_dst, plus);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }

View File

@ -69,7 +69,6 @@ struct sc_server_params {
bool power_on; bool power_on;
bool kill_adb_on_close; bool kill_adb_on_close;
bool camera_high_speed; bool camera_high_speed;
bool vd_destroy_content;
bool vd_system_decorations; bool vd_system_decorations;
uint8_t list; uint8_t list;
}; };

View File

@ -20,21 +20,12 @@ struct sc_gamepad_processor {
struct sc_gamepad_processor_ops { struct sc_gamepad_processor_ops {
/** /**
* Process a gamepad device added event * Process a gamepad device added or removed
* *
* This function is mandatory. * This function is mandatory.
*/ */
void void
(*process_gamepad_added)(struct sc_gamepad_processor *gp, (*process_gamepad_device)(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event);
/**
* Process a gamepad device removed event
*
* This function is mandatory.
*/
void
(*process_gamepad_removed)(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event); const struct sc_gamepad_device_event *event);
/** /**

View File

@ -7,11 +7,6 @@
/** Downcast gamepad processor to sc_gamepad_uhid */ /** Downcast gamepad processor to sc_gamepad_uhid */
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor) #define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
// Xbox 360
#define SC_GAMEPAD_UHID_VENDOR_ID UINT16_C(0x045e)
#define SC_GAMEPAD_UHID_PRODUCT_ID UINT16_C(0x028e)
#define SC_GAMEPAD_UHID_NAME "Microsoft X-Box 360 Pad"
static void static void
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad, sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
const struct sc_hid_input *hid_input, const struct sc_hid_input *hid_input,
@ -35,9 +30,7 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = hid_open->hid_id; msg.uhid_create.id = hid_open->hid_id;
msg.uhid_create.vendor_id = SC_GAMEPAD_UHID_VENDOR_ID; msg.uhid_create.name = hid_open->name;
msg.uhid_create.product_id = SC_GAMEPAD_UHID_PRODUCT_ID;
msg.uhid_create.name = SC_GAMEPAD_UHID_NAME;
msg.uhid_create.report_desc = hid_open->report_desc; msg.uhid_create.report_desc = hid_open->report_desc;
msg.uhid_create.report_desc_size = hid_open->report_desc_size; msg.uhid_create.report_desc_size = hid_open->report_desc_size;
@ -59,29 +52,20 @@ sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
} }
static void static void
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp, sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) { const struct sc_gamepad_device_event *event) {
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp); struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
struct sc_hid_open hid_open; struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open, if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) { event->gamepad_id)) {
return; return;
} }
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(event->gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
sc_gamepad_uhid_send_open(gamepad, &hid_open); sc_gamepad_uhid_send_open(gamepad, &hid_open);
} } else {
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
static void
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
struct sc_hid_close hid_close; struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close, if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
@ -89,10 +73,9 @@ sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
return; return;
} }
LOGI("Gamepad removed: [%" PRIu32 "]", event->gamepad_id);
sc_gamepad_uhid_send_close(gamepad, &hid_close); sc_gamepad_uhid_send_close(gamepad, &hid_close);
} }
}
static void static void
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp, sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
@ -131,8 +114,7 @@ sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
gamepad->controller = controller; gamepad->controller = controller;
static const struct sc_gamepad_processor_ops ops = { static const struct sc_gamepad_processor_ops ops = {
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added, .process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis, .process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button, .process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
}; };

View File

@ -141,9 +141,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_KEYBOARD; msg.uhid_create.id = SC_HID_ID_KEYBOARD;
msg.uhid_create.vendor_id = 0; msg.uhid_create.name = hid_open.name;
msg.uhid_create.product_id = 0;
msg.uhid_create.name = NULL;
msg.uhid_create.report_desc = hid_open.report_desc; msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size; msg.uhid_create.report_desc_size = hid_open.report_desc_size;
if (!sc_controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {

View File

@ -81,9 +81,7 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_MOUSE; msg.uhid_create.id = SC_HID_ID_MOUSE;
msg.uhid_create.vendor_id = 0; msg.uhid_create.name = hid_open.name;
msg.uhid_create.product_id = 0;
msg.uhid_create.name = NULL;
msg.uhid_create.report_desc = hid_open.report_desc; msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size; msg.uhid_create.report_desc_size = hid_open.report_desc_size;
if (!sc_controller_push_msg(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {

View File

@ -7,10 +7,11 @@
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor) #define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
static void static void
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp, sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) { const struct sc_gamepad_device_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp); struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
struct sc_hid_open hid_open; struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open, if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) { event->gamepad_id)) {
@ -21,12 +22,8 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) { if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
LOGW("Could not push AOA HID open (gamepad)"); LOGW("Could not push AOA HID open (gamepad)");
} }
} } else {
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
static void
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
struct sc_hid_close hid_close; struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close, if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
@ -38,6 +35,7 @@ sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
LOGW("Could not push AOA HID close (gamepad)"); LOGW("Could not push AOA HID close (gamepad)");
} }
} }
}
static void static void
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp, sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
@ -78,8 +76,7 @@ sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
sc_hid_gamepad_init(&gamepad->hid); sc_hid_gamepad_init(&gamepad->hid);
static const struct sc_gamepad_processor_ops ops = { static const struct sc_gamepad_processor_ops ops = {
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added, .process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis, .process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button, .process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
}; };

View File

@ -175,6 +175,7 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
assert(screen->gamepad); assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor; struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
SDL_JoystickID id;
if (event->type == SDL_CONTROLLERDEVICEADDED) { if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which); SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) { if (!gc) {
@ -189,12 +190,9 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
return; return;
} }
struct sc_gamepad_device_event evt = { id = SDL_JoystickInstanceID(joystick);
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
gp->ops->process_gamepad_added(gp, &evt);
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { } else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
SDL_JoystickID id = event->which; id = event->which;
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id); SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) { if (gc) {
@ -202,12 +200,16 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
} else { } else {
LOGW("Unknown gamepad device removed"); LOGW("Unknown gamepad device removed");
} }
} else {
// Nothing to do
return;
}
struct sc_gamepad_device_event evt = { struct sc_gamepad_device_event evt = {
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
.gamepad_id = id, .gamepad_id = id,
}; };
gp->ops->process_gamepad_removed(gp, &evt); gp->ops->process_gamepad_device(gp, &evt);
}
} }
static void static void

View File

@ -329,8 +329,6 @@ static void test_serialize_uhid_create(void) {
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE, .type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
.uhid_create = { .uhid_create = {
.id = 42, .id = 42,
.vendor_id = 0x1234,
.product_id = 0x5678,
.name = "ABC", .name = "ABC",
.report_desc_size = sizeof(report_desc), .report_desc_size = sizeof(report_desc),
.report_desc = report_desc, .report_desc = report_desc,
@ -339,13 +337,11 @@ static void test_serialize_uhid_create(void) {
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 24); assert(size == 20);
const uint8_t expected[] = { const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_CREATE,
0, 42, // id 0, 42, // id
0x12, 0x34, // vendor id
0x56, 0x78, // product id
3, // name size 3, // name size
65, 66, 67, // "ABC" 65, 66, 67, // "ABC"
0, 11, // report desc size 0, 11, // report desc size

View File

@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v3.1`][direct-scrcpy-server] - [`scrcpy-server-v3.0`][direct-scrcpy-server]
<sub>SHA-256: `958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0`</sub> <sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@ -85,12 +85,6 @@ scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555 scrcpy --tcpip=192.168.1.1:5555
``` ```
Prefix the address with a '+' to force a reconnection:
```bash
scrcpy --tcpip=+192.168.1.1
```
### Manual ### Manual
@ -113,17 +107,16 @@ with the device IP address you found)_.
7. Run `scrcpy` as usual. 7. Run `scrcpy` as usual.
8. Run `adb disconnect` once you're done. 8. Run `adb disconnect` once you're done.
Since Android 11, a [wireless debugging option][adb-wireless] allows you to Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
bypass having to physically connect your device to your computer. having to physically connect your device directly to your computer.
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line [adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
## Autostart ## Autostart
A small tool (by the scrcpy author) allows you to run arbitrary commands A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
whenever a new Android device is connected: [AutoAdb]. It can be used to start new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
scrcpy:
```bash ```bash
autoadb scrcpy -s '{}' autoadb scrcpy -s '{}'

View File

@ -34,31 +34,6 @@ adb shell settings put global stay_on_while_plugged_in 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.
## Turn screen off ## Turn screen off
It is possible to turn the device screen off while mirroring on start with a It is possible to turn the device screen off while mirroring on start with a
@ -96,6 +71,31 @@ 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

View File

@ -6,11 +6,11 @@
Download a static build of the [latest release]: Download a static build of the [latest release]:
- [`scrcpy-linux-x86_64-v3.1.tar.gz`][direct-linux-x86_64] (x86_64) - [`scrcpy-linux-v3.0.tar.gz`][direct-linux] (x86_64)
<sub>SHA-256: `37dba54092ed9ec6b2f8f95432f61b8ea124aec9f1e9f2b3d22d4b10bb04c59a`</sub> <sub>SHA-256: `06cb74e22f758228c944cea048b78e42b2925c2affe2b5aca901cfd6a649e503`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-linux-x86_64-v3.1.tar.gz [direct-linux]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-linux-v3.0.tar.gz
and extract it. and extract it.

View File

@ -6,15 +6,11 @@
Download a static build of the [latest release]: Download a static build of the [latest release]:
- [`scrcpy-macos-aarch64-v3.1.tar.gz`][direct-macos-aarch64] (aarch64) - [`scrcpy-macos-v3.0.tar.gz`][direct-macos] (arm64)
<sub>SHA-256: `478618d940421e5f57942f5479d493ecbb38210682937a200f712aee5f235daf`</sub> <sub>SHA-256: `5db9821918537eb3aaf0333cdd05baf85babdd851972d5f1b71f86da0530b4bf`</sub>
- [`scrcpy-macos-x86_64-v3.1.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `acde98e29c273710ffa469371dbca4a728a44c41c380381f8a54e5b5301b9e87`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-aarch64-v3.1.tar.gz [direct-macos]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-macos-v3.0.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-x86_64-v3.1.tar.gz
and extract it. and extract it.

View File

@ -83,9 +83,9 @@ process like the _adb daemon_).
## Mouse bindings ## Mouse bindings
By default, with SDK mouse: By default, with SDK mouse:
- right-click triggers `BACK` (or `POWER` on) - right-click triggers BACK (or POWER on)
- middle-click triggers `HOME` - middle-click triggers HOME
- the 4th click triggers `APP_SWITCH` - the 4th click triggers APP_SWITCH
- the 5th click expands the notification panel - the 5th click expands the notification panel
The secondary clicks may be forwarded to the device instead by pressing the The secondary clicks may be forwarded to the device instead by pressing the
@ -121,9 +121,9 @@ Each character must be one of the following:
- `+`: forward the click to the device - `+`: forward the click to the device
- `-`: ignore the click - `-`: ignore the click
- `b`: trigger shortcut `BACK` (or turn screen on if off) - `b`: trigger shortcut BACK (or turn screen on if off)
- `h`: trigger shortcut `HOME` - `h`: trigger shortcut HOME
- `s`: trigger shortcut `APP_SWITCH` - `s`: trigger shortcut APP_SWITCH
- `n`: trigger shortcut "expand notification panel" - `n`: trigger shortcut "expand notification panel"
For example: For example:

View File

@ -50,14 +50,3 @@ any default launcher UI available in virtual displays.
Note that if no app is started, no content will be rendered, so no video frame Note that if no app is started, no content will be rendered, so no video frame
will be produced at all. will be produced at all.
## Destroy on close
By default, when the virtual display is closed, the running apps are destroyed.
To move them to the main display instead, use:
```
scrcpy --new-display --no-vd-destroy-content
```

View File

@ -6,26 +6,20 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v3.1.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit)
<sub>SHA-256: `0c05ea395d95cfe36bee974eeb435a3db87ea5594ff738370d5dc3068a9538ca`</sub> <sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub>
- [`scrcpy-win32-v3.1.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit)
<sub>SHA-256: `2b4674ef76719680ac5a9b482d1943bdde3fa25821ad2e98f3c40c347d00d560`</sub> <sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win64-v3.1.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win64-v3.0.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win32-v3.1.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.zip
and extract it. and extract it.
### From a package manager ### From a package manager
From [WinGet] (ADB and other dependencies will be installed alongside scrcpy):
```bash
winget install --exact Genymobile.scrcpy
```
From [Chocolatey]: From [Chocolatey]:
```bash ```bash
@ -35,12 +29,12 @@ choco install adb # if you don't have it yet
From [Scoop]: From [Scoop]:
```bash ```bash
scoop install scrcpy scoop install scrcpy
scoop install adb # if you don't have it yet scoop install adb # if you don't have it yet
``` ```
[WinGet]: https://github.com/microsoft/winget-cli
[Chocolatey]: https://chocolatey.org/ [Chocolatey]: https://chocolatey.org/
[Scoop]: https://scoop.sh [Scoop]: https://scoop.sh

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
PREBUILT_SERVER_SHA256=958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0 PREBUILT_SERVER_SHA256=800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '3.1', version: '3.0',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -15,7 +15,6 @@ LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH"
app/deps/adb_linux.sh app/deps/adb_linux.sh
app/deps/sdl.sh linux native static app/deps/sdl.sh linux native static
app/deps/dav1d.sh linux native static
app/deps/ffmpeg.sh linux native static app/deps/ffmpeg.sh linux native static
app/deps/libusb.sh linux native static app/deps/libusb.sh linux native static

View File

@ -15,7 +15,6 @@ MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH"
app/deps/adb_macos.sh app/deps/adb_macos.sh
app/deps/sdl.sh macos native static app/deps/sdl.sh macos native static
app/deps/dav1d.sh macos native static
app/deps/ffmpeg.sh macos native static app/deps/ffmpeg.sh macos native static
app/deps/libusb.sh macos native static app/deps/libusb.sh macos native static

View File

@ -22,7 +22,6 @@ WINXX_BUILD_DIR="$WORK_DIR/build-$WINXX"
app/deps/adb_windows.sh app/deps/adb_windows.sh
app/deps/sdl.sh $WINXX cross shared app/deps/sdl.sh $WINXX cross shared
app/deps/dav1d.sh $WINXX cross shared
app/deps/ffmpeg.sh $WINXX cross shared app/deps/ffmpeg.sh $WINXX cross shared
app/deps/libusb.sh $WINXX cross shared app/deps/libusb.sh $WINXX cross shared

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 35 targetSdkVersion 35
versionCode 30100 versionCode 30000
versionName "3.1" versionName "3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.1 SCRCPY_VERSION_NAME=3.0
PLATFORM=${ANDROID_PLATFORM:-35} PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}

View File

@ -6,8 +6,6 @@ import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException; import com.genymobile.scrcpy.util.SettingsException;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.system.ErrnoException;
import android.system.Os;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -26,7 +24,6 @@ public final class CleanUp {
private boolean pendingRestoreDisplayPower; private boolean pendingRestoreDisplayPower;
private Thread thread; private Thread thread;
private boolean interrupted;
private CleanUp(Options options) { private CleanUp(Options options) {
thread = new Thread(() -> runCleanUp(options), "cleanup"); thread = new Thread(() -> runCleanUp(options), "cleanup");
@ -37,10 +34,8 @@ public final class CleanUp {
return new CleanUp(options); return new CleanUp(options);
} }
public synchronized void interrupt() { public void interrupt() {
// Do not use thread.interrupt() because only the wait() call must be interrupted, not Command.exec() thread.interrupt();
interrupted = true;
notify();
} }
public void join() throws InterruptedException { public void join() throws InterruptedException {
@ -102,13 +97,15 @@ public final class CleanUp {
try { try {
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout); run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
} catch (InterruptedException e) {
// ignore
} catch (IOException e) { } catch (IOException e) {
Ln.e("Clean up I/O exception", e); Ln.e("Clean up I/O exception", e);
} }
} }
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout) private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
throws IOException { throws IOException, InterruptedException {
String[] cmd = { String[] cmd = {
"app_process", "app_process",
"/", "/",
@ -129,15 +126,8 @@ public final class CleanUp {
int localPendingChanges; int localPendingChanges;
boolean localPendingRestoreDisplayPower; boolean localPendingRestoreDisplayPower;
synchronized (this) { synchronized (this) {
while (!interrupted && pendingChanges == 0) { while (pendingChanges == 0) {
try {
wait(); wait();
} catch (InterruptedException e) {
throw new AssertionError("Clean up thread MUST NOT be interrupted");
}
}
if (interrupted) {
break;
} }
localPendingChanges = pendingChanges; localPendingChanges = pendingChanges;
localPendingRestoreDisplayPower = pendingRestoreDisplayPower; localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
@ -165,12 +155,6 @@ public final class CleanUp {
} }
public static void main(String... args) { public static void main(String... args) {
try {
// Start a new session to avoid being terminated along with the server process on some devices
Os.setsid();
} catch (ErrnoException e) {
Ln.e("setsid() failed", e);
}
unlinkSelf(); unlinkSelf();
int displayId = Integer.parseInt(args[0]); int displayId = Integer.parseInt(args[0]);

View File

@ -60,7 +60,6 @@ public class Options {
private boolean powerOn = true; private boolean powerOn = true;
private NewDisplay newDisplay; private NewDisplay newDisplay;
private boolean vdDestroyContent = true;
private boolean vdSystemDecorations = true; private boolean vdSystemDecorations = true;
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked; private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
@ -234,10 +233,6 @@ public class Options {
return captureOrientationLock; return captureOrientationLock;
} }
public boolean getVDDestroyContent() {
return vdDestroyContent;
}
public boolean getVDSystemDecorations() { public boolean getVDSystemDecorations() {
return vdSystemDecorations; return vdSystemDecorations;
} }
@ -471,9 +466,6 @@ public class Options {
case "new_display": case "new_display":
options.newDisplay = parseNewDisplay(value); options.newDisplay = parseNewDisplay(value);
break; break;
case "vd_destroy_content":
options.vdDestroyContent = Boolean.parseBoolean(value);
break;
case "vd_system_decorations": case "vd_system_decorations":
options.vdSystemDecorations = Boolean.parseBoolean(value); options.vdSystemDecorations = Boolean.parseBoolean(value);
break; break;

View File

@ -51,8 +51,6 @@ public final class ControlMessage {
private int id; private int id;
private byte[] data; private byte[] data;
private boolean on; private boolean on;
private int vendorId;
private int productId;
private ControlMessage() { private ControlMessage() {
} }
@ -133,12 +131,10 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createUhidCreate(int id, int vendorId, int productId, String name, byte[] reportDesc) { public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_CREATE; msg.type = TYPE_UHID_CREATE;
msg.id = id; msg.id = id;
msg.vendorId = vendorId;
msg.productId = productId;
msg.text = name; msg.text = name;
msg.data = reportDesc; msg.data = reportDesc;
return msg; return msg;
@ -241,12 +237,4 @@ public final class ControlMessage {
public boolean getOn() { public boolean getOn() {
return on; return on;
} }
public int getVendorId() {
return vendorId;
}
public int getProductId() {
return productId;
}
} }

View File

@ -142,11 +142,9 @@ public class ControlMessageReader {
private ControlMessage parseUhidCreate() throws IOException { private ControlMessage parseUhidCreate() throws IOException {
int id = dis.readUnsignedShort(); int id = dis.readUnsignedShort();
int vendorId = dis.readUnsignedShort();
int productId = dis.readUnsignedShort();
String name = parseString(1); String name = parseString(1);
byte[] data = parseByteArray(2); byte[] data = parseByteArray(2);
return ControlMessage.createUhidCreate(id, vendorId, productId, name, data); return ControlMessage.createUhidCreate(id, name, data);
} }
private ControlMessage parseUhidInput() throws IOException { private ControlMessage parseUhidInput() throws IOException {

View File

@ -290,7 +290,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
Device.rotateDevice(getActionDisplayId()); Device.rotateDevice(getActionDisplayId());
break; break;
case ControlMessage.TYPE_UHID_CREATE: case ControlMessage.TYPE_UHID_CREATE:
getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData()); getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
break; break;
case ControlMessage.TYPE_UHID_INPUT: case ControlMessage.TYPE_UHID_INPUT:
getUhidManager().writeInput(msg.getId(), msg.getData()); getUhidManager().writeInput(msg.getId(), msg.getData());

View File

@ -48,7 +48,7 @@ public final class UhidManager {
} }
} }
public void open(int id, int vendorId, int productId, String name, byte[] reportDesc) throws IOException { public void open(int id, String name, byte[] reportDesc) throws IOException {
try { try {
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
try { try {
@ -58,7 +58,7 @@ public final class UhidManager {
close(old); close(old);
} }
byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc); byte[] req = buildUhidCreate2Req(name, reportDesc);
Os.write(fd, req, 0, req.length); Os.write(fd, req, 0, req.length);
registerUhidListener(id, fd); registerUhidListener(id, fd);
@ -148,7 +148,7 @@ public final class UhidManager {
} }
} }
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) { private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
/* /*
* struct uhid_event { * struct uhid_event {
* uint32_t type; * uint32_t type;
@ -174,7 +174,7 @@ public final class UhidManager {
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder()); ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_CREATE2); buf.putInt(UHID_CREATE2);
String actualName = name.isEmpty() ? "scrcpy" : name; String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8); byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127); int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
assert len <= 127; assert len <= 127;
@ -183,8 +183,8 @@ public final class UhidManager {
buf.putShort((short) reportDesc.length); buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL); buf.putShort(BUS_VIRTUAL);
buf.putInt(vendorId); buf.putInt(0); // vendor id
buf.putInt(productId); buf.putInt(0); // product id
buf.putInt(0); // version buf.putInt(0); // version
buf.putInt(0); // country; buf.putInt(0); // country;
buf.put(reportDesc); buf.put(reportDesc);

View File

@ -72,8 +72,4 @@ public final class IO {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE; return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
} }
public static boolean isBrokenPipe(Exception e) {
return e instanceof IOException && isBrokenPipe((IOException) e);
}
} }

View File

@ -53,7 +53,6 @@ public class NewDisplayCapture extends SurfaceCapture {
private final boolean captureOrientationLocked; private final boolean captureOrientationLocked;
private final Orientation captureOrientation; private final Orientation captureOrientation;
private final float angle; private final float angle;
private final boolean vdDestroyContent;
private final boolean vdSystemDecorations; private final boolean vdSystemDecorations;
private VirtualDisplay virtualDisplay; private VirtualDisplay virtualDisplay;
@ -74,7 +73,6 @@ public class NewDisplayCapture extends SurfaceCapture {
this.captureOrientation = options.getCaptureOrientation(); this.captureOrientation = options.getCaptureOrientation();
assert captureOrientation != null; assert captureOrientation != null;
this.angle = options.getAngle(); this.angle = options.getAngle();
this.vdDestroyContent = options.getVDDestroyContent();
this.vdSystemDecorations = options.getVDSystemDecorations(); this.vdSystemDecorations = options.getVDSystemDecorations();
} }
@ -169,10 +167,8 @@ public class NewDisplayCapture extends SurfaceCapture {
int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC
| VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | 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
if (vdDestroyContent) { | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
flags |= VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
}
if (vdSystemDecorations) { if (vdSystemDecorations) {
flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
} }

View File

@ -124,9 +124,15 @@ public class ScreenCapture extends SurfaceCapture {
inputSize = videoSize; inputSize = videoSize;
} }
int virtualDisplayId;
PositionMapper positionMapper;
try { try {
virtualDisplay = ServiceManager.getDisplayManager() virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface); .createVirtualDisplay("scrcpy", inputSize.getWidth(), inputSize.getHeight(), displayId, surface);
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
// The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!)
positionMapper = PositionMapper.create(videoSize, transform, inputSize);
Ln.d("Display: using DisplayManager API"); Ln.d("Display: using DisplayManager API");
} catch (Exception displayManagerException) { } catch (Exception displayManagerException) {
try { try {
@ -134,7 +140,11 @@ public class ScreenCapture extends SurfaceCapture {
Size deviceSize = displayInfo.getSize(); Size deviceSize = displayInfo.getSize();
int layerStack = displayInfo.getLayerStack(); int layerStack = displayInfo.getLayerStack();
setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack); setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack);
virtualDisplayId = displayId;
positionMapper = PositionMapper.create(videoSize, transform, deviceSize);
Ln.d("Display: using SurfaceControl API"); Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) { } catch (Exception surfaceControlException) {
Ln.e("Could not create display using DisplayManager", displayManagerException); Ln.e("Could not create display using DisplayManager", displayManagerException);
@ -144,18 +154,6 @@ public class ScreenCapture extends SurfaceCapture {
} }
if (vdListener != null) { if (vdListener != null) {
int virtualDisplayId;
PositionMapper positionMapper;
if (virtualDisplay == null || displayId == 0) {
// Surface control or main display: send all events to the original display, relative to the device size
Size deviceSize = displayInfo.getSize();
positionMapper = PositionMapper.create(videoSize, transform, deviceSize);
virtualDisplayId = displayId;
} else {
// The positions are relative to the virtual display, not the original display (so use inputSize, not deviceSize!)
positionMapper = PositionMapper.create(videoSize, transform, inputSize);
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
}
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper); vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
} }
} }

View File

@ -113,10 +113,6 @@ public class SurfaceEncoder implements AsyncProcessor {
alive = !stopped.get() && !capture.isClosed(); alive = !stopped.get() && !capture.isClosed();
} }
} catch (IllegalStateException | IllegalArgumentException | IOException e) { } catch (IllegalStateException | IllegalArgumentException | IOException e) {
if (IO.isBrokenPipe(e)) {
// Do not retry on broken pipe, which is expected on close because the socket is closed by the client
throw e;
}
Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage()); Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) { if (!prepareRetry(size)) {
throw e; throw e;

View File

@ -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.os.IBinder; import android.os.IBinder;
import android.system.Os;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -22,9 +21,7 @@ public final class DisplayControl {
Class<?> classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory"); Class<?> classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory");
Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class, Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class,
ClassLoader.class, int.class, boolean.class, String.class); ClassLoader.class, int.class, boolean.class, String.class);
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null,
String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, systemServerClasspath, null, null,
ClassLoader.getSystemClassLoader(), 0, true, null); ClassLoader.getSystemClassLoader(), 0, true, null);
displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl"); displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl");

View File

@ -322,8 +322,6 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_CREATE); dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
dos.writeShort(42); // id dos.writeShort(42); // id
dos.writeShort(0x1234); // vendorId
dos.writeShort(0x5678); // productId
dos.writeByte(3); // name size dos.writeByte(3); // name size
dos.write("ABC".getBytes(StandardCharsets.US_ASCII)); dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
@ -337,8 +335,6 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.read(); ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
Assert.assertEquals(42, event.getId()); Assert.assertEquals(42, event.getId());
Assert.assertEquals(0x1234, event.getVendorId());
Assert.assertEquals(0x5678, event.getProductId());
Assert.assertEquals("ABC", event.getText()); Assert.assertEquals("ABC", event.getText());
Assert.assertArrayEquals(data, event.getData()); Assert.assertArrayEquals(data, event.getData());