From db9dc6ae836193dcd7883d001fdddbf54cbe9859 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 1 Apr 2025 11:04:34 +0200 Subject: [PATCH 01/83] Make the snap version as obsolete The version of scrcpy packaged in snap is currently 1.25. Refs --- doc/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/linux.md b/doc/linux.md index 52345d1a..979ef568 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -27,7 +27,7 @@ Scrcpy is packaged in several distributions and package managers: - Arch Linux: `pacman -S scrcpy` - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` - Gentoo: `emerge scrcpy` - - Snap: `snap install scrcpy` + - Snap: ~~`snap install scrcpy`~~ _(obsolete version)_ - … (see [repology](https://repology.org/project/scrcpy/versions)) From 882003f314ad5077a41bbc936831aeb36dd8b078 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 3 Apr 2025 08:04:11 +0200 Subject: [PATCH 02/83] Fix segfault on SDL event without window Since #5804, controls have been enabled even with --no-window. As a result, the Android clipboard is synchronized with the computer, causing SDL to trigger an SDL_CLIPBOARDUPDATE event. This event is ignored by scrcpy, but it was still transmitted to the sc_screen instance, even if it had not been initialized. Fix the issue by calling sc_screen_handle_event() only when a screen instance exists. Refs #5804 Fixes #5970 --- app/src/scrcpy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b3ff9b36..4d08e667 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -165,7 +165,7 @@ sdl_configure(bool video_playback, bool disable_screensaver) { } static enum scrcpy_exit_code -event_loop(struct scrcpy *s) { +event_loop(struct scrcpy *s, bool has_screen) { SDL_Event event; while (SDL_WaitEvent(&event)) { switch (event.type) { @@ -197,7 +197,7 @@ event_loop(struct scrcpy *s) { break; } default: - if (!sc_screen_handle_event(&s->screen, &event)) { + if (has_screen && !sc_screen_handle_event(&s->screen, &event)) { return SCRCPY_EXIT_FAILURE; } break; @@ -933,7 +933,7 @@ aoa_complete: } } - ret = event_loop(s); + ret = event_loop(s, options->window); terminate_event_loop(); LOGD("quit..."); From 5900e9e39c7496bf8dfc1f246c6bbf0a1e072a69 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 7 Apr 2025 10:30:56 +0200 Subject: [PATCH 03/83] Remove irrelevant link in FAQ --- FAQ.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FAQ.md b/FAQ.md index 5f089cd7..24722c74 100644 --- a/FAQ.md +++ b/FAQ.md @@ -166,14 +166,13 @@ Rebooting the device is necessary once this option is set. ### Special characters do not work -The default text injection method is [limited to ASCII characters][text-input]. -A trick allows to also inject some [accented characters][accented-characters], +The default text injection method is limited to ASCII characters. A trick allows +to also inject some [accented characters][accented-characters], but that's all. See [#37]. To avoid the problem, [change the keyboard mode to simulate a physical keyboard][hid]. -[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters [#37]: https://github.com/Genymobile/scrcpy/issues/37 [hid]: doc/keyboard.md#physical-keyboard-simulation From d2447b5c1982b8c91fbce8f515aeedac3d2ecb33 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Apr 2025 18:05:08 +0200 Subject: [PATCH 04/83] Fix --screen-off-timeout bash completion Only the option must be auto-completed, not its value. --- app/data/bash-completion/scrcpy | 1 + 1 file changed, 1 insertion(+) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 9918918c..a49da8ca 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -205,6 +205,7 @@ _scrcpy() { |-p|--port \ |--push-target \ |--rotation \ + |--screen-off-timeout \ |--tunnel-host \ |--tunnel-port \ |--v4l2-buffer \ From 1a0d300786827974b5a593959c1c21f89469777e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 14 Apr 2025 18:07:37 +0200 Subject: [PATCH 05/83] Add missing --screen-off-timeout doc in manpage Refs eff5b4b219be6043a3baf51149b1d6752569a173 --- app/scrcpy.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d481ddd1..d72fda13 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -510,6 +510,10 @@ The device serial number. Mandatory only if several devices are connected to adb .B \-S, \-\-turn\-screen\-off Turn the device screen off immediately. +.TP +.B "\-\-screen\-off\-timeout " seconds +Set the screen off timeout while scrcpy is running (restore the initial value on exit). + .TP .BI "\-\-shortcut\-mod " key\fR[+...]][,...] Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". From c5ed2cfc28ee7c7b59b11eb4db1258ac1c633bff Mon Sep 17 00:00:00 2001 From: Nicholas Wilson Date: Fri, 18 Apr 2025 09:54:59 -0500 Subject: [PATCH 06/83] Replace "licence" with "license" in README Although "licence" is correct in British English, the rest of the statement uses "license," so change it for consistency. PR #6017 Signed-off-by: Romain Vimont --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3b0d834..c1fd9f7f 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ work][donate]: [donate]: https://blog.rom1v.com/about/#support-my-open-source-work -## Licence +## License Copyright (C) 2018 Genymobile Copyright (C) 2018-2025 Romain Vimont From 6875e9aa88833525b60322597304b01f6ba91987 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Apr 2025 16:05:13 +0200 Subject: [PATCH 07/83] Revert "Fix AudioRecord package name for Android 16" This reverts commit c27d116a662c87ee84963820669ee0d2ce60e6f1. This commit breaks audio on Android 16 beta 4. Refs #5960 comment Fixes #6021 --- server/src/main/java/com/genymobile/scrcpy/FakeContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 22fc6d49..2b83e397 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -72,7 +72,7 @@ public final class FakeContext extends ContextWrapper { @Override public AttributionSource getAttributionSource() { AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID); - builder.setPackageName("shell"); + builder.setPackageName(PACKAGE_NAME); return builder.build(); } From 48f38c4bb6d91e378a657082e0da7eb846d25acc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 24 Apr 2025 16:12:28 +0200 Subject: [PATCH 08/83] Fix default locked capture orientation The default landscape locked orientation was reversed. Fixes #6010 --- .../java/com/genymobile/scrcpy/device/Orientation.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java index c269750e..81168aae 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java @@ -32,9 +32,11 @@ public enum Orientation { throw new IllegalArgumentException("Unknown orientation: " + name); } - public static Orientation fromRotation(int rotation) { - assert rotation >= 0 && rotation < 4; - return values()[rotation]; + public static Orientation fromRotation(int ccwRotation) { + assert ccwRotation >= 0 && ccwRotation < 4; + // Display rotation is expressed counter-clockwise, orientation is expressed clockwise + int cwRotation = (4 - ccwRotation) % 4; + return values()[cwRotation]; } public boolean isFlipped() { From 91a4a74641903bedff189548cc5c33289752b4b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 25 Apr 2025 10:23:08 +0200 Subject: [PATCH 09/83] Move regex pattern initialization If text == null, then the Pattern is not used. --- .../java/com/genymobile/scrcpy/wrappers/DisplayManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index d44ac608..130f86c6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -95,12 +95,12 @@ public final class DisplayManager { } private static int parseDisplayFlags(String text) { - Pattern regex = Pattern.compile("FLAG_[A-Z_]+"); if (text == null) { return 0; } int flags = 0; + Pattern regex = Pattern.compile("FLAG_[A-Z_]+"); Matcher m = regex.matcher(text); while (m.find()) { String flagString = m.group(); From cc309a2b34da13bbc15fbb64a6bba33ff8c79ce1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 May 2025 11:37:47 +0200 Subject: [PATCH 10/83] Build static linux binary on Ubuntu 22.04 Ubuntu 20.04 is no longer available on GitHub Actions. Refs Refs #6050 This reverts commit 69858c6f437b1bfece96bc291c607de842837d36. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5875c6bf..49402a6e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: run: release/test_client.sh build-linux-x86_64: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check architecture run: | From 8cd63cb63eeb4873b304a44fb66d55db03f2dd36 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 9 May 2025 18:22:40 +0200 Subject: [PATCH 11/83] Report specific error for INJECT_EVENT permission Some devices require a specific option to be enabled in Developer Options to avoid a permission issue when injecting input events. When this error occurs, hide the stack trace and print a human-readable message explaining how to fix the issue. PR #6080 --- README.md | 2 +- .../scrcpy/wrappers/InputManager.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3b0d834..36f978f9 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s). On some devices (especially Xiaomi), you might get the following error: ``` -java.lang.SecurityException: Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission. +Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission. ``` In that case, you need to enable [an additional option][control] `USB debugging diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 5c5ba56c..f9f8e3ac 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -6,6 +6,7 @@ import android.annotation.SuppressLint; import android.view.InputEvent; import android.view.MotionEvent; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -17,6 +18,7 @@ public final class InputManager { private final Object manager; private Method injectInputEventMethod; + private long lastPermissionLogDate; private static Method setDisplayIdMethod; private static Method setActionButtonMethod; @@ -57,6 +59,23 @@ public final class InputManager { Method method = getInjectInputEventMethod(); return (boolean) method.invoke(manager, inputEvent, mode); } catch (ReflectiveOperationException e) { + if (e instanceof InvocationTargetException) { + Throwable cause = e.getCause(); + if (cause instanceof SecurityException) { + String message = e.getCause().getMessage(); + if (message != null && message.contains("INJECT_EVENTS permission")) { + // Do not flood the console, limit to one permission error log every 3 seconds + long now = System.currentTimeMillis(); + if (lastPermissionLogDate <= now - 3000) { + Ln.e(message); + Ln.e("Make sure you have enabled \"USB debugging (Security Settings)\" and then rebooted your device."); + lastPermissionLogDate = now; + } + // Do not print the stack trace + return false; + } + } + } Ln.e("Could not invoke method", e); return false; } From 38f779d9d37833e617a577b5a9f3bc91d0358174 Mon Sep 17 00:00:00 2001 From: hltdev8642 <39349712+hltdev8642@users.noreply.github.com> Date: Fri, 9 May 2025 09:42:01 -0400 Subject: [PATCH 12/83] Escape parentheses in zsh completion script PR #6079 Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- app/data/zsh-completion/_scrcpy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 450fc8f5..8c2498f1 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -11,7 +11,7 @@ arguments=( '--always-on-top[Make scrcpy window always on top \(above other windows\)]' '--angle=[Rotate the video content by a custom angle, in degrees]' '--audio-bit-rate=[Encode the audio at the given bit-rate]' - '--audio-buffer=[Configure the audio buffering delay (in milliseconds)]' + '--audio-buffer=[Configure the audio buffering delay \(in milliseconds\)]' '--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-dup=[Duplicate audio]' @@ -35,10 +35,10 @@ arguments=( {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' - '-G[Use UHID/AOA gamepad (same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode)]' + '-G[Use UHID/AOA gamepad \(same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode\)]' '--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)' {-h,--help}'[Print the help]' - '-K[Use UHID/AOA keyboard (same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode)]' + '-K[Use UHID/AOA keyboard \(same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode\)]' '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--kill-adb-on-close[Kill adb when scrcpy terminates]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' @@ -48,7 +48,7 @@ arguments=( '--list-displays[List displays available on the device]' '--list-encoders[List video and audio encoders available on the device]' {-m,--max-size=}'[Limit both the width and height of the video to value]' - '-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]' + '-M[Use UHID/AOA mouse \(same as --mouse=uhid or --mouse=aoa, depending on OTG mode\)]' '--max-fps=[Limit the frame rate of screen capture]' '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' '--mouse-bind=[Configure bindings of secondary clicks]' From 70bfa2cf394955accb7446a99f9b6c6f5dfbaa2c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 15 May 2025 19:51:36 +0200 Subject: [PATCH 13/83] Remove useless flag in zsh completion script The -N flag is only useful after a pattern section (-p) to switch back to listing command names. Refs --- app/data/zsh-completion/_scrcpy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 8c2498f1..04ffb8f1 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -1,4 +1,4 @@ -#compdef -N scrcpy -N scrcpy.exe +#compdef scrcpy scrcpy.exe # # name: scrcpy # auth: hltdev [hltdev8642@gmail.com] From 52f5d08d1fab9600e78b21c71fa4a6c106d3783f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 3 Jun 2025 21:13:29 +0200 Subject: [PATCH 14/83] Avoid calling wait(0) Calling wait(0) results in waiting without a timeout, which is unintended. Refs #6009 comment --- .../main/java/com/genymobile/scrcpy/control/Controller.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index 5e64a4c5..24d827fd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -699,7 +699,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener { if (timeout < 0) { return null; } - displayDataAvailable.wait(timeout); + if (timeout > 0) { + displayDataAvailable.wait(timeout); + } data = displayData.get(); } From d2cc930975a8c7a2a073a9bffd9c2576d58cff7b Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Thu, 22 May 2025 18:50:41 +0100 Subject: [PATCH 15/83] Add app name SDL hint This allows pulseaudio to label the audio stream "scrcpy" rather than "SDL Application". PR #6107 Signed-off-by: Romain Vimont --- app/src/compat.h | 8 ++++++++ app/src/scrcpy.c | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/src/compat.h b/app/src/compat.h index 1995d384..296d1a9f 100644 --- a/app/src/compat.h +++ b/app/src/compat.h @@ -75,6 +75,14 @@ # define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL #endif +#if SDL_VERSION_ATLEAST(2, 0, 18) +# define SCRCPY_SDL_HAS_HINT_APP_NAME +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 14) +# define SCRCPY_SDL_HAS_HINT_AUDIO_DEVICE_APP_NAME +#endif + #ifndef HAVE_STRDUP char *strdup(const char *s); #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4d08e667..a4c8c340 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -107,6 +107,17 @@ sdl_set_hints(const char *render_driver) { LOGW("Could not set render driver"); } + // App name used in various contexts (such as PulseAudio) +#if defined(SCRCPY_SDL_HAS_HINT_APP_NAME) + if (!SDL_SetHint(SDL_HINT_APP_NAME, "scrcpy")) { + LOGW("Could not set app name"); + } +#elif defined(SCRCPY_SDL_HAS_HINT_AUDIO_DEVICE_APP_NAME) + if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "scrcpy")) { + LOGW("Could not set audio device app name"); + } +#endif + // Linear filtering if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { LOGW("Could not enable linear filtering"); From 41ed40f5f9ee557c5ccbca8b30a51c74af92da92 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:31:22 +0800 Subject: [PATCH 16/83] Simplify InputManager wrapper Use the public InputManager API. PR #6009 Signed-off-by: Romain Vimont --- .../scrcpy/wrappers/InputManager.java | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index f9f8e3ac..24c5f80c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.util.Ln; import android.annotation.SuppressLint; @@ -16,40 +17,26 @@ public final class InputManager { public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; - private final Object manager; - private Method injectInputEventMethod; + private final android.hardware.input.InputManager manager; private long lastPermissionLogDate; + private static Method injectInputEventMethod; private static Method setDisplayIdMethod; private static Method setActionButtonMethod; static InputManager create() { - try { - Class inputManagerClass = getInputManagerClass(); - Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance"); - Object im = getInstanceMethod.invoke(null); - return new InputManager(im); - } catch (ReflectiveOperationException e) { - throw new AssertionError(e); - } + android.hardware.input.InputManager manager = (android.hardware.input.InputManager) FakeContext.get() + .getSystemService(FakeContext.INPUT_SERVICE); + return new InputManager(manager); } - private static Class getInputManagerClass() { - try { - // Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview - return Class.forName("android.hardware.input.InputManagerGlobal"); - } catch (ClassNotFoundException e) { - return android.hardware.input.InputManager.class; - } - } - - private InputManager(Object manager) { + private InputManager(android.hardware.input.InputManager manager) { this.manager = manager; } - private Method getInjectInputEventMethod() throws NoSuchMethodException { + private static Method getInjectInputEventMethod() throws NoSuchMethodException { if (injectInputEventMethod == null) { - injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); + injectInputEventMethod = android.hardware.input.InputManager.class.getMethod("injectInputEvent", InputEvent.class, int.class); } return injectInputEventMethod; } From ee414231ed136a59113787dc83a739623518c728 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 3 May 2025 13:43:10 +0200 Subject: [PATCH 17/83] Cache getDisplayInfo method Do not use reflection to retrieve the method for every call. PR #6009 --- .../genymobile/scrcpy/wrappers/DisplayManager.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 130f86c6..3f8ed2bd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -46,6 +46,7 @@ public final class DisplayManager { } private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal + private Method getDisplayInfoMethod; private Method createVirtualDisplayMethod; private Method requestDisplayPowerMethod; @@ -114,9 +115,17 @@ public final class DisplayManager { return flags; } + private Method getGetDisplayInfoMethod() throws NoSuchMethodException { + if (getDisplayInfoMethod == null) { + getDisplayInfoMethod = manager.getClass().getMethod("getDisplayInfo", int.class); + } + return getDisplayInfoMethod; + } + public DisplayInfo getDisplayInfo(int displayId) { try { - Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId); + Method method = getGetDisplayInfoMethod(); + Object displayInfo = method.invoke(manager, displayId); if (displayInfo == null) { // fallback when displayInfo is null return getDisplayInfoFromDumpsysDisplay(displayId); From 7a3fe830d4d85d02ec21a23b88d8b48ce798a13e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 2 May 2025 23:03:15 +0200 Subject: [PATCH 18/83] Synchronize access to DisplayManager The DisplayManager and its method getDisplayInfo() may be used from both the Controller thread and the video (main) thread. PR #6009 --- .../java/com/genymobile/scrcpy/wrappers/DisplayManager.java | 3 ++- .../java/com/genymobile/scrcpy/wrappers/ServiceManager.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 3f8ed2bd..2f86bbd2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -115,7 +115,8 @@ public final class DisplayManager { return flags; } - private Method getGetDisplayInfoMethod() throws NoSuchMethodException { + // getDisplayInfo() may be used from both the Controller thread and the video (main) thread + private synchronized Method getGetDisplayInfoMethod() throws NoSuchMethodException { if (getDisplayInfoMethod == null) { getDisplayInfoMethod = manager.getClass().getMethod("getDisplayInfo", int.class); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index a8a56dab..b1123b55 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -54,7 +54,8 @@ public final class ServiceManager { return windowManager; } - public static DisplayManager getDisplayManager() { + // The DisplayManager may be used from both the Controller thread and the video (main) thread + public static synchronized DisplayManager getDisplayManager() { if (displayManager == null) { displayManager = DisplayManager.create(); } From ca4f50c5ef12eee2c0efb562e157a8b2d75cb001 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:26:24 +0800 Subject: [PATCH 19/83] Associate UHID devices to virtual displays This allows the mouse pointer to appear on the correct display (only for devices running Android 15+). Fixes #5547 PR #6009 Signed-off-by: Romain Vimont --- .../genymobile/scrcpy/control/Controller.java | 29 ++++++++- .../scrcpy/control/UhidManager.java | 65 ++++++++++++++++--- .../genymobile/scrcpy/device/DisplayInfo.java | 9 ++- .../scrcpy/wrappers/DisplayManager.java | 5 +- .../scrcpy/wrappers/InputManager.java | 40 ++++++++++++ 5 files changed, 134 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index 24d827fd..a905b6c9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -6,6 +6,7 @@ import com.genymobile.scrcpy.CleanUp; import com.genymobile.scrcpy.Options; import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.DeviceApp; +import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.Point; import com.genymobile.scrcpy.device.Position; import com.genymobile.scrcpy.device.Size; @@ -156,8 +157,34 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener { private UhidManager getUhidManager() { if (uhidManager == null) { - uhidManager = new UhidManager(sender); + int uhidDisplayId = displayId; + if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { + if (displayId == Device.DISPLAY_ID_NONE) { + // Mirroring a new virtual display id (using --new-display-id feature) on Android >= 15, where the UHID mouse pointer can be + // associated to the virtual display + try { + // Wait for at most 1 second until a virtual display id is known + DisplayData data = waitDisplayData(1000); + if (data != null) { + uhidDisplayId = data.virtualDisplayId; + } + } catch (InterruptedException e) { + // do nothing + } + } + } + + String displayUniqueId = null; + if (uhidDisplayId > 0) { + // Ignore Device.DISPLAY_ID_NONE and 0 (main display) + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(uhidDisplayId); + if (displayInfo != null) { + displayUniqueId = displayInfo.getUniqueId(); + } + } + uhidManager = new UhidManager(sender, displayUniqueId); } + return uhidManager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java index c4867a3f..20532c0b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy.control; import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.StringUtils; +import com.genymobile.scrcpy.wrappers.ServiceManager; import android.os.Build; import android.os.HandlerThread; @@ -31,14 +32,20 @@ public final class UhidManager { private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event) + // Must be unique across the system + private static final String INPUT_PORT = "scrcpy:" + Os.getpid(); + + private final String displayUniqueId; + private final ArrayMap fds = new ArrayMap<>(); private final ByteBuffer buffer = ByteBuffer.allocate(SIZE_OF_UHID_EVENT).order(ByteOrder.nativeOrder()); private final DeviceMessageSender sender; private final MessageQueue queue; - public UhidManager(DeviceMessageSender sender) { + public UhidManager(DeviceMessageSender sender, String displayUniqueId) { this.sender = sender; + this.displayUniqueId = displayUniqueId; if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) { HandlerThread thread = new HandlerThread("UHidManager"); thread.start(); @@ -52,15 +59,22 @@ public final class UhidManager { try { FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); try { + // First UHID device added + boolean firstDevice = fds.isEmpty(); + FileDescriptor old = fds.put(id, fd); if (old != null) { Ln.w("Duplicate UHID id: " + id); close(old); } - byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc); + String phys = mustUseInputPort() ? INPUT_PORT : null; + byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc, phys); Os.write(fd, req, 0, req.length); + if (firstDevice) { + addUniqueIdAssociation(); + } registerUhidListener(id, fd); } catch (Exception e) { close(fd); @@ -148,7 +162,7 @@ public final class UhidManager { } } - private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) { + private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc, String phys) { /* * struct uhid_event { * uint32_t type; @@ -170,17 +184,23 @@ public final class UhidManager { * } __attribute__((__packed__)); */ - byte[] empty = new byte[256]; ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder()); buf.putInt(UHID_CREATE2); String actualName = name.isEmpty() ? "scrcpy" : name; - byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8); - int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127); - assert len <= 127; - buf.put(utf8Name, 0, len); - buf.put(empty, 0, 256 - len); + byte[] nameBytes = actualName.getBytes(StandardCharsets.UTF_8); + int nameLen = StringUtils.getUtf8TruncationIndex(nameBytes, 127); + assert nameLen <= 127; + buf.put(nameBytes, 0, nameLen); + if (phys != null) { + buf.position(4 + 128); + byte[] physBytes = phys.getBytes(StandardCharsets.US_ASCII); + assert physBytes.length <= 63; + buf.put(physBytes); + } + + buf.position(4 + 256); buf.putShort((short) reportDesc.length); buf.putShort(BUS_VIRTUAL); buf.putInt(vendorId); @@ -219,15 +239,26 @@ public final class UhidManager { if (fd != null) { unregisterUhidListener(fd); close(fd); + + if (fds.isEmpty()) { + // Last UHID device removed + removeUniqueIdAssociation(); + } } else { Ln.w("Closing unknown UHID device: " + id); } } public void closeAll() { + if (fds.isEmpty()) { + return; + } + for (FileDescriptor fd : fds.values()) { close(fd); } + + removeUniqueIdAssociation(); } private static void close(FileDescriptor fd) { @@ -237,4 +268,20 @@ public final class UhidManager { Ln.e("Failed to close uhid: " + e.getMessage()); } } + + private boolean mustUseInputPort() { + return Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15 && displayUniqueId != null; + } + + private void addUniqueIdAssociation() { + if (mustUseInputPort()) { + ServiceManager.getInputManager().addUniqueIdAssociationByPort(INPUT_PORT, displayUniqueId); + } + } + + private void removeUniqueIdAssociation() { + if (mustUseInputPort()) { + ServiceManager.getInputManager().removeUniqueIdAssociationByPort(INPUT_PORT); + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/device/DisplayInfo.java b/server/src/main/java/com/genymobile/scrcpy/device/DisplayInfo.java index cdd4bab9..8d26b7ce 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/DisplayInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/DisplayInfo.java @@ -7,16 +7,18 @@ public final class DisplayInfo { private final int layerStack; private final int flags; private final int dpi; + private final String uniqueId; public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; - public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags, int dpi) { + public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags, int dpi, String uniqueId) { this.displayId = displayId; this.size = size; this.rotation = rotation; this.layerStack = layerStack; this.flags = flags; this.dpi = dpi; + this.uniqueId = uniqueId; } public int getDisplayId() { @@ -42,5 +44,8 @@ public final class DisplayInfo { public int getDpi() { return dpi; } -} + public String getUniqueId() { + return uniqueId; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 2f86bbd2..a12470a4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -82,7 +82,7 @@ public final class DisplayManager { int density = Integer.parseInt(m.group(5)); int layerStack = Integer.parseInt(m.group(6)); - return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, density); + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, density, null); } private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) { @@ -139,7 +139,8 @@ public final class DisplayManager { int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); int flags = cls.getDeclaredField("flags").getInt(displayInfo); int dpi = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo); - return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi); + String uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo); + return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index 24c5f80c..f55648d5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -1,9 +1,11 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.util.Ln; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.view.InputEvent; import android.view.MotionEvent; @@ -23,6 +25,8 @@ public final class InputManager { private static Method injectInputEventMethod; private static Method setDisplayIdMethod; private static Method setActionButtonMethod; + private static Method addUniqueIdAssociationByPortMethod; + private static Method removeUniqueIdAssociationByPortMethod; static InputManager create() { android.hardware.input.InputManager manager = (android.hardware.input.InputManager) FakeContext.get() @@ -103,4 +107,40 @@ public final class InputManager { return false; } } + + private static Method getAddUniqueIdAssociationByPortMethod() throws NoSuchMethodException { + if (addUniqueIdAssociationByPortMethod == null) { + addUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod( + "addUniqueIdAssociationByPort", String.class, String.class); + } + return addUniqueIdAssociationByPortMethod; + } + + @TargetApi(AndroidVersions.API_35_ANDROID_15) + public void addUniqueIdAssociationByPort(String inputPort, String uniqueId) { + try { + Method method = getAddUniqueIdAssociationByPortMethod(); + method.invoke(manager, inputPort, uniqueId); + } catch (ReflectiveOperationException e) { + Ln.e("Cannot add unique id association by port", e); + } + } + + private static Method getRemoveUniqueIdAssociationByPortMethod() throws NoSuchMethodException { + if (removeUniqueIdAssociationByPortMethod == null) { + removeUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod( + "removeUniqueIdAssociationByPort", String.class); + } + return removeUniqueIdAssociationByPortMethod; + } + + @TargetApi(AndroidVersions.API_35_ANDROID_15) + public void removeUniqueIdAssociationByPort(String inputPort) { + try { + Method method = getRemoveUniqueIdAssociationByPortMethod(); + method.invoke(manager, inputPort); + } catch (ReflectiveOperationException e) { + Ln.e("Cannot remove unique id association by port", e); + } + } } From 283326b2f6fa3fdaeecc181f69a3a4bcd429c06a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 5 Jun 2025 20:33:15 +0200 Subject: [PATCH 20/83] Run a main looper Instead of blocking the main thread until completion, run a looper. This will allow the main thread to process any event posted to the main looper. Refs #6009 comment PR #6129 --- .../java/com/genymobile/scrcpy/Server.java | 31 ++++++++++++------- .../com/genymobile/scrcpy/Workarounds.java | 15 --------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 09cfd6cf..c1d8c1f2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -25,9 +25,11 @@ import com.genymobile.scrcpy.video.SurfaceEncoder; import com.genymobile.scrcpy.video.VideoSource; import android.os.Build; +import android.os.Looper; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -55,17 +57,7 @@ public final class Server { this.fatalError = true; } if (running == 0 || this.fatalError) { - notify(); - } - } - - synchronized void await() { - try { - while (running > 0 && !fatalError) { - wait(); - } - } catch (InterruptedException e) { - // ignore + Looper.getMainLooper().quitSafely(); } } } @@ -104,6 +96,7 @@ public final class Server { boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); + prepareMainLooper(); Workarounds.apply(); List asyncProcessors = new ArrayList<>(); @@ -172,7 +165,7 @@ public final class Server { }); } - completion.await(); + Looper.loop(); // interrupted by the Completion implementation } finally { if (cleanUp != null) { cleanUp.interrupt(); @@ -201,6 +194,20 @@ public final class Server { } } + private static void prepareMainLooper() { + // Like Looper.prepareMainLooper(), but with quitAllowed set to true + Looper.prepare(); + synchronized (Looper.class) { + try { + Field field = Looper.class.getDeclaredField("sMainLooper"); + field.setAccessible(true); + field.set(null, Looper.myLooper()); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + } + public static void main(String... args) { int status = 0; try { diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index fb4c1389..b89f19ae 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -29,8 +29,6 @@ public final class Workarounds { private static final Object ACTIVITY_THREAD; static { - prepareMainLooper(); - try { // ActivityThread activityThread = new ActivityThread(); ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread"); @@ -77,19 +75,6 @@ public final class Workarounds { fillAppContext(); } - @SuppressWarnings("deprecation") - private static void prepareMainLooper() { - // Some devices internally create a Handler when creating an input Surface, causing an exception: - // "Can't create handler inside thread that has not called Looper.prepare()" - // - // - // Use Looper.prepareMainLooper() instead of Looper.prepare() to avoid a NullPointerException: - // "Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' - // on a null object reference" - // - Looper.prepareMainLooper(); - } - private static void fillAppInfo() { try { // ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData(); From 8a02e3c2f58cffc3fdd8c08b26aae04bbf9d5a97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Apr 2025 18:09:55 +0200 Subject: [PATCH 21/83] Simplify ClipboardManager wrapper Use the public ClipboardManager API, with the FakeContext as context. This requires a running main looper, otherwise clipboard changes are not processed. Refs #6009 PR #6129 Suggested by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- .../com/genymobile/scrcpy/FakeContext.java | 25 ++ .../genymobile/scrcpy/control/Controller.java | 22 +- .../scrcpy/wrappers/ClipboardManager.java | 255 +----------------- 3 files changed, 48 insertions(+), 254 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 2b83e397..b43e9e1b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -2,8 +2,10 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.AttributionSource; +import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; @@ -11,6 +13,8 @@ import android.content.IContentProvider; import android.os.Binder; import android.os.Process; +import java.lang.reflect.Field; + public final class FakeContext extends ContextWrapper { public static final String PACKAGE_NAME = "com.android.shell"; @@ -91,4 +95,25 @@ public final class FakeContext extends ContextWrapper { public ContentResolver getContentResolver() { return contentResolver; } + + @SuppressLint("SoonBlockedPrivateApi") + @Override + public Object getSystemService(String name) { + Object service = super.getSystemService(name); + if (service == null) { + return null; + } + + if (Context.CLIPBOARD_SERVICE.equals(name)) { + try { + Field field = ClipboardManager.class.getDeclaredField("mContext"); + field.setAccessible(true); + field.set(service, this); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + return service; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index a905b6c9..bfbee7dc 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -18,7 +18,6 @@ import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; -import android.content.IOnPrimaryClipChangedListener; import android.content.Intent; import android.os.Build; import android.os.SystemClock; @@ -119,18 +118,15 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardManager != null) { - clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() { - @Override - public void dispatchPrimaryClipChanged() { - if (isSettingClipboard.get()) { - // This is a notification for the change we are currently applying, ignore it - return; - } - String text = Device.getClipboardText(); - if (text != null) { - DeviceMessage msg = DeviceMessage.createClipboard(text); - sender.send(msg); - } + clipboardManager.addPrimaryClipChangedListener(() -> { + if (isSettingClipboard.get()) { + // This is a notification for the change we are currently applying, ignore it + return; + } + String text = Device.getClipboardText(); + if (text != null) { + DeviceMessage msg = DeviceMessage.createClipboard(text); + sender.send(msg); } }); } else { diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index 791df0f8..fae8a056 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -1,270 +1,43 @@ package com.genymobile.scrcpy.wrappers; -import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.FakeContext; -import com.genymobile.scrcpy.util.Ln; import android.content.ClipData; -import android.content.IOnPrimaryClipChangedListener; -import android.os.Build; -import android.os.IInterface; - -import java.lang.reflect.Method; public final class ClipboardManager { - private final IInterface manager; - private Method getPrimaryClipMethod; - private Method setPrimaryClipMethod; - private Method addPrimaryClipChangedListener; - private int getMethodVersion; - private int setMethodVersion; - private int addListenerMethodVersion; + private final android.content.ClipboardManager manager; static ClipboardManager create() { - IInterface clipboard = ServiceManager.getService("clipboard", "android.content.IClipboard"); - if (clipboard == null) { + android.content.ClipboardManager manager = (android.content.ClipboardManager) FakeContext.get() + .getSystemService(FakeContext.CLIPBOARD_SERVICE); + if (manager == null) { // Some devices have no clipboard manager // // return null; } - return new ClipboardManager(clipboard); + return new ClipboardManager(manager); } - private ClipboardManager(IInterface manager) { + private ClipboardManager(android.content.ClipboardManager manager) { this.manager = manager; } - private Method getGetPrimaryClipMethod() throws NoSuchMethodException { - if (getPrimaryClipMethod == null) { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class); - return getPrimaryClipMethod; - } - - try { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class); - getMethodVersion = 0; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - try { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class); - getMethodVersion = 1; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - try { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); - getMethodVersion = 2; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - try { - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); - getMethodVersion = 3; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - try { - getPrimaryClipMethod = manager.getClass() - .getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class); - getMethodVersion = 4; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - try { - getPrimaryClipMethod = manager.getClass() - .getMethod("getPrimaryClip", String.class, String.class, String.class, String.class, int.class, int.class, boolean.class); - getMethodVersion = 5; - return getPrimaryClipMethod; - } catch (NoSuchMethodException e) { - // fall-through - } - - getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, String.class); - getMethodVersion = 6; - } - return getPrimaryClipMethod; - } - - private Method getSetPrimaryClipMethod() throws NoSuchMethodException { - if (setPrimaryClipMethod == null) { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class); - return setPrimaryClipMethod; - } - - try { - setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class); - setMethodVersion = 0; - return setPrimaryClipMethod; - } catch (NoSuchMethodException e1) { - // fall-through - } - - try { - setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class); - setMethodVersion = 1; - return setPrimaryClipMethod; - } catch (NoSuchMethodException e2) { - // fall-through - } - - try { - setPrimaryClipMethod = manager.getClass() - .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class); - setMethodVersion = 2; - return setPrimaryClipMethod; - } catch (NoSuchMethodException e3) { - // fall-through - } - - setPrimaryClipMethod = manager.getClass() - .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class, boolean.class); - setMethodVersion = 3; - } - return setPrimaryClipMethod; - } - - private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager) throws ReflectiveOperationException { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME); - } - - switch (methodVersion) { - case 0: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); - case 1: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - case 2: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); - case 3: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null); - case 4: - // The last boolean parameter is "userOperate" - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true); - case 5: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, null, null, FakeContext.ROOT_UID, 0, true); - default: - return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, null); - } - } - - private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData) throws ReflectiveOperationException { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME); - return; - } - - switch (methodVersion) { - case 0: - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); - break; - case 1: - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - break; - case 2: - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); - break; - default: - // The last boolean parameter is "userOperate" - method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true); - } - } - public CharSequence getText() { - try { - Method method = getGetPrimaryClipMethod(); - ClipData clipData = getPrimaryClip(method, getMethodVersion, manager); - if (clipData == null || clipData.getItemCount() == 0) { - return null; - } - return clipData.getItemAt(0).getText(); - } catch (ReflectiveOperationException e) { - Ln.e("Could not invoke method", e); + ClipData clipData = manager.getPrimaryClip(); + if (clipData == null || clipData.getItemCount() == 0) { return null; } + return clipData.getItemAt(0).getText(); } public boolean setText(CharSequence text) { - try { - Method method = getSetPrimaryClipMethod(); - ClipData clipData = ClipData.newPlainText(null, text); - setPrimaryClip(method, setMethodVersion, manager, clipData); - return true; - } catch (ReflectiveOperationException e) { - Ln.e("Could not invoke method", e); - return false; - } + ClipData clipData = ClipData.newPlainText(null, text); + manager.setPrimaryClip(clipData); + return true; } - private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener) - throws ReflectiveOperationException { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - method.invoke(manager, listener, FakeContext.PACKAGE_NAME); - return; - } - - switch (methodVersion) { - case 0: - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID); - break; - case 1: - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); - break; - default: - method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); - break; - } - } - - private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException { - if (addPrimaryClipChangedListener == null) { - if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class); - } else { - try { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class); - addListenerMethodVersion = 0; - } catch (NoSuchMethodException e1) { - try { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, - int.class); - addListenerMethodVersion = 1; - } catch (NoSuchMethodException e2) { - addPrimaryClipChangedListener = manager.getClass() - .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, - int.class, int.class); - addListenerMethodVersion = 2; - } - } - } - } - return addPrimaryClipChangedListener; - } - - public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { - try { - Method method = getAddPrimaryClipChangedListener(); - addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener); - return true; - } catch (ReflectiveOperationException e) { - Ln.e("Could not invoke method", e); - return false; - } + public void addPrimaryClipChangedListener(android.content.ClipboardManager.OnPrimaryClipChangedListener listener) { + manager.addPrimaryClipChangedListener(listener); } } From ac16be54c8d4afed7c69b7132719b37282baea49 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 11 Jun 2025 19:36:22 +0200 Subject: [PATCH 22/83] Upgrade platform-tools (36.0.0) --- app/deps/adb_linux.sh | 4 ++-- app/deps/adb_macos.sh | 4 ++-- app/deps/adb_windows.sh | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/deps/adb_linux.sh b/app/deps/adb_linux.sh index 17b5641d..a3e339ec 100755 --- a/app/deps/adb_linux.sh +++ b/app/deps/adb_linux.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=35.0.2 +VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-linux.zip PROJECT_DIR=platform-tools-$VERSION-linux -SHA256SUM=acfdcccb123a8718c46c46c059b2f621140194e5ec1ac9d81715be3d6ab6cd0a +SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8 cd "$SOURCES_DIR" diff --git a/app/deps/adb_macos.sh b/app/deps/adb_macos.sh index 8a25915e..36f5df89 100755 --- a/app/deps/adb_macos.sh +++ b/app/deps/adb_macos.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=35.0.2 +VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-darwin.zip PROJECT_DIR=platform-tools-$VERSION-darwin -SHA256SUM=1820078db90bf21628d257ff052528af1c61bb48f754b3555648f5652fa35d78 +SHA256SUM=b241878e6ec20650b041bf715ea05f7d5dc73bd24529464bd9cf68946e3132bd cd "$SOURCES_DIR" diff --git a/app/deps/adb_windows.sh b/app/deps/adb_windows.sh index d36706b0..de37162c 100755 --- a/app/deps/adb_windows.sh +++ b/app/deps/adb_windows.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=35.0.2 +VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-win.zip PROJECT_DIR=platform-tools-$VERSION-windows -SHA256SUM=2975a3eac0b19182748d64195375ad056986561d994fffbdc64332a516300bb9 +SHA256SUM=24bd8bebbbb58b9870db202b5c6775c4a49992632021c60750d9d8ec8179d5f0 cd "$SOURCES_DIR" From 1a9ffb38146b7f70021387ccb0206c873fb07d99 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 11 Jun 2025 19:38:29 +0200 Subject: [PATCH 23/83] Upgrade SDL (2.32.8) --- ...that-the-correct-struct-is-used-for-.patch | 33 ------------------- app/deps/sdl.sh | 5 ++- 2 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 app/deps/patches/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch diff --git a/app/deps/patches/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch b/app/deps/patches/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch deleted file mode 100644 index cbb516ec..00000000 --- a/app/deps/patches/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 6be87ceb33a9aad3bf5204bb13b3a5e8b498fd26 Mon Sep 17 00:00:00 2001 -From: Neal Gompa -Date: Mon, 10 Feb 2025 05:00:56 -0500 -Subject: [PATCH] pipewire: Ensure that the correct struct is used for - enumeration APIs - -PipeWire now requires the correct struct type is used, otherwise -it will fail to compile. - -Reference: https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/188d920733f0791413d3386e5536ee7377f71b2f - -Fixes: https://github.com/libsdl-org/SDL/issues/12224 -(cherry picked from commit d35bef64e913dd7d5dd3153a4b61f10ef837dad6) ---- - src/audio/pipewire/SDL_pipewire.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c -index 889e05decb..5d1bfc28de 100644 ---- a/src/audio/pipewire/SDL_pipewire.c -+++ b/src/audio/pipewire/SDL_pipewire.c -@@ -590,7 +590,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) - - /* Need to parse the parameters to get the sample rate */ - for (i = 0; i < info->n_params; ++i) { -- pw_node_enum_params(node->proxy, 0, info->params[i].id, 0, 0, NULL); -+ pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL); - } - - hotplug_core_sync(node); --- -2.49.0 - diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh index c3edee58..54fee12b 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -5,10 +5,10 @@ cd "$DEPS_DIR" . common process_args "$@" -VERSION=2.32.2 +VERSION=2.32.8 FILENAME=SDL-$VERSION.tar.gz PROJECT_DIR=SDL-release-$VERSION -SHA256SUM=f2c7297ae7b3d3910a8b131e1e2a558fdd6d1a4443d5e345374d45cadfcb05a4 +SHA256SUM=dd35e05644ae527848d02433bec24dd0ea65db59faecf1a0e5d1880c533dac2c cd "$SOURCES_DIR" @@ -18,7 +18,6 @@ then else get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" - patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch fi mkdir -p "$BUILD_DIR/$PROJECT_DIR" From 454beaa7571d3d86339a53a5a3202bd47e1d2353 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 11 Jun 2025 19:39:02 +0200 Subject: [PATCH 24/83] Upgrade libusb (1.0.29) --- app/deps/libusb.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/deps/libusb.sh b/app/deps/libusb.sh index 4be03eb1..887a2a77 100755 --- a/app/deps/libusb.sh +++ b/app/deps/libusb.sh @@ -5,10 +5,10 @@ cd "$DEPS_DIR" . common process_args "$@" -VERSION=1.0.28 +VERSION=1.0.29 FILENAME=libusb-$VERSION.tar.gz PROJECT_DIR=libusb-$VERSION -SHA256SUM=378b3709a405065f8f9fb9f35e82d666defde4d342c2a1b181a9ac134d23c6fe +SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74 cd "$SOURCES_DIR" From dc169e425e9cc94e8e871dbeac24bdebd277bf39 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 11 Jun 2025 19:39:48 +0200 Subject: [PATCH 25/83] Bump version to 3.3 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 19475e0b..45f1960c 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "3.2" + VALUE "ProductVersion", "3.3" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index b64a6c90..1e9a5729 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.2', + version: '3.3', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 02508001..059a6f30 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 35 - versionCode 30200 - versionName "3.2" + versionCode 30300 + versionName "3.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 8bb8632b..5b35e3ec 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.2 +SCRCPY_VERSION_NAME=3.3 PLATFORM=${ANDROID_PLATFORM:-35} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} From 696402c68c5f91fa77c3ed03cd835dc4412a253e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 11 Jun 2025 22:15:30 +0200 Subject: [PATCH 26/83] Update links to 3.3 --- README.md | 2 +- doc/build.md | 6 +++--- doc/linux.md | 6 +++--- doc/macos.md | 12 ++++++------ doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 81399f52..dc00ac22 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v3.2) +# scrcpy (v3.3) scrcpy diff --git a/doc/build.md b/doc/build.md index afe8b21b..c915e367 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v3.2`][direct-scrcpy-server] - SHA-256: `b920e0ea01936bf2482f4ba2fa985c22c13c621999e3d33b45baa5acfc1ea3d0` + - [`scrcpy-server-v3.3`][direct-scrcpy-server] + SHA-256: `351cb2edc7e4c2c75f09a7933fdabcf137be52e2602df154f24ec02db46e9e51` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-server-v3.2 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-server-v3.3 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/linux.md b/doc/linux.md index 979ef568..5cfd6e4e 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,11 +6,11 @@ Download a static build of the [latest release]: - - [`scrcpy-linux-x86_64-v3.2.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `df6cf000447428fcde322022848d655ff0211d98688d0f17cbbf21be9c1272be` + - [`scrcpy-linux-x86_64-v3.3.tar.gz`][direct-linux-x86_64] (x86_64) + SHA-256: `a0abf37003c3c47a53c1b2a12420296a2b0ee323cf3610fd6fbf9d9bab9d99f3` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-linux-x86_64-v3.2.tar.gz +[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-linux-x86_64-v3.3.tar.gz and extract it. diff --git a/doc/macos.md b/doc/macos.md index b0335d18..73a982f6 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -6,15 +6,15 @@ Download a static build of the [latest release]: - - [`scrcpy-macos-aarch64-v3.2.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `f6d1f3c5f74d4d46f5080baa5b56b69f5edbf698d47e0cf4e2a1fd5058f9507b` + - [`scrcpy-macos-aarch64-v3.3.tar.gz`][direct-macos-aarch64] (aarch64) + SHA-256: `7a4cdaeb8ba74593edda278c000ddedc8d70a51263a80b16a6345475d42ac21e` - - [`scrcpy-macos-x86_64-v3.2.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `e337d5cf0ba4e1281699c338ce5f104aee96eb7b2893dc851399b6643eb4044e` + - [`scrcpy-macos-x86_64-v3.3.tar.gz`][direct-macos-x86_64] (x86_64) + SHA-256: `bb3c13aac166b92539371883a8781aa861a7cd18e3e6077e570ab7a1f562f774` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-macos-aarch64-v3.2.tar.gz -[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-macos-x86_64-v3.2.tar.gz +[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-macos-aarch64-v3.3.tar.gz +[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-macos-x86_64-v3.3.tar.gz and extract it. diff --git a/doc/windows.md b/doc/windows.md index fb3e3887..7935461d 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -6,14 +6,14 @@ Download the [latest release]: - - [`scrcpy-win64-v3.2.zip`][direct-win64] (64-bit) - SHA-256: `eaa27133e0520979873ba57ad651560a4cc2618373bd05450b23a84d32beafd0` - - [`scrcpy-win32-v3.2.zip`][direct-win32] (32-bit) - SHA-256: `4a3407d7f0c2c8a03e22a12cf0b5e1e585a5056fe23c8e5cf3252207c6fa8357` + - [`scrcpy-win64-v3.3.zip`][direct-win64] (64-bit) + SHA-256: `a120cb4be7cde2891af38e83d2008173a0b6b6b5e344b2dfe668d0f892999933` + - [`scrcpy-win32-v3.3.zip`][direct-win32] (32-bit) + SHA-256: `e409ab83f8c57bd6ac741d652635cab7699fcf3d384e233833872f117b993ca6` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-win64-v3.2.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-win32-v3.2.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-win64-v3.3.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-win32-v3.3.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 2d2d2c2f..aabe9873 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-server-v3.2 -PREBUILT_SERVER_SHA256=b920e0ea01936bf2482f4ba2fa985c22c13c621999e3d33b45baa5acfc1ea3d0 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-server-v3.3 +PREBUILT_SERVER_SHA256=351cb2edc7e4c2c75f09a7933fdabcf137be52e2602df154f24ec02db46e9e51 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 4e1cf13a5092bfe8651c8f55eda3861b7d01b64a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 12 Jun 2025 09:03:39 +0200 Subject: [PATCH 27/83] Run a main looper in the cleanup process Since a main looper is explicitly run in the main process, the initialization of workarounds no longer calls Looper.prepareMainLooper(), leading to a crash: java.lang.RuntimeException: Can't create handler inside thread Thread[main,5,main] that has not called Looper.prepare() As a result, --power-off-on-close was broken. Refs 283326b2f6fa3fdaeecc181f69a3a4bcd429c06a Fixes #6146 --- server/src/main/java/com/genymobile/scrcpy/CleanUp.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 51db985c..77018afa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -7,6 +7,7 @@ import com.genymobile.scrcpy.util.SettingsException; import com.genymobile.scrcpy.wrappers.ServiceManager; import android.os.BatteryManager; +import android.os.Looper; import android.system.ErrnoException; import android.system.Os; @@ -179,6 +180,11 @@ public final class CleanUp { } } + @SuppressWarnings("deprecation") + private static void prepareMainLooper() { + Looper.prepareMainLooper(); + } + public static void main(String... args) { try { // Start a new session to avoid being terminated along with the server process on some devices @@ -188,6 +194,9 @@ public final class CleanUp { } unlinkSelf(); + // Needed for workarounds + prepareMainLooper(); + int displayId = Integer.parseInt(args[0]); int restoreStayOn = Integer.parseInt(args[1]); boolean disableShowTouches = Boolean.parseBoolean(args[2]); From 38256d8ff9d019f8d4fd84719eeafd0214c836e8 Mon Sep 17 00:00:00 2001 From: berk ziya Date: Thu, 12 Jun 2025 16:27:40 +0300 Subject: [PATCH 28/83] Fix deprecated brew command `brew cask` is an outdated command, replaced by `brew install --cask`. Refs #5398 PR #6149 Signed-off-by: Romain Vimont --- app/src/adb/adb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 40e9e968..9e9cfd6b 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -110,7 +110,7 @@ show_adb_installation_msg(void) { } pkg_managers[] = { {"apt", "apt install adb"}, {"apt-get", "apt-get install adb"}, - {"brew", "brew cask install android-platform-tools"}, + {"brew", "brew install --cask android-platform-tools"}, {"dnf", "dnf install android-tools"}, {"emerge", "emerge dev-util/android-tools"}, {"pacman", "pacman -S android-tools"}, From 772f42134a327eea60955463d0ee8bb712168dd0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 13 Jun 2025 09:36:34 +0200 Subject: [PATCH 29/83] Use Context.CLIPBOARD_SERVICE directly The constant is defined in Context, not FakeContext. --- .../java/com/genymobile/scrcpy/wrappers/ClipboardManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java index fae8a056..54936122 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java @@ -3,13 +3,13 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.FakeContext; import android.content.ClipData; +import android.content.Context; public final class ClipboardManager { private final android.content.ClipboardManager manager; static ClipboardManager create() { - android.content.ClipboardManager manager = (android.content.ClipboardManager) FakeContext.get() - .getSystemService(FakeContext.CLIPBOARD_SERVICE); + android.content.ClipboardManager manager = (android.content.ClipboardManager) FakeContext.get().getSystemService(Context.CLIPBOARD_SERVICE); if (manager == null) { // Some devices have no clipboard manager // From cd3a5d50b650da6dcafbdbddd606ef5031f1833a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 13 Jun 2025 09:24:36 +0200 Subject: [PATCH 30/83] Create ClipboardManager from the main thread The ClipboardManager is instantiated by the first call to ServiceManager.getClipboardManager(). Now that scrcpy uses android.content.ClipboardManager directly, it must ensure that it is created on the main thread (or at least on a thread with a Looper), to avoid the following error: > Can't create handler inside thread that has not called > Looper.prepare() Refs 8a02e3c2f58cffc3fdd8c08b26aae04bbf9d5a97 Fixes #6151 --- .../main/java/com/genymobile/scrcpy/control/Controller.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index bfbee7dc..b4a8e3ca 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -114,9 +114,10 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener { Ln.w("Input events are not supported for secondary displays before Android 10"); } + // Make sure the clipboard manager is always created from the main thread (even if clipboardAutosync is disabled) + ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardAutosync) { // If control and autosync are enabled, synchronize Android clipboard to the computer automatically - ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); if (clipboardManager != null) { clipboardManager.addPrimaryClipChangedListener(() -> { if (isSettingClipboard.get()) { From d74cfd5711b2ae2a12e38c0e7111e1af0f9af72c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 13 Jun 2025 09:40:20 +0200 Subject: [PATCH 31/83] Silence DiscouragedPrivateApi lint warning --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c1d8c1f2..46f3294f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -24,6 +24,7 @@ import com.genymobile.scrcpy.video.SurfaceCapture; import com.genymobile.scrcpy.video.SurfaceEncoder; import com.genymobile.scrcpy.video.VideoSource; +import android.annotation.SuppressLint; import android.os.Build; import android.os.Looper; @@ -199,6 +200,7 @@ public final class Server { Looper.prepare(); synchronized (Looper.class) { try { + @SuppressLint("DiscouragedPrivateApi") Field field = Looper.class.getDeclaredField("sMainLooper"); field.setAccessible(true); field.set(null, Looper.myLooper()); From 98d30288f78b0dd40ae1aa1b285c45f5769f49fc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 17 Jun 2025 21:02:41 +0200 Subject: [PATCH 32/83] Prepare the main looper earlier The looper must be initialized before listing apps, to avoid the following error: > Can't create handler inside thread that has not called > Looper.prepare() Refs 283326b2f6fa3fdaeecc181f69a3a4bcd429c06a Fixes #6165 --- server/src/main/java/com/genymobile/scrcpy/Server.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 46f3294f..a08c948c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -97,7 +97,6 @@ public final class Server { boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); - prepareMainLooper(); Workarounds.apply(); List asyncProcessors = new ArrayList<>(); @@ -230,6 +229,8 @@ public final class Server { Ln.e("Exception on thread " + t, e); }); + prepareMainLooper(); + Options options = Options.parse(args); Ln.disableSystemStreams(); From 9787fe5d261df8255e49b65f37e2d89bf1a129fa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 19 Jun 2025 20:50:26 +0200 Subject: [PATCH 33/83] Preserve original scroll values in mouse event Clamp scroll values to [-1, 1] only for the SDK mouse. HID mouse implementations perform their own clamping to [-127, 127] (in hid_mouse.c). PR #6172 --- app/src/input_manager.c | 8 ++++---- app/src/mouse_sdk.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 635825c9..f7a787d1 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -897,11 +897,11 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, struct sc_mouse_scroll_event evt = { .position = sc_input_manager_get_position(im, mouse_x, mouse_y), #if SDL_VERSION_ATLEAST(2, 0, 18) - .hscroll = CLAMP(event->preciseX, -1.0f, 1.0f), - .vscroll = CLAMP(event->preciseY, -1.0f, 1.0f), + .hscroll = event->preciseX, + .vscroll = event->preciseY, #else - .hscroll = CLAMP(event->x, -1, 1), - .vscroll = CLAMP(event->y, -1, 1), + .hscroll = event->x, + .vscroll = event->y, #endif .buttons_state = im->mouse_buttons_state, }; diff --git a/app/src/mouse_sdk.c b/app/src/mouse_sdk.c index 7eceffa7..1b05d02b 100644 --- a/app/src/mouse_sdk.c +++ b/app/src/mouse_sdk.c @@ -113,8 +113,8 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, .type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = event->position, - .hscroll = event->hscroll, - .vscroll = event->vscroll, + .hscroll = CLAMP(event->hscroll, -1, 1), + .vscroll = CLAMP(event->vscroll, -1, 1), .buttons = convert_mouse_buttons(event->buttons_state), }, }; From 7c8bdccbdc24b616c8d4ada861c424b3686912ea Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 20 Jun 2025 09:06:10 +0200 Subject: [PATCH 34/83] Extend value range for SDK mouse scrolling SDL precise scrolling can sometimes produce values greater than 1 or less than -1. On the wire, the value is encoded as a 16-bit fixed-point number. Previously, the range was interpreted as [-1, 1], using 1 bit for the integral part (the sign) and 15 bits for the fractional part. To support larger values, interpret the range as [-16, 16] instead, using 5 bits for the integral part and 11 bits for the fractional part (which is more than enough). PR #6172 --- app/src/control_msg.c | 12 ++++++++---- app/src/mouse_sdk.c | 4 ++-- app/tests/test_control_msg_serialize.c | 8 ++++---- .../scrcpy/control/ControlMessageReader.java | 5 +++-- .../scrcpy/control/ControlMessageReaderTest.java | 4 ++-- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/src/control_msg.c b/app/src/control_msg.c index e78f0c57..e46c6165 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -127,10 +127,14 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { return 32; case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT: write_position(&buf[1], &msg->inject_scroll_event.position); - int16_t hscroll = - sc_float_to_i16fp(msg->inject_scroll_event.hscroll); - int16_t vscroll = - sc_float_to_i16fp(msg->inject_scroll_event.vscroll); + // Accept values in the range [-16, 16]. + // Normalize to [-1, 1] in order to use sc_float_to_i16fp(). + float hscroll_norm = msg->inject_scroll_event.hscroll / 16; + hscroll_norm = CLAMP(hscroll_norm, -1, 1); + float vscroll_norm = msg->inject_scroll_event.vscroll / 16; + vscroll_norm = CLAMP(vscroll_norm, -1, 1); + int16_t hscroll = sc_float_to_i16fp(hscroll_norm); + int16_t vscroll = sc_float_to_i16fp(vscroll_norm); sc_write16be(&buf[13], (uint16_t) hscroll); sc_write16be(&buf[15], (uint16_t) vscroll); sc_write32be(&buf[17], msg->inject_scroll_event.buttons); diff --git a/app/src/mouse_sdk.c b/app/src/mouse_sdk.c index 1b05d02b..7eceffa7 100644 --- a/app/src/mouse_sdk.c +++ b/app/src/mouse_sdk.c @@ -113,8 +113,8 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, .type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, .inject_scroll_event = { .position = event->position, - .hscroll = CLAMP(event->hscroll, -1, 1), - .vscroll = CLAMP(event->vscroll, -1, 1), + .hscroll = event->hscroll, + .vscroll = event->vscroll, .buttons = convert_mouse_buttons(event->buttons_state), }, }; diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index af97182d..0d19919e 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -127,8 +127,8 @@ static void test_serialize_inject_scroll_event(void) { .height = 1920, }, }, - .hscroll = 1, - .vscroll = -1, + .hscroll = 16, + .vscroll = -16, .buttons = 1, }, }; @@ -141,8 +141,8 @@ static void test_serialize_inject_scroll_event(void) { SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026 0x04, 0x38, 0x07, 0x80, // 1080 1920 - 0x7F, 0xFF, // 1 (float encoded as i16) - 0x80, 0x00, // -1 (float encoded as i16) + 0x7F, 0xFF, // 16 (float encoded as i16 in the range [-16, 16]) + 0x80, 0x00, // -16 (float encoded as i16 in the range [-16, 16]) 0x00, 0x00, 0x00, 0x01, // 1 }; assert(!memcmp(buf, expected, sizeof(expected))); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java index e503ec61..830a7ec7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java @@ -112,8 +112,9 @@ public class ControlMessageReader { private ControlMessage parseInjectScrollEvent() throws IOException { Position position = parsePosition(); - float hScroll = Binary.i16FixedPointToFloat(dis.readShort()); - float vScroll = Binary.i16FixedPointToFloat(dis.readShort()); + // Binary.i16FixedPointToFloat() decodes values assuming the full range is [-1, 1], but the actual range is [-16, 16]. + float hScroll = Binary.i16FixedPointToFloat(dis.readShort()) * 16; + float vScroll = Binary.i16FixedPointToFloat(dis.readShort()) * 16; int buttons = dis.readInt(); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); } diff --git a/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java index 74df064f..0cc0a6b5 100644 --- a/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/control/ControlMessageReaderTest.java @@ -125,7 +125,7 @@ public class ControlMessageReaderTest { dos.writeShort(1080); dos.writeShort(1920); dos.writeShort(0); // 0.0f encoded as i16 - dos.writeShort(0x8000); // -1.0f encoded as i16 + dos.writeShort(0x8000); // -16.0f encoded as i16 (the range is [-16, 16]) dos.writeInt(1); byte[] packet = bos.toByteArray(); @@ -139,7 +139,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth()); Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(0f, event.getHScroll(), 0f); - Assert.assertEquals(-1f, event.getVScroll(), 0f); + Assert.assertEquals(-16f, event.getVScroll(), 0f); Assert.assertEquals(1, event.getButtons()); Assert.assertEquals(-1, bis.read()); // EOS From fc75319bb291121116419c784a5fa507fd820eca Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 18 Jun 2025 18:26:13 +0200 Subject: [PATCH 35/83] Fix HID mouse support with SDL precise scrolling Over HID, only integral scroll values can be sent. When SDL precise scrolling is active, scroll events may include fractional values (e.g., 0.05), which are truncated to 0 in the HID event. To fix the problem, use the integral scroll value reported by SDL, which internally accumulates fractional deltas. Fixes #6156 PR #6172 --- app/src/hid/hid_mouse.c | 12 ++++++++---- app/src/hid/hid_mouse.h | 2 +- app/src/input_events.h | 2 ++ app/src/input_manager.c | 2 ++ app/src/uhid/mouse_uhid.c | 4 +++- app/src/usb/mouse_aoa.c | 4 +++- app/src/usb/screen_otg.c | 7 +++++++ 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/app/src/hid/hid_mouse.c b/app/src/hid/hid_mouse.c index 29cfc594..e1fff45b 100644 --- a/app/src/hid/hid_mouse.c +++ b/app/src/hid/hid_mouse.c @@ -175,19 +175,23 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input, data[3] = 0; // wheel coordinates only used for scrolling } -void +bool sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input, const struct sc_mouse_scroll_event *event) { + if (!event->vscroll_int) { + // Need a full integral value for HID + return false; + } + sc_hid_mouse_input_init(hid_input); uint8_t *data = hid_input->data; data[0] = 0; // buttons state irrelevant (and unknown) data[1] = 0; // no x motion data[2] = 0; // no y motion - // In practice, vscroll is always -1, 0 or 1, but in theory other values - // are possible - data[3] = CLAMP(event->vscroll, -127, 127); + data[3] = CLAMP(event->vscroll_int, -127, 127); // Horizontal scrolling ignored + return true; } void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) { diff --git a/app/src/hid/hid_mouse.h b/app/src/hid/hid_mouse.h index 06c61dd1..4ae4bfd4 100644 --- a/app/src/hid/hid_mouse.h +++ b/app/src/hid/hid_mouse.h @@ -22,7 +22,7 @@ void sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input, const struct sc_mouse_click_event *event); -void +bool sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input, const struct sc_mouse_scroll_event *event); diff --git a/app/src/input_events.h b/app/src/input_events.h index 0c022acc..1e34b50e 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -393,6 +393,8 @@ struct sc_mouse_scroll_event { struct sc_position position; float hscroll; float vscroll; + int32_t hscroll_int; + int32_t vscroll_int; uint8_t buttons_state; // bitwise-OR of sc_mouse_button values }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f7a787d1..3e4dd0f3 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -903,6 +903,8 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, .hscroll = event->x, .vscroll = event->y, #endif + .hscroll_int = event->x, + .vscroll_int = event->y, .buttons_state = im->mouse_buttons_state, }; diff --git a/app/src/uhid/mouse_uhid.c b/app/src/uhid/mouse_uhid.c index 7fed8383..869e48a4 100644 --- a/app/src/uhid/mouse_uhid.c +++ b/app/src/uhid/mouse_uhid.c @@ -55,7 +55,9 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, struct sc_mouse_uhid *mouse = DOWNCAST(mp); struct sc_hid_input hid_input; - sc_hid_mouse_generate_input_from_scroll(&hid_input, event); + if (!sc_hid_mouse_generate_input_from_scroll(&hid_input, event)) { + return; + } sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll"); } diff --git a/app/src/usb/mouse_aoa.c b/app/src/usb/mouse_aoa.c index b64e9b12..fd5fa5e0 100644 --- a/app/src/usb/mouse_aoa.c +++ b/app/src/usb/mouse_aoa.c @@ -42,7 +42,9 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, struct sc_mouse_aoa *mouse = DOWNCAST(mp); struct sc_hid_input hid_input; - sc_hid_mouse_generate_input_from_scroll(&hid_input, event); + if (!sc_hid_mouse_generate_input_from_scroll(&hid_input, event)) { + return; + } if (!sc_aoa_push_input(mouse->aoa, &hid_input)) { LOGW("Could not push AOA HID input (mouse scroll)"); diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 02edc3a3..5c580df9 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -164,8 +164,15 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen, struct sc_mouse_scroll_event evt = { // .position not used for HID events +#if SDL_VERSION_ATLEAST(2, 0, 18) + .hscroll = event->preciseX, + .vscroll = event->preciseY, +#else .hscroll = event->x, .vscroll = event->y, +#endif + .hscroll_int = event->x, + .vscroll_int = event->y, .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state), }; From 4841fdd1eff58f313a62c539f31453eac3e21b62 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 18 Jun 2025 18:29:04 +0200 Subject: [PATCH 36/83] Add horizontal scrolling support for HID mouse PR #6172 --- app/src/hid/hid_mouse.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/src/hid/hid_mouse.c b/app/src/hid/hid_mouse.c index e1fff45b..33f0807e 100644 --- a/app/src/hid/hid_mouse.c +++ b/app/src/hid/hid_mouse.c @@ -3,8 +3,8 @@ #include // 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position, -// 1 byte for wheel motion -#define SC_HID_MOUSE_INPUT_SIZE 4 +// 1 byte for wheel motion, 1 byte for hozizontal scrolling +#define SC_HID_MOUSE_INPUT_SIZE 5 /** * Mouse descriptor from the specification: @@ -75,6 +75,21 @@ static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = { // Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel) 0x81, 0x06, + // Usage Page (Consumer Page) + 0x05, 0x0C, + // Usage(AC Pan) + 0x0A, 0x38, 0x02, + // Logical Minimum (-127) + 0x15, 0x81, + // Logical Maximum (127) + 0x25, 0x7F, + // Report Size (8) + 0x75, 0x08, + // Report Count (1) + 0x95, 0x01, + // Input (Data, Variable, Relative): 1 byte (AC Pan) + 0x81, 0x06, + // End Collection 0xC0, @@ -160,7 +175,8 @@ sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input, data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[1] = CLAMP(event->xrel, -127, 127); data[2] = CLAMP(event->yrel, -127, 127); - data[3] = 0; // wheel coordinates only used for scrolling + data[3] = 0; // no vertical scrolling + data[4] = 0; // no horizontal scrolling } void @@ -172,13 +188,14 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input, data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[1] = 0; // no x motion data[2] = 0; // no y motion - data[3] = 0; // wheel coordinates only used for scrolling + data[3] = 0; // no vertical scrolling + data[4] = 0; // no horizontal scrolling } bool sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input, const struct sc_mouse_scroll_event *event) { - if (!event->vscroll_int) { + if (!event->vscroll_int && !event->hscroll_int) { // Need a full integral value for HID return false; } @@ -190,7 +207,7 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input, data[1] = 0; // no x motion data[2] = 0; // no y motion data[3] = CLAMP(event->vscroll_int, -127, 127); - // Horizontal scrolling ignored + data[4] = CLAMP(event->hscroll_int, -127, 127); return true; } From 5b18ce0d2e91fd9875b3fe3b10a2c5dcb4399cd1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 20 Jun 2025 19:42:40 +0200 Subject: [PATCH 37/83] Bump version to 3.3.1 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 45f1960c..9c5374ae 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "3.3" + VALUE "ProductVersion", "3.3.1" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 1e9a5729..d991d672 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.3', + version: '3.3.1', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 059a6f30..31092b12 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 35 - versionCode 30300 - versionName "3.3" + versionCode 30301 + versionName "3.3.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 5b35e3ec..193a9902 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.3 +SCRCPY_VERSION_NAME=3.3.1 PLATFORM=${ANDROID_PLATFORM:-35} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} From f01231dff8294fe2c99045a4f9a14b233a71bb86 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 20 Jun 2025 20:14:42 +0200 Subject: [PATCH 38/83] Update links to 3.3.1 --- README.md | 2 +- doc/build.md | 6 +++--- doc/linux.md | 6 +++--- doc/macos.md | 12 ++++++------ doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index dc00ac22..d886d23c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v3.3) +# scrcpy (v3.3.1) scrcpy diff --git a/doc/build.md b/doc/build.md index c915e367..7f76b4fd 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v3.3`][direct-scrcpy-server] - SHA-256: `351cb2edc7e4c2c75f09a7933fdabcf137be52e2602df154f24ec02db46e9e51` + - [`scrcpy-server-v3.3.1`][direct-scrcpy-server] + SHA-256: `a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-server-v3.3 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/linux.md b/doc/linux.md index 5cfd6e4e..be433df4 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,11 +6,11 @@ Download a static build of the [latest release]: - - [`scrcpy-linux-x86_64-v3.3.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `a0abf37003c3c47a53c1b2a12420296a2b0ee323cf3610fd6fbf9d9bab9d99f3` + - [`scrcpy-linux-x86_64-v3.3.1.tar.gz`][direct-linux-x86_64] (x86_64) + SHA-256: `bbfe54c6b178adafeaffbbfbbc1548a74486553170c63e63bdd41863ad123422` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-linux-x86_64-v3.3.tar.gz +[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-linux-x86_64-v3.3.1.tar.gz and extract it. diff --git a/doc/macos.md b/doc/macos.md index 73a982f6..f6b01c30 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -6,15 +6,15 @@ Download a static build of the [latest release]: - - [`scrcpy-macos-aarch64-v3.3.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `7a4cdaeb8ba74593edda278c000ddedc8d70a51263a80b16a6345475d42ac21e` + - [`scrcpy-macos-aarch64-v3.3.1.tar.gz`][direct-macos-aarch64] (aarch64) + SHA-256: `907b925900ebd8499c1e47acc9689a95bd3a6f9930eb1d7bdfbca8375ae4f139` - - [`scrcpy-macos-x86_64-v3.3.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `bb3c13aac166b92539371883a8781aa861a7cd18e3e6077e570ab7a1f562f774` + - [`scrcpy-macos-x86_64-v3.3.1.tar.gz`][direct-macos-x86_64] (x86_64) + SHA-256: `69772491dad718eea82fc65c8e89febff7d41c4ce6faff02f4789a588d10fd7d` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-macos-aarch64-v3.3.tar.gz -[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-macos-x86_64-v3.3.tar.gz +[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-aarch64-v3.3.1.tar.gz +[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-x86_64-v3.3.1.tar.gz and extract it. diff --git a/doc/windows.md b/doc/windows.md index 7935461d..8fa1921f 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -6,14 +6,14 @@ Download the [latest release]: - - [`scrcpy-win64-v3.3.zip`][direct-win64] (64-bit) - SHA-256: `a120cb4be7cde2891af38e83d2008173a0b6b6b5e344b2dfe668d0f892999933` - - [`scrcpy-win32-v3.3.zip`][direct-win32] (32-bit) - SHA-256: `e409ab83f8c57bd6ac741d652635cab7699fcf3d384e233833872f117b993ca6` + - [`scrcpy-win64-v3.3.1.zip`][direct-win64] (64-bit) + SHA-256: `4fcad494772a3ae5de9a133149f8856d2fc429b41795f7cf7c754e0c1bb6fbc0` + - [`scrcpy-win32-v3.3.1.zip`][direct-win32] (32-bit) + SHA-256: `ccdf1b4f5d19dfe760446a107e55b0a010a00e097d46533a161499c9333a20a6` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-win64-v3.3.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-win32-v3.3.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win64-v3.3.1.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win32-v3.3.1.zip and extract it. diff --git a/install_release.sh b/install_release.sh index aabe9873..d960932b 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3/scrcpy-server-v3.3 -PREBUILT_SERVER_SHA256=351cb2edc7e4c2c75f09a7933fdabcf137be52e2602df154f24ec02db46e9e51 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1 +PREBUILT_SERVER_SHA256=a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 8761dcb7a8dfb0aee56e13baa9b411084ca9b5a6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 16 Jul 2025 19:07:58 +0200 Subject: [PATCH 39/83] Fix SDL dependency script error message Commit 360936248c0fb59bdc211e298d1aaea49af5ee07 mistakenly left an additional 'H' when replacing $HOST with $DIRNAME. Refs --- app/deps/sdl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh index 54fee12b..e04deb0d 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -28,7 +28,7 @@ export CXXFLAGS="$CFLAGS" if [[ -d "$DIRNAME" ]] then - echo "'$PWD/$HDIRNAME' already exists, not reconfigured" + echo "'$PWD/$DIRNAME' already exists, not reconfigured" cd "$DIRNAME" else mkdir "$DIRNAME" From 04542a9f58a1b7fdf42bda93757e4a1d858fe537 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Jul 2025 13:19:15 +0200 Subject: [PATCH 40/83] Fix window leak on icon error --- app/src/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/screen.c b/app/src/screen.c index 1d694f12..2f325e05 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -409,7 +409,7 @@ sc_screen_init(struct sc_screen *screen, } else { // without video, the icon is used as window content, it must be present LOGE("Could not load icon"); - goto error_destroy_fps_counter; + goto error_destroy_window; } SDL_Surface *icon_novideo = params->video ? NULL : icon; From a79ddc35a764b68a5a8c2599cca22f7984d9ad81 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Jul 2025 19:28:59 +0200 Subject: [PATCH 41/83] Update platform-tools checksums The release binaries of platform-tools_r36.0.0 have changed upstream. Both releases versions are referenced from Refs #6214 Refs --- app/deps/adb_macos.sh | 2 +- app/deps/adb_windows.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/deps/adb_macos.sh b/app/deps/adb_macos.sh index 36f5df89..6880fb93 100755 --- a/app/deps/adb_macos.sh +++ b/app/deps/adb_macos.sh @@ -7,7 +7,7 @@ cd "$DEPS_DIR" VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-darwin.zip PROJECT_DIR=platform-tools-$VERSION-darwin -SHA256SUM=b241878e6ec20650b041bf715ea05f7d5dc73bd24529464bd9cf68946e3132bd +SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd cd "$SOURCES_DIR" diff --git a/app/deps/adb_windows.sh b/app/deps/adb_windows.sh index de37162c..b9bd05dc 100755 --- a/app/deps/adb_windows.sh +++ b/app/deps/adb_windows.sh @@ -7,7 +7,7 @@ cd "$DEPS_DIR" VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-win.zip PROJECT_DIR=platform-tools-$VERSION-windows -SHA256SUM=24bd8bebbbb58b9870db202b5c6775c4a49992632021c60750d9d8ec8179d5f0 +SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c cd "$SOURCES_DIR" From c3d2ef1b1f4e9f9d5c98bbcf400948cf013dfbec Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 11 Jul 2025 19:27:23 +0200 Subject: [PATCH 42/83] Remove redundant ninja install for GA macOS runner The ninja package is already installed, so this triggered a warning: > ninja 1.13.0 is already installed and up-to-date. To reinstall 1.13.0, > run: brew reinstall ninja --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49402a6e..27e3abf2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -202,8 +202,7 @@ jobs: - name: Install dependencies run: | - brew install meson ninja nasm libiconv zlib automake autoconf \ - libtool + brew install meson nasm libiconv zlib automake autoconf libtool - name: Build env: @@ -245,7 +244,7 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies - run: brew install meson ninja nasm libiconv zlib automake + run: brew install meson nasm libiconv zlib automake # autoconf and libtool are already installed on macos-13 - name: Build From 30bfc80f9bf0c76572e2f2ab282042fb984dde3a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 4 Jul 2025 17:43:49 +0200 Subject: [PATCH 43/83] Fix style for 80-char limit --- app/src/util/vecdeque.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/util/vecdeque.h b/app/src/util/vecdeque.h index e31724e2..6f34a4ef 100644 --- a/app/src/util/vecdeque.h +++ b/app/src/util/vecdeque.h @@ -191,7 +191,8 @@ sc_vecdeque_reallocdata_(void *ptr, size_t newcap, size_t item_size, size_t right_len = MIN(size, oldcap - oldorigin); assert(right_len); - memcpy(newptr, (char *) ptr + (oldorigin * item_size), right_len * item_size); + memcpy(newptr, (char *) ptr + (oldorigin * item_size), + right_len * item_size); if (size > right_len) { memcpy((char *) newptr + (right_len * item_size), ptr, From 0522d02d40ac561969b7e148406a98002ab45781 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Jul 2025 22:59:16 +0200 Subject: [PATCH 44/83] Add missing includes The headers were implicitly included recursively, but include them explicitly. --- app/src/main.c | 1 + app/src/util/net.c | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main.c b/app/src/main.c index c58e0be7..968b1934 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -1,6 +1,7 @@ #include "common.h" #include +#include #ifdef HAVE_V4L2 # include #endif diff --git a/app/src/util/net.c b/app/src/util/net.c index 9562ff6b..9e9e3ae4 100644 --- a/app/src/util/net.c +++ b/app/src/util/net.c @@ -2,6 +2,7 @@ #include #include +#include #ifdef _WIN32 # include From eb576c44f82b57d7a3711b3c037e9c5b63b000cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 10 Jul 2025 23:01:05 +0200 Subject: [PATCH 45/83] Replace __WINDOWS__ by _WIN32 Replace the SDL2-specific preprocessor macro __WINDOWS__ by the "standard" _WIN32 macro. --- app/src/adb/adb.c | 10 +++++----- app/src/screen.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 9e9cfd6b..52ed4592 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -103,7 +103,7 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) { static void show_adb_installation_msg(void) { -#ifndef __WINDOWS__ +#ifndef _WIN32 static const struct { const char *binary; const char *command; @@ -331,7 +331,7 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, bool sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, const char *remote, unsigned flags) { -#ifdef __WINDOWS__ +#ifdef _WIN32 // Windows will parse the string, so the paths must be quoted // (see sys/win/command.c) local = sc_str_quote(local); @@ -351,7 +351,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, sc_pid pid = sc_adb_execute(argv, flags); -#ifdef __WINDOWS__ +#ifdef _WIN32 free((void *) remote); free((void *) local); #endif @@ -362,7 +362,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local, bool sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, unsigned flags) { -#ifdef __WINDOWS__ +#ifdef _WIN32 // Windows will parse the string, so the local name must be quoted // (see sys/win/command.c) local = sc_str_quote(local); @@ -377,7 +377,7 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local, sc_pid pid = sc_adb_execute(argv, flags); -#ifdef __WINDOWS__ +#ifdef _WIN32 free((void *) local); #endif diff --git a/app/src/screen.c b/app/src/screen.c index 2f325e05..da17df0e 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -225,7 +225,7 @@ sc_screen_render_novideo(struct sc_screen *screen) { (void) res; // any error already logged } -#if defined(__APPLE__) || defined(__WINDOWS__) +#if defined(__APPLE__) || defined(_WIN32) # define CONTINUOUS_RESIZING_WORKAROUND #endif From 939c8e7f68583f444bd3f2dd4aa8b02f0ca11ddc Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 17 Jul 2025 18:26:44 +0200 Subject: [PATCH 46/83] Simplify settings access For Android >= 12, scrcpy executed "settings" commands (in a new process) rather than using the ContentProvider directly, due to permission issues [1]. However, these permission issues were resolved by introducing FakeContext.getContentResolver() [2]. Therefore, remove the use of "settings" commands and use the ContentProvider directly in all cases. Refs [1] cc0902b13c87fc98b1ed90b0700cc53ac4d7ee3c Refs [2] 91373d906b100349de959f49172d4605f66f64b2 Refs #6224 comment --- .../com/genymobile/scrcpy/util/Settings.java | 63 +++---------------- 1 file changed, 9 insertions(+), 54 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/util/Settings.java b/server/src/main/java/com/genymobile/scrcpy/util/Settings.java index e6465525..4eb67d18 100644 --- a/server/src/main/java/com/genymobile/scrcpy/util/Settings.java +++ b/server/src/main/java/com/genymobile/scrcpy/util/Settings.java @@ -1,13 +1,8 @@ package com.genymobile.scrcpy.util; -import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.wrappers.ContentProvider; import com.genymobile.scrcpy.wrappers.ServiceManager; -import android.os.Build; - -import java.io.IOException; - public final class Settings { public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM; @@ -18,66 +13,26 @@ public final class Settings { /* not instantiable */ } - private static void execSettingsPut(String table, String key, String value) throws SettingsException { - try { - Command.exec("settings", "put", table, key, value); - } catch (IOException | InterruptedException e) { - throw new SettingsException("put", table, key, value, e); - } - } - - private static String execSettingsGet(String table, String key) throws SettingsException { - try { - return Command.execReadLine("settings", "get", table, key); - } catch (IOException | InterruptedException e) { - throw new SettingsException("get", table, key, null, e); - } - } - public static String getValue(String table, String key) throws SettingsException { - if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) { - // on Android >= 12, it always fails: - try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { - return provider.getValue(table, key); - } catch (SettingsException e) { - Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e); - } + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + return provider.getValue(table, key); } - - return execSettingsGet(table, key); } public static void putValue(String table, String key, String value) throws SettingsException { - if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) { - // on Android >= 12, it always fails: - try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { - provider.putValue(table, key, value); - } catch (SettingsException e) { - Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e); - } + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + provider.putValue(table, key, value); } - execSettingsPut(table, key, value); } public static String getAndPutValue(String table, String key, String value) throws SettingsException { - if (Build.VERSION.SDK_INT <= AndroidVersions.API_30_ANDROID_11) { - // on Android >= 12, it always fails: - try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { - String oldValue = provider.getValue(table, key); - if (!value.equals(oldValue)) { - provider.putValue(table, key, value); - } - return oldValue; - } catch (SettingsException e) { - Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e); + try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) { + String oldValue = provider.getValue(table, key); + if (!value.equals(oldValue)) { + provider.putValue(table, key, value); } + return oldValue; } - - String oldValue = getValue(table, key); - if (!value.equals(oldValue)) { - putValue(table, key, value); - } - return oldValue; } } From e47529ab9ce6656c4c9c48193ce5476e15071433 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 25 Jul 2025 12:22:09 +0200 Subject: [PATCH 47/83] Fix gl_context declared type The field gl_context is initialized from SDL_GL_CreateContext(), which returns a raw SDL_GLContext, not a pointer. The type mismatch was silently ignored by SDL2 because SDL_GLContext was defined as an alias to `void *` (in SDL3, it is instead an alias to `struct SDL_GLContextState *`, so compilation fails). Refs #3895 PR #6259 Signed-off-by: Romain Vimont --- app/src/display.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/display.h b/app/src/display.h index 4de9b0a9..49110994 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -22,7 +22,7 @@ struct sc_display { struct sc_opengl gl; #ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE - SDL_GLContext *gl_context; + SDL_GLContext gl_context; #endif bool mipmaps; From 8057835a0d2913a45d7982bc4710a82b0a7f4fed Mon Sep 17 00:00:00 2001 From: Filip Buda Date: Sun, 20 Jul 2025 21:42:59 -0400 Subject: [PATCH 48/83] Catch CTRL_BREAK_EVENT signal on Windows This ensures the process can terminate properly when a CTRL_BREAK_EVENT signal is sent programmatically. PR #6244 Signed-off-by: Romain Vimont --- app/src/scrcpy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index a4c8c340..aedfdf9c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -93,7 +93,7 @@ struct scrcpy { #ifdef _WIN32 static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { - if (ctrl_type == CTRL_C_EVENT) { + if (ctrl_type == CTRL_C_EVENT || ctrl_type == CTRL_BREAK_EVENT) { sc_push_event(SDL_QUIT); return TRUE; } From afaca80b375a2fbdb966c15f52645abb578071f4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 5 Sep 2025 18:41:49 +0200 Subject: [PATCH 49/83] Fix virtual display after Android 16 upgrade Several methods have been added upstream to IDisplayWindowListener.aidl, causing an AbstractMethodError when they are called on the listener instance implemented by scrcpy. Fixes #6234 Fixes #6331 --- .../aidl/android/view/IDisplayWindowListener.aidl | 8 ++++++++ .../scrcpy/wrappers/DisplayWindowListener.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/server/src/main/aidl/android/view/IDisplayWindowListener.aidl b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl index 2b331175..3e664e81 100644 --- a/server/src/main/aidl/android/view/IDisplayWindowListener.aidl +++ b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl @@ -63,4 +63,12 @@ oneway interface IDisplayWindowListener { * Called when the keep clear ares on a display have changed. */ void onKeepClearAreasChanged(int displayId, in List restricted, in List unrestricted); + + /** + * Called when the eligibility of the desktop mode for a display have changed. + */ + void onDesktopModeEligibleChanged(int displayId); + + void onDisplayAddSystemDecorations(int displayId); + void onDisplayRemoveSystemDecorations(int displayId); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java index f2ecb158..b8b00e44 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java @@ -36,4 +36,19 @@ public class DisplayWindowListener extends IDisplayWindowListener.Stub { public void onKeepClearAreasChanged(int displayId, List restricted, List unrestricted) { // empty default implementation } + + @Override + public void onDesktopModeEligibleChanged(int displayId) { + // empty default implementation + } + + @Override + public void onDisplayAddSystemDecorations(int displayId) { + // empty default implementation + } + + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + // empty default implementation + } } From 4ee94cb84581b6171e9e5b21e2df5dfb00ea280d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Sep 2025 14:25:27 +0200 Subject: [PATCH 50/83] Workaround clipboard issue on Samsung devices Fixes #6224 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- .../main/java/com/genymobile/scrcpy/FakeContext.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index b43e9e1b..7c0f3645 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -5,7 +5,6 @@ import com.genymobile.scrcpy.wrappers.ServiceManager; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.AttributionSource; -import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; @@ -91,6 +90,11 @@ public final class FakeContext extends ContextWrapper { return this; } + @Override + public Context createPackageContext(String packageName, int flags) { + return this; + } + @Override public ContentResolver getContentResolver() { return contentResolver; @@ -104,9 +108,11 @@ public final class FakeContext extends ContextWrapper { return null; } - if (Context.CLIPBOARD_SERVICE.equals(name)) { + // "semclipboard" is a Samsung-internal service + // See + if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name)) { try { - Field field = ClipboardManager.class.getDeclaredField("mContext"); + Field field = service.getClass().getDeclaredField("mContext"); field.setAccessible(true); field.set(service, this); } catch (ReflectiveOperationException e) { From 2506d1768b2740948831aea81d7a17b6e167825f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Sep 2025 14:33:49 +0200 Subject: [PATCH 51/83] Bump version to 3.3.2 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 9c5374ae..f75a8976 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "3.3.1" + VALUE "ProductVersion", "3.3.2" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index d991d672..4a03d122 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.3.1', + version: '3.3.2', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 31092b12..7afd1e7a 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 35 - versionCode 30301 - versionName "3.3.1" + versionCode 30302 + versionName "3.3.2" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 193a9902..dd1fe623 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.3.1 +SCRCPY_VERSION_NAME=3.3.2 PLATFORM=${ANDROID_PLATFORM:-35} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} From f663bbec129b10287377580306f06bf8c50ffb6a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 6 Sep 2025 14:54:36 +0200 Subject: [PATCH 52/83] Update links to 3.3.2 --- README.md | 2 +- doc/build.md | 6 +++--- doc/linux.md | 6 +++--- doc/macos.md | 12 ++++++------ doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d886d23c..a610a8ba 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v3.3.1) +# scrcpy (v3.3.2) scrcpy diff --git a/doc/build.md b/doc/build.md index 7f76b4fd..d6e067d3 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v3.3.1`][direct-scrcpy-server] - SHA-256: `a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8` + - [`scrcpy-server-v3.3.2`][direct-scrcpy-server] + SHA-256: `2ee5ca0863ef440f5b7c75856bb475c5283d0a8359cb370b1c161314fd29dfd9` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-server-v3.3.2 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/linux.md b/doc/linux.md index be433df4..7498ed98 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,11 +6,11 @@ Download a static build of the [latest release]: - - [`scrcpy-linux-x86_64-v3.3.1.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `bbfe54c6b178adafeaffbbfbbc1548a74486553170c63e63bdd41863ad123422` + - [`scrcpy-linux-x86_64-v3.3.2.tar.gz`][direct-linux-x86_64] (x86_64) + SHA-256: `92bed0fa274b9165eb8740e07cf2e2692ebe09ad6911175b0ee42e08799dc51c` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-linux-x86_64-v3.3.1.tar.gz +[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-linux-x86_64-v3.3.2.tar.gz and extract it. diff --git a/doc/macos.md b/doc/macos.md index f6b01c30..81e0d65d 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -6,15 +6,15 @@ Download a static build of the [latest release]: - - [`scrcpy-macos-aarch64-v3.3.1.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `907b925900ebd8499c1e47acc9689a95bd3a6f9930eb1d7bdfbca8375ae4f139` + - [`scrcpy-macos-aarch64-v3.3.2.tar.gz`][direct-macos-aarch64] (aarch64) + SHA-256: `a213eeff8ac95893e69c4bc6a001a402c6680dbfcb74cb353c0124184ed88e8d` - - [`scrcpy-macos-x86_64-v3.3.1.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `69772491dad718eea82fc65c8e89febff7d41c4ce6faff02f4789a588d10fd7d` + - [`scrcpy-macos-x86_64-v3.3.2.tar.gz`][direct-macos-x86_64] (x86_64) + SHA-256: `2a1b27fbb67821a886c7e8dea641899836c0abbe7afd37905584b99bcd21bc04` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-aarch64-v3.3.1.tar.gz -[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-x86_64-v3.3.1.tar.gz +[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-macos-aarch64-v3.3.2.tar.gz +[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-macos-x86_64-v3.3.2.tar.gz and extract it. diff --git a/doc/windows.md b/doc/windows.md index 8fa1921f..63fff892 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -6,14 +6,14 @@ Download the [latest release]: - - [`scrcpy-win64-v3.3.1.zip`][direct-win64] (64-bit) - SHA-256: `4fcad494772a3ae5de9a133149f8856d2fc429b41795f7cf7c754e0c1bb6fbc0` - - [`scrcpy-win32-v3.3.1.zip`][direct-win32] (32-bit) - SHA-256: `ccdf1b4f5d19dfe760446a107e55b0a010a00e097d46533a161499c9333a20a6` + - [`scrcpy-win64-v3.3.2.zip`][direct-win64] (64-bit) + SHA-256: `8f7b19371657b872e271e6b02a0c758c61c6e31e032e9df55a83aa3aab960bfa` + - [`scrcpy-win32-v3.3.2.zip`][direct-win32] (32-bit) + SHA-256: `cff2bbebdcfe14a023b77cd601fc4420b5631b19bd4b09ce4dcd4e5bf8e63244` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win64-v3.3.1.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win32-v3.3.1.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-win64-v3.3.2.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-win32-v3.3.2.zip and extract it. diff --git a/install_release.sh b/install_release.sh index d960932b..f6e94dae 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1 -PREBUILT_SERVER_SHA256=a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-server-v3.3.2 +PREBUILT_SERVER_SHA256=2ee5ca0863ef440f5b7c75856bb475c5283d0a8359cb370b1c161314fd29dfd9 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 9d56d26d454551d5fae5631fe92c72be0c495abf Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 11 Sep 2025 15:36:14 +0300 Subject: [PATCH 53/83] Make virtual display presentable With this flag, apps with baked in two-screen support can see the virtual display as an external display they can present to. PR #6344 Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/video/NewDisplayCapture.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 792b3a8a..e933f60e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -25,6 +25,7 @@ public class NewDisplayCapture extends SurfaceCapture { // Internal fields copied from android.hardware.display.DisplayManager private static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; + private static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; private static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; private static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6; private static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; @@ -169,6 +170,7 @@ public class NewDisplayCapture extends SurfaceCapture { int virtualDisplayId; try { int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC + | VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; From e11399aff0562a45e397beb9a98743be1c411710 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 18 Sep 2025 10:01:29 +0200 Subject: [PATCH 54/83] Ignore unknown methods in IDisplayWindowListener New Android versions may add methods to IDisplayWindowListener.aidl. When these methods are called by the system, they result in an AbstractMethodError because they are not implemented on the scrcpy side. To avoid releasing a new version for each newly added method, ignore them at the Binder level. Refs afaca80b375a2fbdb966c15f52645abb578071f4 Fixes #6362 --- .../android/view/IDisplayWindowListener.aidl | 23 ---------- .../wrappers/DisplayWindowListener.java | 45 ++++++------------- 2 files changed, 14 insertions(+), 54 deletions(-) diff --git a/server/src/main/aidl/android/view/IDisplayWindowListener.aidl b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl index 3e664e81..0d1f1979 100644 --- a/server/src/main/aidl/android/view/IDisplayWindowListener.aidl +++ b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl @@ -48,27 +48,4 @@ oneway interface IDisplayWindowListener { * Called when a display is removed from the hierarchy. */ void onDisplayRemoved(int displayId); - - /** - * Called when fixed rotation is started on a display. - */ - void onFixedRotationStarted(int displayId, int newRotation); - - /** - * Called when the previous fixed rotation on a display is finished. - */ - void onFixedRotationFinished(int displayId); - - /** - * Called when the keep clear ares on a display have changed. - */ - void onKeepClearAreasChanged(int displayId, in List restricted, in List unrestricted); - - /** - * Called when the eligibility of the desktop mode for a display have changed. - */ - void onDesktopModeEligibleChanged(int displayId); - - void onDisplayAddSystemDecorations(int displayId); - void onDisplayRemoveSystemDecorations(int displayId); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java index b8b00e44..1573d817 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java @@ -1,10 +1,11 @@ package com.genymobile.scrcpy.wrappers; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.view.IDisplayWindowListener; +import com.genymobile.scrcpy.util.Ln; -import java.util.List; +import android.content.res.Configuration; +import android.os.Parcel; +import android.os.RemoteException; +import android.view.IDisplayWindowListener; public class DisplayWindowListener extends IDisplayWindowListener.Stub { @Override @@ -23,32 +24,14 @@ public class DisplayWindowListener extends IDisplayWindowListener.Stub { } @Override - public void onFixedRotationStarted(int displayId, int newRotation) { - // empty default implementation - } - - @Override - public void onFixedRotationFinished(int displayId) { - // empty default implementation - } - - @Override - public void onKeepClearAreasChanged(int displayId, List restricted, List unrestricted) { - // empty default implementation - } - - @Override - public void onDesktopModeEligibleChanged(int displayId) { - // empty default implementation - } - - @Override - public void onDisplayAddSystemDecorations(int displayId) { - // empty default implementation - } - - @Override - public void onDisplayRemoveSystemDecorations(int displayId) { - // empty default implementation + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (AbstractMethodError e) { + Ln.v("Ignoring AbstractMethodError: " + e.getMessage()); + // Ignore unknown methods, write default response to reply parcel + reply.writeNoException(); + return true; + } } } From bfb0872493ee49aaccb000afaa62187f46b7e990 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 23 Sep 2025 20:17:27 +0200 Subject: [PATCH 55/83] Avoid resetting pending frame The function update_texture() calls update_texture_internal() and falls back to set_pending_frame() if it fails. When the frame passed is the pending frame, call only the _internal() version instead. This will prevent issues with frame reference counts by ensuring the source and destination frames are never the same. Refs 6298ef095ffa9a2cdd2b4d245e71280743b5a59e Refs #6357 --- app/src/display.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/display.c b/app/src/display.c index aee8ef80..200118e6 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -181,6 +181,11 @@ sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) { return true; } +// Forward declaration +static bool +sc_display_update_texture_internal(struct sc_display *display, + const AVFrame *frame); + static bool sc_display_apply_pending(struct sc_display *display) { if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) { @@ -196,7 +201,8 @@ sc_display_apply_pending(struct sc_display *display) { if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) { assert(display->pending.frame); - bool ok = sc_display_update_texture(display, display->pending.frame); + bool ok = sc_display_update_texture_internal(display, + display->pending.frame); if (!ok) { return false; } From be21e43be585a1595ee4ad41acacc25f3cffaebe Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 23 Sep 2025 20:18:26 +0200 Subject: [PATCH 56/83] Fix frame leak on pending frame update The previous pending frame was not unreferenced before referencing the new one, causing frames to leak whenever a texture update failed (typically on Windows when the window is minimized with D3D9). Refs 6298ef095ffa9a2cdd2b4d245e71280743b5a59e Fixes #4297 Fixes #6357 --- app/src/display.c | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/display.c b/app/src/display.c index 200118e6..15f9a1f1 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -170,6 +170,7 @@ sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) { } } + av_frame_unref(display->pending.frame); int r = av_frame_ref(display->pending.frame, frame); if (r) { LOGE("Could not ref frame: %d", r); From 10a0974f43cce259ba6c36346e3d962bf06e5259 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 23 Sep 2025 21:16:29 +0200 Subject: [PATCH 57/83] Bump version to 3.3.3 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index f75a8976..196a547c 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "3.3.2" + VALUE "ProductVersion", "3.3.3" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 4a03d122..7abe6a8c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.3.2', + version: '3.3.3', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 7afd1e7a..dbe889e0 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 35 - versionCode 30302 - versionName "3.3.2" + versionCode 30303 + versionName "3.3.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index dd1fe623..edc4f793 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.3.2 +SCRCPY_VERSION_NAME=3.3.3 PLATFORM=${ANDROID_PLATFORM:-35} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} From e5e58b1b307d92cbe3432431a9e22cd648f8d4d1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 27 Sep 2025 16:10:10 +0200 Subject: [PATCH 58/83] Upgrade links to 3.3.3 --- README.md | 2 +- doc/build.md | 6 +++--- doc/linux.md | 6 +++--- doc/macos.md | 12 ++++++------ doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index a610a8ba..195706f6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v3.3.2) +# scrcpy (v3.3.3) scrcpy diff --git a/doc/build.md b/doc/build.md index d6e067d3..1c228fb8 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v3.3.2`][direct-scrcpy-server] - SHA-256: `2ee5ca0863ef440f5b7c75856bb475c5283d0a8359cb370b1c161314fd29dfd9` + - [`scrcpy-server-v3.3.3`][direct-scrcpy-server] + SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-server-v3.3.2 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/linux.md b/doc/linux.md index 7498ed98..39e92961 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,11 +6,11 @@ Download a static build of the [latest release]: - - [`scrcpy-linux-x86_64-v3.3.2.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `92bed0fa274b9165eb8740e07cf2e2692ebe09ad6911175b0ee42e08799dc51c` + - [`scrcpy-linux-x86_64-v3.3.3.tar.gz`][direct-linux-x86_64] (x86_64) + SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-linux-x86_64-v3.3.2.tar.gz +[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz and extract it. diff --git a/doc/macos.md b/doc/macos.md index 81e0d65d..7ba08c85 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -6,15 +6,15 @@ Download a static build of the [latest release]: - - [`scrcpy-macos-aarch64-v3.3.2.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `a213eeff8ac95893e69c4bc6a001a402c6680dbfcb74cb353c0124184ed88e8d` + - [`scrcpy-macos-aarch64-v3.3.3.tar.gz`][direct-macos-aarch64] (aarch64) + SHA-256: `b93299468f19ae89ac70f7c1453914c41f1f2bcd31f6ab530038da885c19581f` - - [`scrcpy-macos-x86_64-v3.3.2.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `2a1b27fbb67821a886c7e8dea641899836c0abbe7afd37905584b99bcd21bc04` + - [`scrcpy-macos-x86_64-v3.3.3.tar.gz`][direct-macos-x86_64] (x86_64) + SHA-256: `c767fc1d41e4ae26e40558656570962f474739924fd22ee023d8754889ee4366` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-macos-aarch64-v3.3.2.tar.gz -[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-macos-x86_64-v3.3.2.tar.gz +[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-aarch64-v3.3.3.tar.gz +[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-x86_64-v3.3.3.tar.gz and extract it. diff --git a/doc/windows.md b/doc/windows.md index 63fff892..b6dc220f 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -6,14 +6,14 @@ Download the [latest release]: - - [`scrcpy-win64-v3.3.2.zip`][direct-win64] (64-bit) - SHA-256: `8f7b19371657b872e271e6b02a0c758c61c6e31e032e9df55a83aa3aab960bfa` - - [`scrcpy-win32-v3.3.2.zip`][direct-win32] (32-bit) - SHA-256: `cff2bbebdcfe14a023b77cd601fc4420b5631b19bd4b09ce4dcd4e5bf8e63244` + - [`scrcpy-win64-v3.3.3.zip`][direct-win64] (64-bit) + SHA-256: `4b458d33d0436688c69875cd267cae6fa8be08aa3c17772edf3a940a3dc4b17e` + - [`scrcpy-win32-v3.3.3.zip`][direct-win32] (32-bit) + SHA-256: `e3d43e21c0bd6e070381c390c1e4cccd48a1e71ae73a8c217e6e6b8506598c79` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-win64-v3.3.2.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-win32-v3.3.2.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win64-v3.3.3.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win32-v3.3.3.zip and extract it. diff --git a/install_release.sh b/install_release.sh index f6e94dae..3419a9bc 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.2/scrcpy-server-v3.3.2 -PREBUILT_SERVER_SHA256=2ee5ca0863ef440f5b7c75856bb475c5283d0a8359cb370b1c161314fd29dfd9 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3 +PREBUILT_SERVER_SHA256=7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 9d7a4c88e067dc523bd16565e1592717dcaca360 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 5 Oct 2025 21:34:59 +0200 Subject: [PATCH 59/83] Apply workarounds in the cleanup process Accessing settings may require workarounds on certain devices. Fixes #6405 --- server/src/main/java/com/genymobile/scrcpy/CleanUp.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java index 77018afa..a5816c32 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CleanUp.java +++ b/server/src/main/java/com/genymobile/scrcpy/CleanUp.java @@ -196,6 +196,7 @@ public final class CleanUp { // Needed for workarounds prepareMainLooper(); + Workarounds.apply(); int displayId = Integer.parseInt(args[0]); int restoreStayOn = Integer.parseInt(args[1]); From 3e40b2473772cea3a23d4932088fd0bc4cc0f52c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 9 Oct 2025 09:26:47 +0200 Subject: [PATCH 60/83] Fix UHID_OUTPUT message parsing The bounds check was incorrect. Fixes #6415 --- app/src/device_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 7621c040..2172d59b 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -53,7 +53,7 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len, } uint16_t id = sc_read16be(&buf[1]); size_t size = sc_read16be(&buf[3]); - if (size < len - 5) { + if (size > len - 5) { return 0; // not available } uint8_t *data = malloc(size); From eee3f24739dfcff2ebc428e9b2b19eb823641849 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Oct 2025 17:02:35 +0200 Subject: [PATCH 61/83] Upgrade Gradle and use Android SDK 36 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- server/build.gradle | 17 ++++++++++------- server/build_without_gradle.sh | 4 ++-- .../main/java/com/genymobile/scrcpy/Server.java | 4 ++++ 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 81c91d37..8c671e6f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.1' + classpath 'com.android.tools.build:gradle:8.13.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b34b7096..fbd6c374 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip # https://gradle.org/release-checksums/ -distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/server/build.gradle b/server/build.gradle index dbe889e0..3dfc7144 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.application' android { - namespace 'com.genymobile.scrcpy' - compileSdk 35 + namespace = 'com.genymobile.scrcpy' + compileSdk 36 defaultConfig { - applicationId "com.genymobile.scrcpy" + applicationId = "com.genymobile.scrcpy" minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 versionCode 30303 versionName "3.3.3" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -18,8 +18,11 @@ android { } } buildFeatures { - buildConfig true - aidl true + buildConfig = true + aidl = true + } + lint { + disable 'UseRequiresApi' } } diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index edc4f793..8c572f62 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -14,8 +14,8 @@ set -e SCRCPY_DEBUG=false SCRCPY_VERSION_NAME=3.3.3 -PLATFORM=${ANDROID_PLATFORM:-35} -BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} +PLATFORM=${ANDROID_PLATFORM:-36} +BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0} PLATFORM_TOOLS="$ANDROID_HOME/platforms/android-$PLATFORM" BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS" diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a08c948c..a675f1e7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -226,7 +226,11 @@ public final class Server { private static void internalMain(String... args) throws Exception { Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Ln.e("Exception on thread " + t, e); + if (defaultHandler != null) { + defaultHandler.uncaughtException(t, e); + } }); prepareMainLooper(); From f3d4fde15b51fd85a59c835d0310aac19d429737 Mon Sep 17 00:00:00 2001 From: Yan Date: Sun, 5 Oct 2025 22:53:22 +0200 Subject: [PATCH 62/83] Fix handling of non-integer ANDROID_PLATFORM ANDROID_PLATFORM is not always an integer; it can also be a value like "36.1". Handle such cases properly. This fixes the following error: server/build_without_gradle.sh: line 89: [[: 36.1: syntax error: invalid arithmetic operator (error token is ".1") PR #6408 Signed-off-by: Romain Vimont --- server/build_without_gradle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 8c572f62..bb477a32 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -86,7 +86,7 @@ javac -encoding UTF-8 -bootclasspath "$ANDROID_JAR" \ echo "Dexing..." cd "$CLASSES_DIR" -if [[ $PLATFORM -lt 31 ]] +if [[ "${PLATFORM%%.*}" -lt 31 ]] then # use dx "$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \ From 925949d54ad88a595c8470b2e22b9cad10d98dfa Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Oct 2025 22:26:51 +0100 Subject: [PATCH 63/83] Refactor dependency build scripts initialization Rename "common" to "_init" because it not only exposes common functions but also initializes environment variables. Call _init in a single line in all dependency build scripts. --- app/deps/{common => _init} | 7 +++---- app/deps/adb_linux.sh | 4 +--- app/deps/adb_macos.sh | 4 +--- app/deps/adb_windows.sh | 4 +--- app/deps/dav1d.sh | 4 +--- app/deps/ffmpeg.sh | 4 +--- app/deps/libusb.sh | 4 +--- app/deps/sdl.sh | 4 +--- 8 files changed, 10 insertions(+), 25 deletions(-) rename app/deps/{common => _init} (92%) diff --git a/app/deps/common b/app/deps/_init similarity index 92% rename from app/deps/common rename to app/deps/_init index daaa96c0..4612d9fe 100644 --- a/app/deps/common +++ b/app/deps/_init @@ -1,10 +1,9 @@ -#!/usr/bin/env bash # This file is intended to be sourced by other scripts, not executed process_args() { if [[ $# != 3 ]] then - # : win32 or win64 + # : linux, macos, win32 or win64 # : native or cross # : static or shared echo "Syntax: $0 " >&2 @@ -12,8 +11,8 @@ process_args() { fi HOST="$1" - BUILD_TYPE="$2" # native or cross - LINK_TYPE="$3" # static or shared + BUILD_TYPE="$2" + LINK_TYPE="$3" DIRNAME="$HOST-$BUILD_TYPE-$LINK_TYPE" if [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]] diff --git a/app/deps/adb_linux.sh b/app/deps/adb_linux.sh index a3e339ec..c284dd24 100755 --- a/app/deps/adb_linux.sh +++ b/app/deps/adb_linux.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-linux.zip diff --git a/app/deps/adb_macos.sh b/app/deps/adb_macos.sh index 6880fb93..e580e571 100755 --- a/app/deps/adb_macos.sh +++ b/app/deps/adb_macos.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-darwin.zip diff --git a/app/deps/adb_windows.sh b/app/deps/adb_windows.sh index b9bd05dc..da95bb81 100755 --- a/app/deps/adb_windows.sh +++ b/app/deps/adb_windows.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 FILENAME=platform-tools_r$VERSION-win.zip diff --git a/app/deps/dav1d.sh b/app/deps/dav1d.sh index 3069b6fe..89956636 100755 --- a/app/deps/dav1d.sh +++ b/app/deps/dav1d.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init process_args "$@" VERSION=1.5.0 diff --git a/app/deps/ffmpeg.sh b/app/deps/ffmpeg.sh index fb8b9a25..a879669d 100755 --- a/app/deps/ffmpeg.sh +++ b/app/deps/ffmpeg.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init process_args "$@" VERSION=7.1.1 diff --git a/app/deps/libusb.sh b/app/deps/libusb.sh index 887a2a77..d1dd4cbf 100755 --- a/app/deps/libusb.sh +++ b/app/deps/libusb.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init process_args "$@" VERSION=1.0.29 diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh index e04deb0d..d382c183 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -ex -DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) -cd "$DEPS_DIR" -. common +. $(dirname ${BASH_SOURCE[0]})/_init process_args "$@" VERSION=2.32.8 From 3281fda6ef6189b173a464c845a87511bfaf62e9 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 Oct 2025 22:31:16 +0100 Subject: [PATCH 64/83] Set URL explicitly in dependency build scripts Explicitly set the URL of each dependency at the beginning of its script. PROJECT_DIR and FILENAME are internal details. --- app/deps/adb_linux.sh | 8 +++++--- app/deps/adb_macos.sh | 8 +++++--- app/deps/adb_windows.sh | 8 +++++--- app/deps/dav1d.sh | 8 +++++--- app/deps/ffmpeg.sh | 8 +++++--- app/deps/libusb.sh | 8 +++++--- app/deps/sdl.sh | 11 +++++++---- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/app/deps/adb_linux.sh b/app/deps/adb_linux.sh index c284dd24..e47edf88 100755 --- a/app/deps/adb_linux.sh +++ b/app/deps/adb_linux.sh @@ -3,17 +3,19 @@ set -ex . $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 -FILENAME=platform-tools_r$VERSION-linux.zip -PROJECT_DIR=platform-tools-$VERSION-linux +URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-linux.zip" SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8 +PROJECT_DIR="platform-tools-$VERSION-linux" +FILENAME="$PROJECT_DIR.zip" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM" + get_file "$URL" "$FILENAME" "$SHA256SUM" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" ZIP_PREFIX=platform-tools diff --git a/app/deps/adb_macos.sh b/app/deps/adb_macos.sh index e580e571..a56117dc 100755 --- a/app/deps/adb_macos.sh +++ b/app/deps/adb_macos.sh @@ -3,17 +3,19 @@ set -ex . $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 -FILENAME=platform-tools_r$VERSION-darwin.zip -PROJECT_DIR=platform-tools-$VERSION-darwin +URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-darwin.zip" SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd +PROJECT_DIR="platform-tools-$VERSION-darwin" +FILENAME="$PROJECT_DIR.zip" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM" + get_file "$URL" "$FILENAME" "$SHA256SUM" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" ZIP_PREFIX=platform-tools diff --git a/app/deps/adb_windows.sh b/app/deps/adb_windows.sh index da95bb81..69287b1d 100755 --- a/app/deps/adb_windows.sh +++ b/app/deps/adb_windows.sh @@ -3,17 +3,19 @@ set -ex . $(dirname ${BASH_SOURCE[0]})/_init "$@" VERSION=36.0.0 -FILENAME=platform-tools_r$VERSION-win.zip -PROJECT_DIR=platform-tools-$VERSION-windows +URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-win.zip" SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c +PROJECT_DIR="platform-tools-$VERSION-windows" +FILENAME="$PROJECT_DIR.zip" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM" + get_file "$URL" "$FILENAME" "$SHA256SUM" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" ZIP_PREFIX=platform-tools diff --git a/app/deps/dav1d.sh b/app/deps/dav1d.sh index 89956636..46fa3954 100755 --- a/app/deps/dav1d.sh +++ b/app/deps/dav1d.sh @@ -4,17 +4,19 @@ set -ex process_args "$@" VERSION=1.5.0 -FILENAME=dav1d-$VERSION.tar.gz -PROJECT_DIR=dav1d-$VERSION +URL="https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/dav1d-$VERSION.tar.gz" SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b +PROJECT_DIR="dav1d-$VERSION" +FILENAME="$PROJECT_DIR.tar.gz" + 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" + get_file "$URL" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" fi diff --git a/app/deps/ffmpeg.sh b/app/deps/ffmpeg.sh index a879669d..d6cfad54 100755 --- a/app/deps/ffmpeg.sh +++ b/app/deps/ffmpeg.sh @@ -4,17 +4,19 @@ set -ex process_args "$@" VERSION=7.1.1 -FILENAME=ffmpeg-$VERSION.tar.xz -PROJECT_DIR=ffmpeg-$VERSION +URL="https://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz" SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1 +PROJECT_DIR="ffmpeg-$VERSION" +FILENAME="$PROJECT_DIR.tar.xz" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM" + get_file "$URL" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" fi diff --git a/app/deps/libusb.sh b/app/deps/libusb.sh index d1dd4cbf..72170b9e 100755 --- a/app/deps/libusb.sh +++ b/app/deps/libusb.sh @@ -4,17 +4,19 @@ set -ex process_args "$@" VERSION=1.0.29 -FILENAME=libusb-$VERSION.tar.gz -PROJECT_DIR=libusb-$VERSION +URL="https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74 +PROJECT_DIR="libusb-$VERSION" +FILENAME="$PROJECT_DIR.tar.gz" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" + get_file "$URL" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" fi diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh index d382c183..f74ccf41 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -4,18 +4,21 @@ set -ex process_args "$@" VERSION=2.32.8 -FILENAME=SDL-$VERSION.tar.gz -PROJECT_DIR=SDL-release-$VERSION +URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" SHA256SUM=dd35e05644ae527848d02433bec24dd0ea65db59faecf1a0e5d1880c533dac2c +PROJECT_DIR="sdl-$VERSION" +FILENAME="$PROJECT_DIR.tar.gz" + cd "$SOURCES_DIR" if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" - tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" + get_file "$URL" "$FILENAME" "$SHA256SUM" + tar xf "$FILENAME" # First level directory is "SDL-release-$VERSION" + mv "SDL-release-$VERSION" "$PROJECT_DIR" fi mkdir -p "$BUILD_DIR/$PROJECT_DIR" From d0047b2110e2bd70ac2b27e7cec012747b01dfb7 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 1 Nov 2025 00:34:20 +0100 Subject: [PATCH 65/83] Do not fail when uniqueId field is missing On some devices, DisplayInfo does not have a "uniqueId" field. This field is only used for correct UHID behavior on virtual displays, so its absence should not prevent scrcpy from working. Refs #6009 Fixes #6461 --- .../com/genymobile/scrcpy/wrappers/DisplayManager.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index a12470a4..9e84ec00 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -139,7 +139,13 @@ public final class DisplayManager { int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); int flags = cls.getDeclaredField("flags").getInt(displayInfo); int dpi = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo); - String uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo); + String uniqueId; + try { + uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo); + } catch (NoSuchFieldException e) { + // This field might not exist: + uniqueId = null; + } return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId); } catch (ReflectiveOperationException e) { throw new AssertionError(e); From 35308510712ebf464c58d5aaebd4da9515f9098c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 4 Nov 2025 20:35:39 +0100 Subject: [PATCH 66/83] Fix uncaught exception handler The default handler was mistakenly retrieved after our custom handler was set, causing it to reference itself. As a result, this led to infinite recursion. Bug introduced by eee3f24739dfcff2ebc428e9b2b19eb823641849. --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a675f1e7..04e4a837 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -225,8 +225,8 @@ public final class Server { } private static void internalMain(String... args) throws Exception { + Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler((t, e) -> { - Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Ln.e("Exception on thread " + t, e); if (defaultHandler != null) { defaultHandler.uncaughtException(t, e); From 7dd9bcaf606eca7f785ab71423aa113c214522a5 Mon Sep 17 00:00:00 2001 From: valord577 Date: Tue, 18 Nov 2025 15:23:07 +0800 Subject: [PATCH 67/83] Prevent error log interleaving Between the calls to CONSOLE_ERR.print() and printStackTrace(CONSOLE_ERR), logs from other threads may be inserted. Synchronizing access to CONSOLE_ERR ensures that logs from different threads do not mix. PR #6487 Signed-off-by: valord577 Signed-off-by: Romain Vimont --- .../main/java/com/genymobile/scrcpy/util/Ln.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/util/Ln.java b/server/src/main/java/com/genymobile/scrcpy/util/Ln.java index c0700125..811a4f31 100644 --- a/server/src/main/java/com/genymobile/scrcpy/util/Ln.java +++ b/server/src/main/java/com/genymobile/scrcpy/util/Ln.java @@ -74,9 +74,11 @@ public final class Ln { public static void w(String message, Throwable throwable) { if (isEnabled(Level.WARN)) { Log.w(TAG, message, throwable); - CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n'); - if (throwable != null) { - throwable.printStackTrace(CONSOLE_ERR); + synchronized (CONSOLE_ERR) { + CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n'); + if (throwable != null) { + throwable.printStackTrace(CONSOLE_ERR); + } } } } @@ -88,9 +90,11 @@ public final class Ln { public static void e(String message, Throwable throwable) { if (isEnabled(Level.ERROR)) { Log.e(TAG, message, throwable); - CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n'); - if (throwable != null) { - throwable.printStackTrace(CONSOLE_ERR); + synchronized (CONSOLE_ERR) { + CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n'); + if (throwable != null) { + throwable.printStackTrace(CONSOLE_ERR); + } } } } From b08093d1c09d3fff1238abfa57e3efddeadcf41c Mon Sep 17 00:00:00 2001 From: paradoxskin <1312269430@qq.com> Date: Wed, 19 Nov 2025 23:14:46 +0800 Subject: [PATCH 68/83] Fix incorrect icon filename in build documentation The installed icon was listed as `icon.png`, but the actual filename is `scrcpy.png`. PR #6490 Signed-off-by: Romain Vimont --- doc/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.md b/doc/build.md index 1c228fb8..fd272fde 100644 --- a/doc/build.md +++ b/doc/build.md @@ -271,7 +271,7 @@ This installs several files: - `/usr/local/bin/scrcpy` (main app) - `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device) - `/usr/local/share/man/man1/scrcpy.1` (manpage) - - `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon) + - `/usr/local/share/icons/hicolor/256x256/apps/scrcpy.png` (app icon) - `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion) - `/usr/local/share/bash-completion/completions/scrcpy` (bash completion) From 9cfa5b197a1b94acc7cece4b91c4173c5fe1c181 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 23 Nov 2025 14:33:50 +0100 Subject: [PATCH 69/83] Create Application instance via instrumentation This fixes an issue on certain Meizu devices. Fixes #6480 --- .../src/main/java/com/genymobile/scrcpy/Workarounds.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index b89f19ae..1e012de6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -6,9 +6,9 @@ import com.genymobile.scrcpy.util.Ln; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Application; +import android.app.Instrumentation; import android.content.AttributionSource; import android.content.Context; -import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.media.AudioAttributes; import android.media.AudioManager; @@ -103,10 +103,7 @@ public final class Workarounds { private static void fillAppContext() { try { - Application app = new Application(); - Field baseField = ContextWrapper.class.getDeclaredField("mBase"); - baseField.setAccessible(true); - baseField.set(app, FakeContext.get()); + Application app = Instrumentation.newApplication(Application.class, FakeContext.get()); // activityThread.mInitialApplication = app; Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication"); From 6f9eb31d529a520bbf90febd62096f84a286ddb2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 25 Nov 2025 00:06:10 +0100 Subject: [PATCH 70/83] Add missing test for START_APP serialization A test for Java deserialization of the START_APP control message was already present, but the corresponding C-side serialization test was missing. Refs 13ce277e1f1eec6312350c5a3a3ac3be7b9be6e1 --- app/tests/test_control_msg_serialize.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 0d19919e..e43c7ce4 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -411,6 +411,26 @@ static void test_serialize_open_hard_keyboard(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_start_app(void) { + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_START_APP, + .start_app = { + .name = "firefox", + }, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 9); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_START_APP, + 7, // length + 'f', 'i', 'r', 'e', 'f', 'o', 'x', // app name + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + static void test_serialize_reset_video(void) { struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_RESET_VIDEO, @@ -448,6 +468,7 @@ int main(int argc, char *argv[]) { test_serialize_uhid_input(); test_serialize_uhid_destroy(); test_serialize_open_hard_keyboard(); + test_serialize_start_app(); test_serialize_reset_video(); return 0; } From 7e66062086aaf085f9315a89f60b170bb105dc72 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 Nov 2025 22:31:26 +0100 Subject: [PATCH 71/83] Extract function to execute code on a handler Extract function to synchronously execute code on a handler, waiting for the execution to complete. --- .../scrcpy/opengl/OpenGLRunner.java | 32 +++++--------- .../com/genymobile/scrcpy/util/Threads.java | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/util/Threads.java diff --git a/server/src/main/java/com/genymobile/scrcpy/opengl/OpenGLRunner.java b/server/src/main/java/com/genymobile/scrcpy/opengl/OpenGLRunner.java index 86bd1859..06bcd7eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/opengl/OpenGLRunner.java +++ b/server/src/main/java/com/genymobile/scrcpy/opengl/OpenGLRunner.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy.opengl; import com.genymobile.scrcpy.device.Size; +import com.genymobile.scrcpy.util.Threads; import android.graphics.SurfaceTexture; import android.opengl.EGL14; @@ -15,6 +16,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.view.Surface; +import java.util.concurrent.Callable; import java.util.concurrent.Semaphore; public final class OpenGLRunner { @@ -80,31 +82,17 @@ public final class OpenGLRunner { public Surface start(Size inputSize, Size outputSize, Surface outputSurface) throws OpenGLException { initOnce(); - // Simulate CompletableFuture, but working for all Android versions - final Semaphore sem = new Semaphore(0); - Throwable[] throwableRef = new Throwable[1]; - // The whole OpenGL execution must be performed on a Handler, so that SurfaceTexture.setOnFrameAvailableListener() works correctly. // See - handler.post(() -> { - try { - run(inputSize, outputSize, outputSurface); - } catch (Throwable throwable) { - throwableRef[0] = throwable; - } finally { - sem.release(); - } - }); - try { - sem.acquire(); - } catch (InterruptedException e) { - // Behave as if this method call was synchronous - Thread.currentThread().interrupt(); - } - - Throwable throwable = throwableRef[0]; - if (throwable != null) { + Threads.executeSynchronouslyOn(handler, new Callable() { + @Override + public Void call() throws Exception { + run(inputSize, outputSize, outputSurface); + return null; + } + }); + } catch (Throwable throwable) { if (throwable instanceof OpenGLException) { throw (OpenGLException) throwable; } diff --git a/server/src/main/java/com/genymobile/scrcpy/util/Threads.java b/server/src/main/java/com/genymobile/scrcpy/util/Threads.java new file mode 100644 index 00000000..c561d1ef --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/util/Threads.java @@ -0,0 +1,43 @@ +package com.genymobile.scrcpy.util; + +import android.os.Handler; + +import java.util.concurrent.Callable; +import java.util.concurrent.Semaphore; + +public final class Threads { + private Threads() { + // not instantiable + } + + public static T executeSynchronouslyOn(Handler handler, Callable callable) throws Throwable { + // Simulate CompletableFuture, but working for all Android versions + final Semaphore sem = new Semaphore(0); + @SuppressWarnings("unchecked") + T[] resultRef = (T[]) new Object[1]; + Throwable[] throwableRef = new Throwable[1]; + + handler.post(() -> { + try { + resultRef[0] = callable.call(); + } catch (Throwable throwable) { + throwableRef[0] = throwable; + } finally { + sem.release(); + } + }); + + try { + sem.acquire(); + } catch (InterruptedException e) { + // Behave as if this method call was synchronous + Thread.currentThread().interrupt(); + } + + if (throwableRef[0] != null) { + throw throwableRef[0]; + } + + return resultRef[0]; + } +} From 5b51396a8c60f2c0d198569b05d589bd34d9ce9c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 9 Dec 2025 20:20:22 +0100 Subject: [PATCH 72/83] Fix permission denial error after Android upgrade Assign the FakeContext instance to ActivityManager.mContext to avoid a permission error: Permission Denial: package=android does not belong to uid=2000 Fixes #6523 --- server/src/main/java/com/genymobile/scrcpy/FakeContext.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 7c0f3645..5d41a8f3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -109,8 +109,10 @@ public final class FakeContext extends ContextWrapper { } // "semclipboard" is a Samsung-internal service - // See - if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name)) { + // See: + // - + // - + if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name) || Context.ACTIVITY_SERVICE.equals(name)) { try { Field field = service.getClass().getDeclaredField("mContext"); field.setAccessible(true); From 1d1a4103cc7ba6a9bdb458e77011067e3ccb46c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Dec 2025 19:55:27 +0100 Subject: [PATCH 73/83] Replace macos-13 runner with macos-15-intel Refs Refs #5526 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27e3abf2..c4ca1829 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -229,7 +229,7 @@ jobs: path: release/work/build-macos-aarch64/dist-tar/ build-macos-x86_64: - runs-on: macos-13 + runs-on: macos-15-intel steps: - name: Check architecture run: | From 06fd3b47860374350ef2bd8b62dc8dfac5c28720 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Dec 2025 19:43:29 +0100 Subject: [PATCH 74/83] Bump version to 3.3.4 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 196a547c..07df992f 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "3.3.3" + VALUE "ProductVersion", "3.3.4" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 7abe6a8c..fc82dee1 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.3.3', + version: '3.3.4', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 3dfc7144..abbbb60a 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId = "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 36 - versionCode 30303 - versionName "3.3.3" + versionCode 30304 + versionName "3.3.4" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index bb477a32..435ac8f6 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.3.3 +SCRCPY_VERSION_NAME=3.3.4 PLATFORM=${ANDROID_PLATFORM:-36} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0} From fb6381f5b9bb96f3fa823d899f4c32de2ec84ab3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Dec 2025 20:18:49 +0100 Subject: [PATCH 75/83] Upgrade links to 3.3.4 --- README.md | 2 +- doc/build.md | 6 +++--- doc/linux.md | 6 +++--- doc/macos.md | 13 ++++++------- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 195706f6..07501a2c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v3.3.3) +# scrcpy (v3.3.4) scrcpy diff --git a/doc/build.md b/doc/build.md index 1c228fb8..45aaf6bf 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v3.3.3`][direct-scrcpy-server] - SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0` + - [`scrcpy-server-v3.3.4`][direct-scrcpy-server] + SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/linux.md b/doc/linux.md index 39e92961..fd9eca8c 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,11 +6,11 @@ Download a static build of the [latest release]: - - [`scrcpy-linux-x86_64-v3.3.3.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638` + - [`scrcpy-linux-x86_64-v3.3.4.tar.gz`][direct-linux-x86_64] (x86_64) + SHA-256: `0305d98c06178c67e12427bbf340c436d0d58c9e2a39bf9ffbbf8f54d7ef95a5` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz +[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-linux-x86_64-v3.3.4.tar.gz and extract it. diff --git a/doc/macos.md b/doc/macos.md index 7ba08c85..5435e613 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -6,15 +6,14 @@ Download a static build of the [latest release]: - - [`scrcpy-macos-aarch64-v3.3.3.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `b93299468f19ae89ac70f7c1453914c41f1f2bcd31f6ab530038da885c19581f` - - - [`scrcpy-macos-x86_64-v3.3.3.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `c767fc1d41e4ae26e40558656570962f474739924fd22ee023d8754889ee4366` + - [`scrcpy-macos-aarch64-v3.3.4.tar.gz`][direct-macos-aarch64] (aarch64) + SHA-256: `8fef43520405dd523c74e1530ac68febcc5a405ea89712c874936675da8513dd` + - [`scrcpy-macos-x86_64-v3.3.4.tar.gz`][direct-macos-x86_64] (x86_64) + SHA-256: `cf9b3453a33279b6009dfb256b1a84c374bd4c30a71edd74bacab28d72a5d929` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-aarch64-v3.3.3.tar.gz -[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-x86_64-v3.3.3.tar.gz +[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-aarch64-v3.3.4.tar.gz +[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-x86_64-v3.3.4.tar.gz and extract it. diff --git a/doc/windows.md b/doc/windows.md index b6dc220f..cdc30bdc 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -6,14 +6,14 @@ Download the [latest release]: - - [`scrcpy-win64-v3.3.3.zip`][direct-win64] (64-bit) - SHA-256: `4b458d33d0436688c69875cd267cae6fa8be08aa3c17772edf3a940a3dc4b17e` - - [`scrcpy-win32-v3.3.3.zip`][direct-win32] (32-bit) - SHA-256: `e3d43e21c0bd6e070381c390c1e4cccd48a1e71ae73a8c217e6e6b8506598c79` + - [`scrcpy-win64-v3.3.4.zip`][direct-win64] (64-bit) + SHA-256: `d8a155b7c180b7ca4cdadd40712b8750b63f3aab48cb5b8a2a39ac2d0d4c5d38` + - [`scrcpy-win32-v3.3.4.zip`][direct-win32] (32-bit) + SHA-256: `393f7d5379dabd8aacc41184755c3d0df975cd2861353cb7a8d50e0835e2eb72` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win64-v3.3.3.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win32-v3.3.3.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win64-v3.3.4.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win32-v3.3.4.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 3419a9bc..5930e54e 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3 -PREBUILT_SERVER_SHA256=7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4 +PREBUILT_SERVER_SHA256=8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From c8c1316db9adefca6aae054be936049c2f8e02f1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 1 Jan 2026 16:43:12 +0100 Subject: [PATCH 76/83] Happy new year 2026! --- LICENSE | 2 +- README.md | 2 +- app/scrcpy.1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 1196b3da..3325e9f3 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ identification within third-party archives. Copyright (C) 2018 Genymobile - Copyright (C) 2018-2025 Romain Vimont + Copyright (C) 2018-2026 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 07501a2c..2ee76736 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ work][donate]: ## License Copyright (C) 2018 Genymobile - Copyright (C) 2018-2025 Romain Vimont + Copyright (C) 2018-2026 Romain Vimont Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index d72fda13..d6940449 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -852,7 +852,7 @@ Report bugs to . .SH COPYRIGHT Copyright \(co 2018 Genymobile -Copyright \(co 2018\-2025 Romain Vimont +Copyright \(co 2018\-2026 Romain Vimont Licensed under the Apache License, Version 2.0. From 3fcc177da5b6b4514d0e8e8d90d7d58d6731eac9 Mon Sep 17 00:00:00 2001 From: Almog Kurtser Date: Thu, 22 Jan 2026 17:48:42 +0200 Subject: [PATCH 77/83] Use OpenJDK instead of Adoptium on Mac PR #6621 Signed-off-by: Romain Vimont --- doc/build.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/build.md b/doc/build.md index 07efa6b7..bf246ce2 100644 --- a/doc/build.md +++ b/doc/build.md @@ -172,8 +172,7 @@ Additionally, if you want to build the server, install Java 17 from Caskroom, an make it available from the `PATH`: ```bash -brew tap homebrew/cask-versions -brew install adoptopenjdk/openjdk/adoptopenjdk17 +brew install openjdk@17 export JAVA_HOME="$(/usr/libexec/java_home --version 1.17)" export PATH="$JAVA_HOME/bin:$PATH" ``` From 247a37d57bcf389b06d6e17e1af60ded72704a47 Mon Sep 17 00:00:00 2001 From: Benjamin Loison Date: Mon, 6 Apr 2026 01:55:11 +0200 Subject: [PATCH 78/83] Fix typo in doc/develop.md PR #6752 Signed-off-by: Romain Vimont --- doc/develop.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/develop.md b/doc/develop.md index 21949ea6..61102bf4 100644 --- a/doc/develop.md +++ b/doc/develop.md @@ -409,8 +409,8 @@ with any client which uses the same protocol. For simplicity, some [server-specific options] have been added to produce raw streams easily: - - `send_device_meta=false`: disable the device metata (in practice, the device - name) sent on the _first_ socket + - `send_device_meta=false`: disable the device metadata (in practice, the + device name) sent on the _first_ socket - `send_frame_meta=false`: disable the 12-byte header for each packet - `send_dummy_byte`: disable the dummy byte sent on forward connections - `send_codec_meta`: disable the codec information (and initial device size for From 158cae323787661ac7563e5bf5ce40be3e096445 Mon Sep 17 00:00:00 2001 From: Managor <42655600+Managor@users.noreply.github.com> Date: Sun, 15 Mar 2026 23:26:31 +0200 Subject: [PATCH 79/83] Add instructions for creating v4l2loopback device PR #6725 Signed-off-by: Romain Vimont --- doc/v4l2.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/v4l2.md b/doc/v4l2.md index 54272b2b..ac1e3540 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -38,6 +38,13 @@ v4l2-ctl --list-devices ls /dev/video* ``` +If a loopback device was not created automatically, you can make a new one: + +``` +# requires v4l2loopback-utils package +sudo v4l2loopback-ctl add +``` + To start `scrcpy` using a v4l2 sink: ```bash From 0abee2d01e0b89e7fd7aec26b4b9b0f512463f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98andor=20Sergiu?= Date: Fri, 16 Jan 2026 01:26:07 +0200 Subject: [PATCH 80/83] Reference OTG comment in FAQ.md Add a reference to a comment that can help resolve the "OTG Entity not found" issue on Windows. PR #6609 Signed-off-by: Romain Vimont --- FAQ.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FAQ.md b/FAQ.md index 24722c74..f9da5d1f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -141,12 +141,13 @@ On Windows, if `scrcpy --otg` (or `--keyboard=aoa`/`--mouse=aoa`) results in: (or if only unrelated USB devices are detected), there might be drivers issues. -Please read [#3654], in particular [this comment][#3654-comment1] and [the next -one][#3654-comment2]. +Please read [#3654], in particular [this comment][#3654-comment1], [the next +one][#3654-comment2] and [this one][#3654-comment3]. [#3654]: https://github.com/Genymobile/scrcpy/issues/3654 [#3654-comment1]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369278232 [#3654-comment2]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-1369295011 +[#3654-comment3]: https://github.com/Genymobile/scrcpy/issues/3654#issuecomment-2613219725 ## Control issues From db013aa7a74d54fa03f5bfe879f81740492d30ba Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 17 Apr 2026 23:06:34 +0200 Subject: [PATCH 81/83] Fix macOS spelling in documentation --- doc/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.md b/doc/build.md index bf246ce2..31be38fd 100644 --- a/doc/build.md +++ b/doc/build.md @@ -154,7 +154,7 @@ install it manually and make it available from the `PATH`: export PATH="$JAVA_HOME/bin:$PATH" ``` -### Mac OS +### macOS Install the packages with [Homebrew]: From cbc59170503f35b54c2e5186a78f0cc19bd50bf7 Mon Sep 17 00:00:00 2001 From: Satyam Kashyap Date: Wed, 23 Jul 2025 12:30:20 +0530 Subject: [PATCH 82/83] Clarify MSYS2 MinGW terminal requirement PR #6254 Signed-off-by: Romain Vimont --- doc/build.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/build.md b/doc/build.md index 31be38fd..e1873f93 100644 --- a/doc/build.md +++ b/doc/build.md @@ -154,6 +154,10 @@ install it manually and make it available from the `PATH`: export PATH="$JAVA_HOME/bin:$PATH" ``` +When following the rest of the build instructions below, make sure you use the +MinGW terminal within MSYS2. + + ### macOS Install the packages with [Homebrew]: From 4671927c34ce390e3f3c0044187b31c910f1c11e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 19 Apr 2026 11:17:53 +0200 Subject: [PATCH 83/83] Add missing language identifier in v4l2.md Refs #6725 comment --- doc/v4l2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/v4l2.md b/doc/v4l2.md index ac1e3540..b50eb9b8 100644 --- a/doc/v4l2.md +++ b/doc/v4l2.md @@ -24,7 +24,7 @@ to create several devices or devices with specific IDs). If you encounter problems detecting your device with Chrome/WebRTC, you can try `exclusive_caps` mode: -``` +```bash sudo modprobe v4l2loopback exclusive_caps=1 ``` @@ -40,7 +40,7 @@ ls /dev/video* If a loopback device was not created automatically, you can make a new one: -``` +```bash # requires v4l2loopback-utils package sudo v4l2loopback-ctl add ```