Compare commits

..

8 Commits

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
24 changed files with 125 additions and 56 deletions

View File

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v3.0.1)
# scrcpy (v3.0.2)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

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

View File

@ -63,6 +63,7 @@ arguments=(
'--no-mipmaps[Disable the generation of mipmaps]'
'--no-mouse-hover[Do not forward mouse hover events]'
'--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-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'

View File

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

View File

@ -369,6 +369,12 @@ Do not forward mouse hover (mouse motion without any clicks) events.
.B \-\-no\-power\-on
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
.B \-\-no\-vd\-system\-decorations
Disable virtual display system decorations flag.

View File

@ -110,6 +110,7 @@ enum {
OPT_CAPTURE_ORIENTATION,
OPT_ANGLE,
OPT_NO_VD_SYSTEM_DECORATIONS,
OPT_NO_VD_DESTROY_CONTENT,
};
struct sc_option {
@ -659,6 +660,15 @@ static const struct sc_option options[] = {
.longopt = "no-power-on",
.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 = "no-vd-system-decorations",
@ -2705,8 +2715,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_ANGLE:
opts->angle = optarg;
break;
case OPT_NO_VD_DESTROY_CONTENT:
opts->vd_destroy_content = false;
break;
case OPT_NO_VD_SYSTEM_DECORATIONS:
opts->vd_system_decorations = optarg;
opts->vd_system_decorations = false;
break;
default:
// getopt prints the error message on stderr

View File

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

View File

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

View File

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

View File

@ -377,6 +377,9 @@ execute_server(struct sc_server *server,
VALIDATE_STRING(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) {
ADD_PARAM("vd_system_decorations=false");
}

View File

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

View File

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

View File

@ -6,11 +6,11 @@
Download a static build of the [latest release]:
- [`scrcpy-linux-x86_64-v3.0.1.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `6cb7fb16efbe3afd6db19b1ee31ee9f6e104a4735dc1f41c4a478cabbeac3f77`</sub>
- [`scrcpy-linux-x86_64-v3.0.2.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `20b69dcd379bb7d7208bf1e4858cf04162fc856697be0e6c03863d7b3c1e734a`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-linux-x86_64-v3.0.1.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.

View File

@ -6,15 +6,15 @@
Download a static build of the [latest release]:
- [`scrcpy-macos-aarch64-v3.0.1.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `e1af70898b6881b3e714ee0e15a7664bfab5eda3ea27c101163a09a36e1df753`</sub>
- [`scrcpy-macos-aarch64-v3.0.2.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `811ba2f4e856146bdd161e24c3490d78efbec2339ca783fac791d041c0aecfb6`</sub>
- [`scrcpy-macos-x86_64-v3.0.1.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `d6f9fad290e0142a6dfb0a405a8d1bfbe1698bbb146c1c0c33e38da53762e442`</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
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-macos-aarch64-v3.0.1.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-macos-x86_64-v3.0.1.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.

View File

@ -50,3 +50,14 @@ any default launcher UI available in virtual displays.
Note that if no app is started, no content will be rendered, so no video frame
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]:
- [`scrcpy-win64-v3.0.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `2d2485cead6bb9d80ec337a660a571fc4b3c2e15034ad73c6a2867442206a5f4`</sub>
- [`scrcpy-win32-v3.0.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `43296f8bf34dd408c65463d45ca367febe68cec3ae34b78917a8f3ecbf321829`</sub>
- [`scrcpy-win64-v3.0.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f0de59f5d46127c87cd822d39d6665e016b86db4cd048101b262f6adb6766832`</sub>
- [`scrcpy-win32-v3.0.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `8db8d4984d642012c55802de71f507f8ff9f68a8cfed456d7a1982d47e065f64`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-win64-v3.0.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-win32-v3.0.1.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.2/scrcpy-win32-v3.0.2.zip
and extract it.

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0.1/scrcpy-server-v3.0.1
PREBUILT_SERVER_SHA256=86c4ef31f5acb060a24d5d63d8dd262ef83384e19ae5f9ad78e6408a50743d17
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
PREBUILT_SERVER_SHA256=e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

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

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.0
SCRCPY_VERSION_NAME=3.0.2
PLATFORM=${ANDROID_PLATFORM:-35}
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.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Handle the cleanup of scrcpy, even if the main process is killed.
@ -24,6 +26,7 @@ public final class CleanUp {
private boolean pendingRestoreDisplayPower;
private Thread thread;
private boolean interrupted;
private CleanUp(Options options) {
thread = new Thread(() -> runCleanUp(options), "cleanup");
@ -34,8 +37,10 @@ public final class CleanUp {
return new CleanUp(options);
}
public void interrupt() {
thread.interrupt();
public synchronized void 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 {
@ -97,25 +102,29 @@ public final class CleanUp {
try {
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
} catch (InterruptedException e) {
// ignore
} catch (IOException e) {
Ln.e("Clean up I/O exception", e);
}
}
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
throws IOException, InterruptedException {
String[] cmd = {
"app_process",
"/",
CleanUp.class.getName(),
String.valueOf(displayId),
String.valueOf(restoreStayOn),
String.valueOf(disableShowTouches),
String.valueOf(powerOffScreen),
String.valueOf(restoreScreenOffTimeout),
};
throws IOException {
List<String> cmd = new ArrayList<>();
if (new File("/system/bin/setsid").exists()) {
cmd.add("/system/bin/setsid");
} else if (new File("/system/bin/nohup").exists()) {
cmd.add("/system/bin/nohup");
}
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);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
@ -126,8 +135,15 @@ public final class CleanUp {
int localPendingChanges;
boolean localPendingRestoreDisplayPower;
synchronized (this) {
while (pendingChanges == 0) {
wait();
while (!interrupted && pendingChanges == 0) {
try {
wait();
} catch (InterruptedException e) {
throw new AssertionError("Clean up thread MUST NOT be interrupted");
}
}
if (interrupted) {
break;
}
localPendingChanges = pendingChanges;
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;

View File

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

View File

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

View File

@ -124,15 +124,9 @@ public class ScreenCapture extends SurfaceCapture {
inputSize = videoSize;
}
int virtualDisplayId;
PositionMapper positionMapper;
try {
virtualDisplay = ServiceManager.getDisplayManager()
.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");
} catch (Exception displayManagerException) {
try {
@ -140,11 +134,7 @@ public class ScreenCapture extends SurfaceCapture {
Size deviceSize = displayInfo.getSize();
int layerStack = displayInfo.getLayerStack();
setDisplaySurface(display, surface, deviceSize.toRect(), inputSize.toRect(), layerStack);
virtualDisplayId = displayId;
positionMapper = PositionMapper.create(videoSize, transform, deviceSize);
Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) {
Ln.e("Could not create display using DisplayManager", displayManagerException);
@ -154,6 +144,18 @@ public class ScreenCapture extends SurfaceCapture {
}
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);
}
}