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: | 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 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 a610a8ba..2ee76736 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.4) scrcpy @@ -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/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..e47edf88 100755 --- a/app/deps/adb_linux.sh +++ b/app/deps/adb_linux.sh @@ -1,21 +1,21 @@ #!/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 -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 6880fb93..a56117dc 100755 --- a/app/deps/adb_macos.sh +++ b/app/deps/adb_macos.sh @@ -1,21 +1,21 @@ #!/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 -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 b9bd05dc..69287b1d 100755 --- a/app/deps/adb_windows.sh +++ b/app/deps/adb_windows.sh @@ -1,21 +1,21 @@ #!/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 -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 3069b6fe..46fa3954 100755 --- a/app/deps/dav1d.sh +++ b/app/deps/dav1d.sh @@ -1,22 +1,22 @@ #!/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 -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 fb8b9a25..d6cfad54 100755 --- a/app/deps/ffmpeg.sh +++ b/app/deps/ffmpeg.sh @@ -1,22 +1,22 @@ #!/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 -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 887a2a77..72170b9e 100755 --- a/app/deps/libusb.sh +++ b/app/deps/libusb.sh @@ -1,22 +1,22 @@ #!/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 -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 e04deb0d..f74ccf41 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -1,23 +1,24 @@ #!/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 -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" diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index f75a8976..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.2" + VALUE "ProductVersion", "3.3.4" END END BLOCK "VarFileInfo" 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. 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); diff --git a/app/src/display.c b/app/src/display.c index aee8ef80..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); @@ -181,6 +182,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 +202,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; } 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; } 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/doc/build.md b/doc/build.md index d6e067d3..e1873f93 100644 --- a/doc/build.md +++ b/doc/build.md @@ -154,7 +154,11 @@ install it manually and make it available from the `PATH`: export PATH="$JAVA_HOME/bin:$PATH" ``` -### Mac OS +When following the rest of the build instructions below, make sure you use the +MinGW terminal within MSYS2. + + +### macOS Install the packages with [Homebrew]: @@ -172,8 +176,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" ``` @@ -233,10 +236,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.4`][direct-scrcpy-server] + SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e` -[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.4/scrcpy-server-v3.3.4 Download the prebuilt server somewhere, and specify its path during the Meson configuration: @@ -271,7 +274,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) 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 diff --git a/doc/linux.md b/doc/linux.md index 7498ed98..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.2.tar.gz`][direct-linux-x86_64] (x86_64) - SHA-256: `92bed0fa274b9165eb8740e07cf2e2692ebe09ad6911175b0ee42e08799dc51c` + - [`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.2/scrcpy-linux-x86_64-v3.3.2.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 81e0d65d..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.2.tar.gz`][direct-macos-aarch64] (aarch64) - SHA-256: `a213eeff8ac95893e69c4bc6a001a402c6680dbfcb74cb353c0124184ed88e8d` - - - [`scrcpy-macos-x86_64-v3.3.2.tar.gz`][direct-macos-x86_64] (x86_64) - SHA-256: `2a1b27fbb67821a886c7e8dea641899836c0abbe7afd37905584b99bcd21bc04` + - [`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.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.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/v4l2.md b/doc/v4l2.md index 54272b2b..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 ``` @@ -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: + +```bash +# requires v4l2loopback-utils package +sudo v4l2loopback-ctl add +``` + To start `scrcpy` using a v4l2 sink: ```bash diff --git a/doc/windows.md b/doc/windows.md index 63fff892..cdc30bdc 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.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.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.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/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/install_release.sh b/install_release.sh index f6e94dae..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.2/scrcpy-server-v3.3.2 -PREBUILT_SERVER_SHA256=2ee5ca0863ef440f5b7c75856bb475c5283d0a8359cb370b1c161314fd29dfd9 +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 diff --git a/meson.build b/meson.build index 4a03d122..fc82dee1 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '3.3.2', + version: '3.3.4', meson_version: '>= 0.49', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 7afd1e7a..abbbb60a 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 - versionCode 30302 - versionName "3.3.2" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + targetSdkVersion 36 + versionCode 30304 + versionName "3.3.4" + 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 dd1fe623..435ac8f6 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,10 +12,10 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=3.3.2 +SCRCPY_VERSION_NAME=3.3.4 -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" @@ -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" \ 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/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]); 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); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a08c948c..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,12 @@ public final class Server { } private static void internalMain(String... args) throws Exception { + Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Ln.e("Exception on thread " + t, e); + if (defaultHandler != null) { + defaultHandler.uncaughtException(t, e); + } }); prepareMainLooper(); 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"); 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/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); + } } } } 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]; + } +} 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; 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); 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; + } } }