Compare commits

...

12 Commits
tmp ... cleanup

Author SHA1 Message Date
c59a3c3169 Start cleanup process with setsid or nohup
If available, start the cleanup process in a new session to reduce the
likelihood of it being terminated along with the scrcpy server process
on some devices.

The binaries setsid and nohup are often available, but it is not
guaranteed.

Refs #5601 <https://github.com/Genymobile/scrcpy/issues/5601>
PR #5613 <https://github.com/Genymobile/scrcpy/pull/5613>
2024-12-08 10:58:22 +01:00
2780e0bd7b Do not interrupt cleanup configuration
Some options, such as --show-touches or --stay-awake, modify Android
settings and must be restored upon exit.

If scrcpy terminates (e.g. due to an early error) in the middle of the
clean up configuration, the device may be left in an inconsistent state
(some settings might be changed but not restored).

This issue can be reproduced with high probability by forcing scrcpy to
fail:

    scrcpy --show-touches --video-encoder=fail

To prevent this problem, ensure that the clean up thread is not
interrupted until the clean up process is started.

Refs #5601 <https://github.com/Genymobile/scrcpy/issues/5601>
PR #5613 <https://github.com/Genymobile/scrcpy/pull/5613>
2024-12-08 10:58:07 +01:00
6c6607d404 Add --no-vd-destroy-content
Add an option to disable the following flag for virtual displays:

    DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL

With this option, when the virtual display is closed, the running apps
are moved to the main display rather than being destroyed.

PR #5615 <https://github.com/Genymobile/scrcpy/pull/5615>
2024-12-08 09:33:03 +01:00
988174805c Fix boolean assignment
On --no-vd-system-decoration, the boolean option must be set to false.

It was wrongly assigned from optarg (this worked because optarg is NULL
at this point, so it was converted to false).

PR #5615 <https://github.com/Genymobile/scrcpy/pull/5615>
2024-12-08 09:26:53 +01:00
f90dc216d1 Refactor virtual display properties initialization
Following the changes from the previous commit, the behavior is now
identical when mirroring the main display or using the SurfaceControl
API.

Factorize the code to perform the initialization in a single location.

Refs #5605 <https://github.com/Genymobile/scrcpy/issues/5605>
PR #5614 <https://github.com/Genymobile/scrcpy/pull/5614>
2024-12-07 20:09:14 +01:00
97fa77c76c Inject main display events to the original display
When mirroring a secondary display, touch and scroll events must be sent
to the mirroring virtual display id (with coordinates relative to the
virtual display size), rather than to the original display (with
coordinates relative to the original display size).

This behavior, introduced by d19396718e,
was also applied for the main display for consistency. However, it
causes some UI elements to become unclickable.

To minimize inconveniences, restore the previous behavior when mirroring
the main display: send all events to the original display id (0) with
coordinates relative to the original display size.

Fixes #5545 <https://github.com/Genymobile/scrcpy/issues/5545>
Fixes #5605 <https://github.com/Genymobile/scrcpy/issues/5605>
Fixes #5616 <https://github.com/Genymobile/scrcpy/issues/5616>
Refs #4598 <https://github.com/Genymobile/scrcpy/issues/4598>
Refs #5137 <https://github.com/Genymobile/scrcpy/issues/5137>
Refs #5370 <https://github.com/Genymobile/scrcpy/pull/5370>
PR #5614 <https://github.com/Genymobile/scrcpy/pull/5614>
2024-12-07 20:08:49 +01:00
baa10ed0a3 Update links to 3.0.2 2024-12-04 22:48:27 +01:00
2ed2247e8f Bump version to 3.0.2
The version was not bumped for 3.0.1.
2024-12-04 22:35:25 +01:00
5febb1e9fb Update links to 3.0.1 2024-12-04 21:46:06 +01:00
5c3626ed47 Handle broken pipe errors specifically
Since 9555d3a537, a capture/encoding error
was sometimes logged on exit.
2024-12-04 18:38:23 +01:00
0e473eb005 Reset TCP/IP connection with a '+' prefix
When running scrcpy with --tcpip=xx.xx.xx.xx, to make sure a new working
connection is established, it was first disconnected by a call to:

    adb disconnect <addr>

