mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 15:24:28 +01:00
Compare commits
41 Commits
dev4
...
video_sess
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
826076f753 | ||
|
|
c36433999c | ||
|
|
78cba1b7c2 | ||
|
|
1015b42e53 | ||
|
|
f4cc07da24 | ||
|
|
fde02a7dfa | ||
|
|
dee1fd46a6 | ||
|
|
a90a039f50 | ||
|
|
ab04e0348d | ||
|
|
99b955f390 | ||
|
|
bbb855b5b0 | ||
|
|
4879950a06 | ||
|
|
170b7a02e7 | ||
|
|
9f1aac41a6 | ||
|
|
02989249f6 | ||
|
|
f8e0b9be4b | ||
|
|
ed62ca124c | ||
|
|
3571fe62ed | ||
|
|
bca98133d1 | ||
|
|
881c71b2e8 | ||
|
|
09eed565fc | ||
|
|
b0da401e6d | ||
|
|
dba2a3778f | ||
|
|
cda4387058 | ||
|
|
42632d3f53 | ||
|
|
db2f72a58a | ||
|
|
fb6381f5b9 | ||
|
|
06fd3b4786 | ||
|
|
1d1a4103cc | ||
|
|
5b51396a8c | ||
|
|
7e66062086 | ||
|
|
6f9eb31d52 | ||
|
|
9cfa5b197a | ||
|
|
b08093d1c0 | ||
|
|
7dd9bcaf60 | ||
|
|
3530851071 | ||
|
|
d0047b2110 | ||
|
|
3281fda6ef | ||
|
|
925949d54a | ||
|
|
f3d4fde15b | ||
|
|
eee3f24739 |
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@@ -72,13 +72,19 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Same as build-linux-x86_64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
|
||||
# SDL3 is not available in Ubuntu yet
|
||||
- name: Install SDL3
|
||||
@@ -106,13 +112,19 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_linux.sh x86_64
|
||||
@@ -140,9 +152,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -171,9 +181,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -237,7 +245,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: |
|
||||
|
||||
@@ -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)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
@@ -129,7 +129,7 @@ Here are just some common examples.
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
- Control the device using gamepad controllers plugged into the computer:
|
||||
- Control the device using gamepads plugged into the computer:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
|
||||
@@ -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
|
||||
# <host>: win32 or win64
|
||||
# <host>: linux, macos, win32 or win64
|
||||
# <build_type>: native or cross
|
||||
# <link_type>: static or shared
|
||||
echo "Syntax: $0 <host> <build_type> <link_type>" >&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 ]]
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=3.2.18
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=51539fa13e546bc50c632beed3f34257de2baa38a4c642048de56377903b4265
|
||||
VERSION=3.4.0
|
||||
URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz"
|
||||
SHA256SUM=9614b9696abc4597ffce6b888829dc6537ae500423474c342ac4a67222c5654c
|
||||
|
||||
PROJECT_DIR="sdl-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -16,8 +16,9 @@ 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"
|
||||
@@ -36,6 +37,7 @@ else
|
||||
|
||||
conf=(
|
||||
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
|
||||
-DSDL_TESTS=OFF
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
|
||||
@@ -57,13 +57,13 @@ src = [
|
||||
'src/util/process.c',
|
||||
'src/util/process_intr.c',
|
||||
'src/util/rand.c',
|
||||
'src/util/sdl.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str.c',
|
||||
'src/util/term.c',
|
||||
'src/util/thread.c',
|
||||
'src/util/tick.c',
|
||||
'src/util/timeout.c',
|
||||
'src/util/window.c',
|
||||
]
|
||||
|
||||
conf = configuration_data()
|
||||
@@ -276,7 +276,7 @@ if get_option('buildtype') == 'debug'
|
||||
exe = executable(t[0], sources,
|
||||
include_directories: src_dir,
|
||||
dependencies: dependencies,
|
||||
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
|
||||
c_args: ['-DSC_TEST'])
|
||||
test(t[0], exe)
|
||||
endforeach
|
||||
endif
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static bool
|
||||
sc_display_init_novideo_icon(struct sc_display *display,
|
||||
@@ -348,7 +349,7 @@ sc_display_update_texture_internal(struct sc_display *display,
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@@ -374,9 +375,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
bool always_ok = SDL_RenderClear(display->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
sc_sdl_render_clear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
@@ -390,8 +389,12 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
SDL_FRect frect;
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
bool ok = SDL_RenderTexture(renderer, texture, NULL, &frect);
|
||||
SDL_FRect *fgeometry = NULL;
|
||||
if (geometry) {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
fgeometry = &frect;
|
||||
}
|
||||
bool ok = SDL_RenderTexture(renderer, texture, NULL, fgeometry);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
@@ -421,8 +424,6 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
}
|
||||
}
|
||||
|
||||
always_ok = SDL_RenderPresent(display->renderer);
|
||||
assert(always_ok);
|
||||
|
||||
sc_sdl_render_present(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
// See "SDL2/SDL_scancode.h".
|
||||
// See "SDL3/SDL_scancode.h".
|
||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||
// HID protocol.
|
||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||
|
||||
@@ -261,7 +261,7 @@ load_from_path(const char *path) {
|
||||
// frame owns the data
|
||||
bool ok = SDL_SetPointerProperty(props, "sc_frame", frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not get surface properties: %s", SDL_GetError());
|
||||
LOGE("Could not set pointer property: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* for simplicity.
|
||||
*
|
||||
* This scrcpy input events API is designed to be consumed by input event
|
||||
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
|
||||
* processors (sc_key_processor, sc_mouse_processor and sc_gamepad_processor,
|
||||
* see app/src/trait/).
|
||||
*
|
||||
* One major semantic difference between SDL input events and scrcpy input
|
||||
* events is their frame of reference (for mouse and touch events): SDL events
|
||||
@@ -322,9 +323,6 @@ enum sc_mouse_button {
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON_MASK(SDL_BUTTON_X2),
|
||||
};
|
||||
|
||||
// Use the naming from SDL3 for gamepad axis and buttons:
|
||||
// <https://wiki.libsdl.org/SDL3/README/migration>
|
||||
|
||||
enum sc_gamepad_axis {
|
||||
SC_GAMEPAD_AXIS_UNKNOWN = -1,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_GAMEPAD_AXIS_LEFTX,
|
||||
@@ -411,10 +409,9 @@ struct sc_touch_event {
|
||||
float pressure;
|
||||
};
|
||||
|
||||
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
||||
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||
// invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||
// As documented in <https://wiki.libsdl.org/SDL3/SDL_JoystickID>:
|
||||
// The value 0 is an invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID 0
|
||||
|
||||
struct sc_gamepad_device_event {
|
||||
uint32_t gamepad_id;
|
||||
@@ -500,7 +497,7 @@ static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
|
||||
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_CONTROLLER_AXIS_*
|
||||
// SDL_GAMEPAD_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||
@@ -510,14 +507,14 @@ static inline enum sc_gamepad_button
|
||||
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
if (button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
||||
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||
// SDL_CONTROLLER_BUTTON_*
|
||||
// SDL_GAMEPAD_BUTTON_*
|
||||
return button;
|
||||
}
|
||||
return SC_GAMEPAD_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_controllerbutton_type(uint32_t type) {
|
||||
sc_action_from_sdl_gamepad_button_type(uint32_t type) {
|
||||
assert(type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || type == SDL_EVENT_GAMEPAD_BUTTON_UP);
|
||||
if (type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "screen.h"
|
||||
#include "shortcut_mod.h"
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
@@ -672,15 +673,12 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(im->screen->window);
|
||||
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
int32_t x = event->x * dw;
|
||||
int32_t y = event->y * dh;
|
||||
int32_t x = event->x * (int32_t) drawable_size.width;
|
||||
int32_t y = event->y * (int32_t) drawable_size.height;
|
||||
|
||||
struct sc_touch_event evt = {
|
||||
.position = {
|
||||
@@ -910,16 +908,16 @@ static void
|
||||
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
const SDL_GamepadDeviceEvent *event) {
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *gc = SDL_OpenGamepad(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
SDL_Gamepad *sdl_gamepad = SDL_OpenGamepad(event->which);
|
||||
if (!sdl_gamepad) {
|
||||
LOGW("Could not open gamepad");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_CloseGamepad(gc);
|
||||
LOGW("Could not get gamepad joystick");
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -930,9 +928,9 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_CloseGamepad(gc);
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
|
||||
if (sdl_gamepad) {
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -973,7 +971,7 @@ sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.action = sc_action_from_sdl_gamepad_button_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#ifdef HAVE_V4L2
|
||||
# include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "usb/scrcpy_otg.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/scrcpy_otg.h"
|
||||
#endif
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
@@ -57,8 +57,8 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
// The mouse will be captured on SDL_MOUSEBUTTONUP, so consume
|
||||
// the event
|
||||
// The mouse will be captured on SDL_EVENT_MOUSE_BUTTON_UP, so
|
||||
// consume the event
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -355,7 +355,7 @@ scrcpy_generate_scid(void) {
|
||||
|
||||
static void
|
||||
init_sdl_gamepads(void) {
|
||||
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||
// Trigger a SDL_EVENT_GAMEPAD_ADDED event for all gamepads already
|
||||
// connected
|
||||
int count;
|
||||
SDL_JoystickID *joysticks = SDL_GetJoysticks(&count);
|
||||
|
||||
213
app/src/screen.c
213
app/src/screen.c
@@ -8,7 +8,7 @@
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "util/log.h"
|
||||
#include "util/window.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
|
||||
@@ -27,45 +27,11 @@ get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
|
||||
return oriented_size;
|
||||
}
|
||||
|
||||
// get the window size in a struct sc_size
|
||||
static struct sc_size
|
||||
get_window_size(const struct sc_screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
bool always_ok = SDL_GetWindowSize(screen->window, &width, &height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size size;
|
||||
size.width = width;
|
||||
size.height = height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct sc_point
|
||||
get_window_position(const struct sc_screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
bool always_ok = SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_point point;
|
||||
point.x = x;
|
||||
point.y = y;
|
||||
return point;
|
||||
}
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static void
|
||||
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
bool always_ok =
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
static inline bool
|
||||
is_windowed(struct sc_screen *screen) {
|
||||
return !(SDL_GetWindowFlags(screen->window) & (SDL_WINDOW_FULLSCREEN
|
||||
| SDL_WINDOW_MINIMIZED
|
||||
| SDL_WINDOW_MAXIMIZED));
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
@@ -73,6 +39,11 @@ static bool
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
SDL_Rect rect;
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
if (!display) {
|
||||
LOGW("Could not get primary display: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = SDL_GetDisplayUsableBounds(display, &rect);
|
||||
if (!ok) {
|
||||
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
||||
@@ -176,15 +147,10 @@ static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct sc_size drawable_size = {dw, dh};
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
@@ -253,7 +219,7 @@ event_watcher(void *data, SDL_Event *event) {
|
||||
struct sc_screen *screen = data;
|
||||
assert(screen->video);
|
||||
|
||||
if (event->type == SDL_EVENT_WINDOW_EXPOSED) {
|
||||
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
|
||||
// In practice, it seems to always be called from the same thread in
|
||||
// that specific case. Anyway, it's just a workaround.
|
||||
sc_screen_render(screen, true);
|
||||
@@ -331,9 +297,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->resize_pending = false;
|
||||
screen->has_frame = false;
|
||||
screen->has_video_window = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
screen->paused = false;
|
||||
screen->resume_frame = NULL;
|
||||
screen->orientation = SC_ORIENTATION_0;
|
||||
@@ -364,7 +327,8 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
// Always create the window hidden to prevent blinking during initialization
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_HIDDEN;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
@@ -373,8 +337,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
if (params->video) {
|
||||
// The window will be shown on first frame
|
||||
window_flags |= SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE;
|
||||
window_flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
const char *title = params->window_title;
|
||||
@@ -399,7 +362,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
// The window will be positioned and sized on first video frame
|
||||
screen->window =
|
||||
sc_create_sdl_window(title, x, y, width, height, window_flags);
|
||||
sc_sdl_create_window(title, x, y, width, height, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
@@ -482,9 +445,14 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->open = false;
|
||||
#endif
|
||||
|
||||
if (!screen->video && sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
if (!screen->video) {
|
||||
// Show the window immediately
|
||||
sc_sdl_show_window(screen->window);
|
||||
|
||||
if (sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -507,18 +475,18 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
|
||||
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
|
||||
struct sc_point position = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(screen->content_size, screen->req.width,
|
||||
screen->req.height);
|
||||
|
||||
set_window_size(screen, window_size);
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, x, y);
|
||||
assert(always_ok);
|
||||
assert(is_windowed(screen));
|
||||
sc_sdl_set_window_size(screen->window, window_size);
|
||||
sc_sdl_set_window_position(screen->window, position);
|
||||
|
||||
if (screen->req.fullscreen) {
|
||||
sc_screen_toggle_fullscreen(screen);
|
||||
@@ -528,17 +496,13 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
sc_fps_counter_start(&screen->fps_counter);
|
||||
}
|
||||
|
||||
always_ok = SDL_ShowWindow(screen->window);
|
||||
assert(always_ok);
|
||||
|
||||
sc_sdl_show_window(screen->window);
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen) {
|
||||
bool always_ok = SDL_HideWindow(screen->window);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
sc_sdl_hide_window(screen->window);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -568,7 +532,7 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
struct sc_size target_size = {
|
||||
.width = (uint32_t) window_size.width * new_content_size.width
|
||||
/ old_content_size.width,
|
||||
@@ -576,14 +540,15 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
/ old_content_size.height,
|
||||
};
|
||||
target_size = get_optimal_size(target_size, new_content_size, true);
|
||||
set_window_size(screen, target_size);
|
||||
assert(is_windowed(screen));
|
||||
sc_sdl_set_window_size(screen->window, target_size);
|
||||
}
|
||||
|
||||
static void
|
||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
if (is_windowed(screen)) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
// Store the windowed size to be able to compute the optimal size once
|
||||
@@ -599,9 +564,7 @@ static void
|
||||
apply_pending_resize(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
assert(is_windowed(screen));
|
||||
if (screen->resize_pending) {
|
||||
resize_for_content(screen, screen->windowed_content_size,
|
||||
screen->content_size);
|
||||
@@ -749,31 +712,28 @@ void
|
||||
sc_screen_toggle_fullscreen(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, !screen->fullscreen);
|
||||
bool req_fullscreen =
|
||||
!(SDL_GetWindowFlags(screen->window) & SDL_WINDOW_FULLSCREEN);
|
||||
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, req_fullscreen);
|
||||
if (!ok) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
screen->fullscreen = !screen->fullscreen;
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
apply_pending_resize(screen);
|
||||
}
|
||||
|
||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||
sc_screen_render(screen, true);
|
||||
LOGD("Requested %s mode", req_fullscreen ? "fullscreen" : "windowed");
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
||||
if (!is_windowed(screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_point point = get_window_position(screen);
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_point point = sc_sdl_get_window_position(screen->window);
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
|
||||
struct sc_size optimal_size =
|
||||
get_optimal_size(window_size, screen->content_size, false);
|
||||
@@ -781,19 +741,14 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
// Center the window related to the device screen
|
||||
assert(optimal_size.width <= window_size.width);
|
||||
assert(optimal_size.height <= window_size.height);
|
||||
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
||||
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||
optimal_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||
assert(always_ok);
|
||||
struct sc_point new_position = {
|
||||
.x = point.x + (window_size.width - optimal_size.width) / 2,
|
||||
.y = point.y + (window_size.height - optimal_size.height) / 2,
|
||||
};
|
||||
|
||||
sc_sdl_set_window_size(screen->window, optimal_size);
|
||||
sc_sdl_set_window_position(screen->window, new_position);
|
||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||
optimal_size.height);
|
||||
}
|
||||
@@ -802,24 +757,12 @@ void
|
||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->minimized) {
|
||||
if (!is_windowed(screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
if (screen->maximized) {
|
||||
always_ok = SDL_RestoreWindow(screen->window);
|
||||
assert(always_ok);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
always_ok = SDL_SetWindowSize(screen->window, content_size.width,
|
||||
content_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
sc_sdl_set_window_size(screen->window, content_size);
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
}
|
||||
@@ -849,24 +792,20 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
screen->maximized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
screen->minimized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
if (screen->fullscreen) {
|
||||
// On Windows, in maximized+fullscreen, disabling
|
||||
// fullscreen mode unexpectedly triggers the "restored"
|
||||
// then "maximized" events, leaving the window in a
|
||||
// weird state (maximized according to the events, but
|
||||
// not maximized visually).
|
||||
return true;
|
||||
if (screen->has_video_window && is_windowed(screen)) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
if (screen->has_video_window) {
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||
LOGD("Switched to fullscreen mode");
|
||||
assert(screen->has_video_window);
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||
LOGD("Switched to windowed mode");
|
||||
assert(screen->has_video_window);
|
||||
if (is_windowed(screen)) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
@@ -948,16 +887,16 @@ sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
||||
|
||||
void
|
||||
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||
int ww, wh, dw, dh;
|
||||
always_ok = SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
assert(always_ok);
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
int64_t ww = window_size.width;
|
||||
int64_t wh = window_size.height;
|
||||
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
int64_t dw = drawable_size.width;
|
||||
int64_t dh = drawable_size.height;
|
||||
|
||||
// scale for HiDPI (64 bits for intermediate multiplications)
|
||||
*x = (int64_t) *x * dw / ww;
|
||||
|
||||
@@ -62,9 +62,6 @@ struct sc_screen {
|
||||
struct SDL_Rect rect;
|
||||
bool has_frame;
|
||||
bool has_video_window;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
|
||||
AVFrame *frame;
|
||||
|
||||
|
||||
@@ -74,10 +74,9 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Gamepad * game_controller =
|
||||
SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(game_controller);
|
||||
const char *name = SDL_GetGamepadName(game_controller);
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(sdl_gamepad);
|
||||
const char *name = SDL_GetGamepadName(sdl_gamepad);
|
||||
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
|
||||
|
||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||
|
||||
@@ -75,7 +75,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
||||
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
// Not fatal, keyboard/mouse should still work
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
#include "options.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/log.h"
|
||||
#include "util/window.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static void
|
||||
sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
bool always_ok = SDL_RenderClear(screen->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
sc_sdl_render_clear(screen->renderer);
|
||||
if (screen->texture) {
|
||||
bool ok =
|
||||
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
|
||||
@@ -22,9 +19,7 @@ sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
LOGW("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
always_ok = SDL_RenderPresent(screen->renderer);
|
||||
assert(always_ok);
|
||||
sc_sdl_render_present(screen->renderer);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -53,7 +48,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
}
|
||||
|
||||
screen->window =
|
||||
sc_create_sdl_window(title, x, y, width, height, window_flags);
|
||||
sc_sdl_create_window(title, x, y, width, height, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
return false;
|
||||
@@ -197,16 +192,16 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *gc = SDL_OpenGamepad(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
SDL_Gamepad *sdl_gamepad = SDL_OpenGamepad(event->which);
|
||||
if (!sdl_gamepad) {
|
||||
LOGW("Could not open gamepad");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_CloseGamepad(gc);
|
||||
LOGW("Could not get gamepad joystick");
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,9 +212,9 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_CloseGamepad(gc);
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
|
||||
if (sdl_gamepad) {
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -263,7 +258,7 @@ sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.action = sc_action_from_sdl_gamepad_button_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
gp->ops->process_gamepad_button(gp, &evt);
|
||||
|
||||
149
app/src/util/sdl.c
Normal file
149
app/src/util/sdl.c
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "sdl.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_sdl_create_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags) {
|
||||
SDL_Window *window = NULL;
|
||||
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ok =
|
||||
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING,
|
||||
title);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
|
||||
width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
|
||||
height);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER,
|
||||
flags);
|
||||
|
||||
if (!ok) {
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindowWithProperties(props);
|
||||
SDL_DestroyProperties(props);
|
||||
return window;
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size(SDL_Window *window) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetWindowSize(window, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_set_window_size(SDL_Window *window, struct sc_size size) {
|
||||
bool ok = SDL_SetWindowSize(window, size.width, size.height);
|
||||
if (!ok) {
|
||||
LOGE("Could not set window size: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
sc_sdl_get_window_position(SDL_Window *window) {
|
||||
int x;
|
||||
int y;
|
||||
bool ok = SDL_GetWindowPosition(window, &x, &y);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window position: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_point point = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
return point;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_set_window_position(SDL_Window *window, struct sc_point point) {
|
||||
bool ok = SDL_SetWindowPosition(window, point.x, point.y);
|
||||
if (!ok) {
|
||||
LOGE("Could not set window position: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_show_window(SDL_Window *window) {
|
||||
bool ok = SDL_ShowWindow(window);
|
||||
if (!ok) {
|
||||
LOGE("Could not show window: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window) {
|
||||
bool ok = SDL_HideWindow(window);
|
||||
if (!ok) {
|
||||
LOGE("Could not hide window: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderClear(renderer);
|
||||
if (!ok) {
|
||||
LOGW("Could not clear rendering: %s", SDL_GetError());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_render_present(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderPresent(renderer);
|
||||
if (!ok) {
|
||||
LOGE("Could not render: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
43
app/src/util/sdl.h
Normal file
43
app/src/util/sdl.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef SC_SDL_H
|
||||
#define SC_SDL_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
#include "coords.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_sdl_create_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_set_window_size(SDL_Window *window, struct sc_size size);
|
||||
|
||||
struct sc_point
|
||||
sc_sdl_get_window_position(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_set_window_position(SDL_Window *window, struct sc_point point);
|
||||
|
||||
void
|
||||
sc_sdl_show_window(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window);
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer);
|
||||
|
||||
void
|
||||
sc_sdl_render_present(SDL_Renderer *renderer);
|
||||
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
#include "window.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags) {
|
||||
SDL_Window *window = NULL;
|
||||
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ok =
|
||||
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING,
|
||||
title);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
|
||||
width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
|
||||
height);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER,
|
||||
flags);
|
||||
|
||||
if (!ok) {
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindowWithProperties(props);
|
||||
SDL_DestroyProperties(props);
|
||||
return window;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef SC_WINDOW_H
|
||||
#define SC_WINDOW_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
SDL_Window *
|
||||
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags);
|
||||
|
||||
#endif
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
24
doc/build.md
24
doc/build.md
@@ -30,13 +30,13 @@ the following files to a directory accessible from your `PATH`:
|
||||
|
||||
It is also available in scrcpy releases.
|
||||
|
||||
The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions.
|
||||
The client requires [FFmpeg] and [SDL]. Just follow the instructions.
|
||||
|
||||
[adb]: https://developer.android.com/studio/command-line/adb.html
|
||||
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
|
||||
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
|
||||
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
|
||||
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
|
||||
[SDL]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
|
||||
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ Install the required packages from your package manager.
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
|
||||
sudo apt install ffmpeg libsdl3-0 adb libusb-1.0-0
|
||||
|
||||
# client build dependencies
|
||||
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
sudo apt install gcc git pkg-config meson ninja-build libsdl3-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libswresample-dev libusb-1.0-0-dev
|
||||
|
||||
@@ -77,7 +77,7 @@ pip3 install meson
|
||||
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
||||
|
||||
# client build dependencies
|
||||
sudo dnf install SDL2-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
|
||||
sudo dnf install SDL3-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
|
||||
|
||||
# server build dependencies
|
||||
sudo dnf install java-devel
|
||||
@@ -121,7 +121,7 @@ install the required packages:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-x86_64-SDL2 \
|
||||
pacman -S mingw-w64-x86_64-sdl3 \
|
||||
mingw-w64-x86_64-ffmpeg \
|
||||
mingw-w64-x86_64-libusb
|
||||
|
||||
@@ -136,7 +136,7 @@ For a 32 bits version, replace `x86_64` by `i686`:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-i686-SDL2 \
|
||||
pacman -S mingw-w64-i686-sdl3 \
|
||||
mingw-w64-i686-ffmpeg \
|
||||
mingw-w64-i686-libusb
|
||||
|
||||
@@ -162,7 +162,7 @@ Install the packages with [Homebrew]:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
brew install sdl2 ffmpeg libusb
|
||||
brew install sdl3 ffmpeg libusb
|
||||
|
||||
# client build dependencies
|
||||
brew install pkg-config meson
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v3.3.3`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0`</sub>
|
||||
- [`scrcpy-server-v3.3.4`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e`</sub>
|
||||
|
||||
[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:
|
||||
@@ -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)
|
||||
|
||||
|
||||
10
doc/linux.md
10
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)
|
||||
<sub>SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638`</sub>
|
||||
- [`scrcpy-linux-x86_64-v3.3.4.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `0305d98c06178c67e12427bbf340c436d0d58c9e2a39bf9ffbbf8f54d7ef95a5`</sub>
|
||||
|
||||
[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.
|
||||
|
||||
@@ -39,8 +39,8 @@ First, you need to install the required packages:
|
||||
|
||||
```bash
|
||||
# for Debian/Ubuntu
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
sudo apt install ffmpeg libsdl3-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl3-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
|
||||
```
|
||||
|
||||
13
doc/macos.md
13
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)
|
||||
<sub>SHA-256: `b93299468f19ae89ac70f7c1453914c41f1f2bcd31f6ab530038da885c19581f`</sub>
|
||||
|
||||
- [`scrcpy-macos-x86_64-v3.3.3.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `c767fc1d41e4ae26e40558656570962f474739924fd22ee023d8754889ee4366`</sub>
|
||||
- [`scrcpy-macos-aarch64-v3.3.4.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `8fef43520405dd523c74e1530ac68febcc5a405ea89712c874936675da8513dd`</sub>
|
||||
- [`scrcpy-macos-x86_64-v3.3.4.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `cf9b3453a33279b6009dfb256b1a84c374bd4c30a71edd74bacab28d72a5d929`</sub>
|
||||
|
||||
[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.
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v3.3.3.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `4b458d33d0436688c69875cd267cae6fa8be08aa3c17772edf3a940a3dc4b17e`</sub>
|
||||
- [`scrcpy-win32-v3.3.3.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `e3d43e21c0bd6e070381c390c1e4cccd48a1e71ae73a8c217e6e6b8506598c79`</sub>
|
||||
- [`scrcpy-win64-v3.3.4.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `d8a155b7c180b7ca4cdadd40712b8750b63f3aab48cb5b8a2a39ac2d0d4c5d38`</sub>
|
||||
- [`scrcpy-win32-v3.3.4.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `393f7d5379dabd8aacc41184755c3d0df975cd2861353cb7a8d50e0835e2eb72`</sub>
|
||||
|
||||
[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.
|
||||
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '3.3.3',
|
||||
version: '3.3.4',
|
||||
meson_version: '>= 0.49',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -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 30303
|
||||
versionName "3.3.3"
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=3.3.3
|
||||
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" \
|
||||
|
||||
@@ -109,8 +109,10 @@ public final class FakeContext extends ContextWrapper {
|
||||
}
|
||||
|
||||
// "semclipboard" is a Samsung-internal service
|
||||
// See <https://github.com/Genymobile/scrcpy/issues/6224>
|
||||
if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name)) {
|
||||
// See:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/6224>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/6523>
|
||||
if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name) || Context.ACTIVITY_SERVICE.equals(name)) {
|
||||
try {
|
||||
Field field = service.getClass().getDeclaredField("mContext");
|
||||
field.setAccessible(true);
|
||||
|
||||
@@ -414,6 +414,7 @@ public class Options {
|
||||
if (!value.isEmpty()) {
|
||||
options.audioEncoder = value;
|
||||
}
|
||||
break;
|
||||
case "power_off_on_close":
|
||||
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||
break;
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.genymobile.scrcpy.video.VideoSource;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.system.Os;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -225,10 +226,16 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
dropRootPrivileges();
|
||||
|
||||
prepareMainLooper();
|
||||
|
||||
Options options = Options.parse(args);
|
||||
@@ -269,4 +276,17 @@ public final class Server {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void dropRootPrivileges() {
|
||||
try {
|
||||
if (Os.getuid() == 0) {
|
||||
// Copy-paste does not work with root user
|
||||
// <https://github.com/Genymobile/scrcpy/issues/6224>
|
||||
Os.setuid(2000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Ln.w("Cannot set UID", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 <https://github.com/Genymobile/scrcpy/issues/5444>
|
||||
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<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
run(inputSize, outputSize, outputSurface);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Throwable throwable) {
|
||||
if (throwable instanceof OpenGLException) {
|
||||
throw (OpenGLException) throwable;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
server/src/main/java/com/genymobile/scrcpy/util/Threads.java
Normal file
43
server/src/main/java/com/genymobile/scrcpy/util/Threads.java
Normal file
@@ -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> T executeSynchronouslyOn(Handler handler, Callable<T> 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];
|
||||
}
|
||||
}
|
||||
@@ -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: <https://github.com/Genymobile/scrcpy/issues/6461>
|
||||
uniqueId = null;
|
||||
}
|
||||
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
|
||||
Reference in New Issue
Block a user