However, this caused all running instances connected to that address to
be killed. Running several instances of scrcpy on the same device is now
useful with virtual displays, so change the default behavior to NOT
disconnect.

To force a reconnection, a '+' prefix can be added:

    scrcpy --tcpip=+192.168.0.x

Fixes #5562 <https://github.com/Genymobile/scrcpy/issues/5562>
2024-12-04 13:16:51 +01:00
b26b4fb745 Document launchers in virtual displays
Mention how to start a launcher in a virtual display.

Refs #5592 <https://github.com/Genymobile/scrcpy/issues/5592>
2024-12-04 13:10:35 +01:00
28 changed files with 189 additions and 72 deletions

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.0) # scrcpy (v3.0.2)
<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" />

View File

@ -57,6 +57,7 @@ _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,6 +63,7 @@ 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

@ -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.0" VALUE "ProductVersion", "3.0.2"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -369,6 +369,12 @@ 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.
@ -518,13 +524,15 @@ 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 reconnect the device over TCP/IP. Configure and connect 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.

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". // "connected" or "already 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,7 +429,8 @@ 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,6 +110,7 @@ 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 {
@ -659,6 +660,15 @@ 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",
@ -860,16 +870,17 @@ 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 reconnect the device over TCP/IP.\n" .text = "Configure and connect 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.", "this address before starting.\n"
"Prefix the address with a '+' to force a reconnection.",
}, },
{ {
.longopt_id = OPT_TIME_LIMIT, .longopt_id = OPT_TIME_LIMIT,
@ -2704,8 +2715,11 @@ 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 = optarg; opts->vd_system_decorations = false;
break; break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr

View File

@ -108,6 +108,7 @@ 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,6 +310,7 @@ 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,6 +458,7 @@ 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,6 +377,9 @@ 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");
} }
@ -829,11 +832,14 @@ 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;
// Error expected if not connected, do not report any error if (disconnect) {
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT); // Error expected if not connected, do not report any error
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
}
LOGI("Connecting to %s...", ip_port); LOGI("Connecting to %s...", ip_port);
@ -849,7 +855,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) { const char *addr, bool disconnect) {
// 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)
@ -860,7 +866,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); return sc_server_connect_to_tcpip(server, ip_port, disconnect);
} }
static bool static bool
@ -885,7 +891,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); return sc_server_connect_to_tcpip(server, ip_port, false);
} }
static void static void
@ -972,7 +978,13 @@ run_server(void *data) {
sc_adb_device_destroy(&device); sc_adb_device_destroy(&device);
} }
} else { } else {
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst); // If the user passed a '+' (--tcpip=+ip), then disconnect first
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,6 +69,7 @@ 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

@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v3.0`][direct-scrcpy-server] - [`scrcpy-server-v3.0.2`][direct-scrcpy-server]
<sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub> <sub>SHA-256: `e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
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,6 +85,12 @@ 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

View File

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

View File

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

View File

@ -15,8 +15,10 @@ scrcpy --new-display=/240 # use the main display size and 240 dpi
On some devices, a launcher is available in the virtual display. On some devices, a launcher is available in the virtual display.
When no launcher is available, the virtual display is empty. In that case, you When no launcher is available (or if is explicitly disabled by
must [start an Android app](device.md#start-android-app). [`--no-vd-system-decorations`](#system-decorations)), the virtual display is
empty. In that case, you must [start an Android
app](device.md#start-android-app).
For example: For example:
@ -24,12 +26,38 @@ For example:
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc
``` ```
The app may itself be a launcher. For example, to run the open source [Fossify
Launcher]:
```bash
scrcpy --new-display=1920x1080 --no-vd-system-decorations --start-app=org.fossify.home
```
[Fossify Launcher]: https://f-droid.org/en/packages/org.fossify.home/
## System decorations ## System decorations
By default, virtual display system decorations are enabled. But some devices By default, virtual display system decorations are enabled. To disable them, use
might display a broken UI; `--no-vd-system-decorations`:
Use `--no-vd-system-decorations` to disable it. ```
scrcpy --new-display --no-vd-system-decorations
```
This is useful for some devices which might display a broken UI, or to disable
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,14 +6,14 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v3.0.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub> <sub>SHA-256: `f0de59f5d46127c87cd822d39d6665e016b86db4cd048101b262f6adb6766832`</sub>
- [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v3.0.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub> <sub>SHA-256: `8db8d4984d642012c55802de71f507f8ff9f68a8cfed456d7a1982d47e065f64`</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.0/scrcpy-win64-v3.0.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win64-v3.0.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win32-v3.0.2.zip
and extract it. and extract it.

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.0/scrcpy-server-v3.0 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
PREBUILT_SERVER_SHA256=800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea PREBUILT_SERVER_SHA256=e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d
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.0', version: '3.0.2',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 35 targetSdkVersion 35
versionCode 30000 versionCode 30002
versionName "3.0" versionName "3.0.2"
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.0 SCRCPY_VERSION_NAME=3.0.2
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

@ -10,6 +10,8 @@ import android.os.BatteryManager;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/** /**
* Handle the cleanup of scrcpy, even if the main process is killed. * Handle the cleanup of scrcpy, even if the main process is killed.
@ -24,6 +26,7 @@ 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");
@ -34,8 +37,10 @@ public final class CleanUp {
return new CleanUp(options); return new CleanUp(options);
} }
public void interrupt() { public synchronized void interrupt() {
thread.interrupt(); // Do not use thread.interrupt() because only the wait() call must be interrupted, not Command.exec()
interrupted = true;
notify();
} }
public void join() throws InterruptedException { public void join() throws InterruptedException {
@ -97,25 +102,29 @@ 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, InterruptedException { throws IOException {
String[] cmd = {
"app_process", List<String> cmd = new ArrayList<>();
"/", if (new File("/system/bin/setsid").exists()) {
CleanUp.class.getName(), cmd.add("/system/bin/setsid");
String.valueOf(displayId), } else if (new File("/system/bin/nohup").exists()) {
String.valueOf(restoreStayOn), cmd.add("/system/bin/nohup");
String.valueOf(disableShowTouches), }
String.valueOf(powerOffScreen),
String.valueOf(restoreScreenOffTimeout), cmd.add("app_process");
}; cmd.add("/");
cmd.add(CleanUp.class.getName());
cmd.add(String.valueOf(displayId));
cmd.add(String.valueOf(restoreStayOn));
cmd.add(String.valueOf(disableShowTouches));
cmd.add(String.valueOf(powerOffScreen));
cmd.add(String.valueOf(restoreScreenOffTimeout));
ProcessBuilder builder = new ProcessBuilder(cmd); ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH); builder.environment().put("CLASSPATH", Server.SERVER_PATH);
@ -126,8 +135,15 @@ public final class CleanUp {
int localPendingChanges; int localPendingChanges;
boolean localPendingRestoreDisplayPower; boolean localPendingRestoreDisplayPower;
synchronized (this) { synchronized (this) {
while (pendingChanges == 0) { while (!interrupted && pendingChanges == 0) {
wait(); try {
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;

View File

@ -60,6 +60,7 @@ 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;
@ -233,6 +234,10 @@ public class Options {
return captureOrientationLock; return captureOrientationLock;
} }
public boolean getVDDestroyContent() {
return vdDestroyContent;
}
public boolean getVDSystemDecorations() { public boolean getVDSystemDecorations() {
return vdSystemDecorations; return vdSystemDecorations;
} }
@ -466,6 +471,9 @@ 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

@ -72,4 +72,8 @@ 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,6 +53,7 @@ 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;
@ -73,6 +74,7 @@ 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();
} }
@ -167,8 +169,10 @@ 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;
| VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; if (vdDestroyContent) {
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,15 +124,9 @@ 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 {
@ -140,11 +134,7 @@ 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);
@ -154,6 +144,18 @@ 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,6 +113,10 @@ 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;