Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
6b4e8e8e59 Bump version to 3.3.2 2025-09-06 14:33:49 +02:00
Romain Vimont
876e42de9b Workaround clipboard issue on Samsung
Fixes #6224 <https://github.com/Genymobile/scrcpy/issues/6224>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2025-09-06 14:32:34 +02:00
80 changed files with 1031 additions and 1434 deletions

View File

@@ -72,30 +72,16 @@ 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 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
run: |
app/deps/sdl.sh linux native shared
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
- name: Test
run: |
export PKG_CONFIG_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib/pkgconfig
export LD_LIBRARY_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib
release/test_client.sh
run: release/test_client.sh
build-linux-x86_64:
runs-on: ubuntu-22.04
@@ -112,19 +98,13 @@ 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 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
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
- name: Build
run: release/build_linux.sh x86_64
@@ -152,7 +132,9 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm \
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 \
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
- name: Build
@@ -181,7 +163,9 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm \
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 \
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
- name: Build
@@ -245,7 +229,7 @@ jobs:
path: release/work/build-macos-aarch64/dist-tar/
build-macos-x86_64:
runs-on: macos-15-intel
runs-on: macos-13
steps:
- name: Check architecture
run: |

View File

@@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v3.3.4)
# scrcpy (v3.3.1)
<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 gamepads plugged into the computer:
- Control the device using gamepad controllers plugged into the computer:
```bash
scrcpy --gamepad=uhid

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=36.0.0
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-linux.zip"
FILENAME=platform-tools_r$VERSION-linux.zip
PROJECT_DIR=platform-tools-$VERSION-linux
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=36.0.0
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-darwin.zip"
FILENAME=platform-tools_r$VERSION-darwin.zip
PROJECT_DIR=platform-tools-$VERSION-darwin
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=36.0.0
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-win.zip"
FILENAME=platform-tools_r$VERSION-win.zip
PROJECT_DIR=platform-tools-$VERSION-windows
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,9 +1,10 @@
#!/usr/bin/env bash
# This file is intended to be sourced by other scripts, not executed
process_args() {
if [[ $# != 3 ]]
then
# <host>: linux, macos, win32 or win64
# <host>: win32 or win64
# <build_type>: native or cross
# <link_type>: static or shared
echo "Syntax: $0 <host> <build_type> <link_type>" >&2
@@ -11,8 +12,8 @@ process_args() {
fi
HOST="$1"
BUILD_TYPE="$2"
LINK_TYPE="$3"
BUILD_TYPE="$2" # native or cross
LINK_TYPE="$3" # static or shared
DIRNAME="$HOST-$BUILD_TYPE-$LINK_TYPE"
if [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]]

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=1.5.0
URL="https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/dav1d-$VERSION.tar.gz"
FILENAME=dav1d-$VERSION.tar.gz
PROJECT_DIR=dav1d-$VERSION
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=7.1.1
URL="https://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz"
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=1.0.29
URL="https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz"
FILENAME=libusb-$VERSION.tar.gz
PROJECT_DIR=libusb-$VERSION
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 "$URL" "$FILENAME" "$SHA256SUM"
get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env bash
set -ex
. $(dirname ${BASH_SOURCE[0]})/_init
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
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"
VERSION=2.32.8
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=dd35e05644ae527848d02433bec24dd0ea65db59faecf1a0e5d1880c533dac2c
cd "$SOURCES_DIR"
@@ -16,9 +16,8 @@ if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "$URL" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "SDL-release-$VERSION"
mv "SDL-release-$VERSION" "$PROJECT_DIR"
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"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
@@ -36,49 +35,45 @@ else
cd "$DIRNAME"
conf=(
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
-DSDL_TESTS=OFF
--prefix="$INSTALL_DIR/$DIRNAME"
)
if [[ "$HOST" == linux ]]
then
conf+=(
-DSDL_WAYLAND=ON
-DSDL_X11=ON
--enable-video-wayland
--enable-video-x11
)
fi
if [[ "$LINK_TYPE" == static ]]
then
conf+=(
-DBUILD_SHARED_LIBS=OFF
--enable-static
--disable-shared
)
else
conf+=(
-DBUILD_SHARED_LIBS=ON
--disable-static
--enable-shared
)
fi
if [[ "$BUILD_TYPE" == cross ]]
then
if [[ "$HOST" = win32 ]]
then
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-i686.cmake"
elif [[ "$HOST" = win64 ]]
then
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-x86_64.cmake"
else
echo "Unsupported cross-build to host: $HOST" >&2
exit 1
fi
conf+=(
-DCMAKE_TOOLCHAIN_FILE="$SOURCES_DIR/$PROJECT_DIR/build-scripts/$TOOLCHAIN_FILENAME"
--host="$HOST_TRIPLET"
)
fi
cmake "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
fi
cmake --build .
cmake --install .
make -j
# There is no "make install-strip"
make install
# Strip manually
if [[ "$LINK_TYPE" == shared && "$HOST" == win* ]]
then
${HOST_TRIPLET}-strip "$INSTALL_DIR/$DIRNAME/bin/SDL2.dll"
fi

View File

@@ -57,7 +57,6 @@ 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',
@@ -118,7 +117,7 @@ dependencies = [
dependency('libavcodec', version: '>= 57.37', static: static),
dependency('libavutil', static: static),
dependency('libswresample', static: static),
dependency('sdl3', version: '>= 3.2.0', static: static),
dependency('sdl2', version: '>= 2.0.5', static: static),
]
if v4l2_support
@@ -276,7 +275,7 @@ if get_option('buildtype') == 'debug'
exe = executable(t[0], sources,
include_directories: src_dir,
dependencies: dependencies,
c_args: ['-DSC_TEST'])
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
test(t[0], exe)
endforeach
endif

View File

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

View File

@@ -1,40 +1,23 @@
#include "audio_player.h"
#include "util/log.h"
#include "SDL3/SDL_hints.h"
/** Downcast frame_sink to sc_audio_player */
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
#define SC_SDL_SAMPLE_FMT SDL_AUDIO_F32LE
#define SC_SDL_SAMPLE_FMT AUDIO_F32
static void SDLCALL
sc_audio_player_stream_callback(void *userdata, SDL_AudioStream *stream,
int additional_amount, int total_amount) {
(void) total_amount;
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
struct sc_audio_player *ap = userdata;
size_t len = additional_amount;
assert(len_int > 0);
size_t len = len_int;
assert(len % ap->audioreg.sample_size == 0);
uint32_t out_samples = len / ap->audioreg.sample_size;
// The requested amount may exceed the internal aout_buffer size.
// In this (unlikely) case, send the data to the stream in multiple chunks.
while (len) {
size_t chunk_size = MIN(ap->aout_buffer_size, len);
uint32_t out_samples = chunk_size / ap->audioreg.sample_size;
sc_audio_regulator_pull(&ap->audioreg, ap->aout_buffer,
out_samples);
assert(chunk_size <= len);
len -= chunk_size;
bool ok =
SDL_PutAudioStreamData(stream, ap->aout_buffer, chunk_size);
if (!ok) {
LOGW("Audio stream error: %s", SDL_GetError());
return;
}
}
sc_audio_regulator_pull(&ap->audioreg, stream, out_samples);
}
static bool
@@ -78,45 +61,23 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
/ SC_TICK_FREQ;
assert(aout_samples <= 0xFFFF);
char str[5 + 1]; // max 65535
int r = snprintf(str, sizeof(str), "%" PRIu16, (uint16_t) aout_samples);
assert(r >= 0 && (size_t) r < sizeof(str));
(void) r;
if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, str)) {
LOGE("Could not set audio output buffer");
sc_audio_regulator_destroy(&ap->audioreg);
return false;
}
// Make the buffer at least 1024 samples long (the hint is not always
// honored)
uint64_t aout_buffer_samples = MAX(1024, aout_samples);
ap->aout_buffer_size = aout_buffer_samples * sample_size;
ap->aout_buffer = malloc(ap->aout_buffer_size);
if (!ap->aout_buffer) {
sc_audio_regulator_destroy(&ap->audioreg);
return false;
}
SDL_AudioSpec spec = {
SDL_AudioSpec desired = {
.freq = ctx->sample_rate,
.format = SC_SDL_SAMPLE_FMT,
.channels = nb_channels,
.samples = aout_samples,
.callback = sc_audio_player_sdl_callback,
.userdata = ap,
};
SDL_AudioSpec obtained;
ap->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&spec,
sc_audio_player_stream_callback, ap);
if (!ap->stream) {
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (!ap->device) {
LOGE("Could not open audio device: %s", SDL_GetError());
free(ap->aout_buffer);
sc_audio_regulator_destroy(&ap->audioreg);
return false;
}
ap->device = SDL_GetAudioStreamDevice(ap->stream);
assert(ap->device);
// The thread calling open() is the thread calling push(), which fills the
// audio buffer consumed by the SDL audio thread.
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL);
@@ -125,14 +86,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
(void) ok; // We don't care if it worked, at least we tried
}
ok = SDL_ResumeAudioDevice(ap->device);
if (!ok) {
LOGE("Could not resume audio device: %s", SDL_GetError());
SDL_DestroyAudioStream(ap->stream);
free(ap->aout_buffer);
sc_audio_regulator_destroy(&ap->audioreg);
return false;
}
SDL_PauseAudioDevice(ap->device, 0);
return true;
}
@@ -141,16 +95,11 @@ static void
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_audio_player *ap = DOWNCAST(sink);
assert(ap->stream);
assert(ap->device);
SDL_PauseAudioDevice(ap->device);
// ap->device is owned by ap->stream
SDL_DestroyAudioStream(ap->stream);
SDL_PauseAudioDevice(ap->device, 1);
SDL_CloseAudioDevice(ap->device);
sc_audio_regulator_destroy(&ap->audioreg);
free(ap->aout_buffer);
}
void

View File

@@ -3,7 +3,7 @@
#include "common.h"
#include <SDL3/SDL_audio.h>
#include <SDL2/SDL_audio.h>
#include "audio_regulator.h"
#include "trait/frame_sink.h"
@@ -22,11 +22,7 @@ struct sc_audio_player {
// SDL audio output buffer size
sc_tick output_buffer_duration;
uint8_t *aout_buffer;
size_t aout_buffer_size;
SDL_AudioStream *stream;
SDL_AudioDeviceID device; // owned by the audio stream
SDL_AudioDeviceID device;
struct sc_audio_regulator audioreg;
};

View File

@@ -6,7 +6,7 @@
#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <libavutil/version.h>
#include <SDL3/SDL_version.h>
#include <SDL2/SDL_version.h>
#ifndef _WIN32
# define PRIu64_ PRIu64
@@ -61,6 +61,28 @@
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#if SDL_VERSION_ATLEAST(2, 0, 16)
# define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
#endif
#if SDL_VERSION_ATLEAST(2, 0, 18)
# define SCRCPY_SDL_HAS_HINT_APP_NAME
#endif
#if SDL_VERSION_ATLEAST(2, 0, 14)
# define SCRCPY_SDL_HAS_HINT_AUDIO_DEVICE_APP_NAME
#endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif

View File

@@ -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);

View File

@@ -6,18 +6,14 @@
#include <libavutil/pixfmt.h>
#include "util/log.h"
#include "util/sdl.h"
static bool
sc_display_init_novideo_icon(struct sc_display *display,
SDL_Surface *icon_novideo) {
assert(icon_novideo);
bool ok = SDL_SetRenderLogicalPresentation(display->renderer,
icon_novideo->w,
icon_novideo->h,
SDL_LOGICAL_PRESENTATION_LETTERBOX);
if (!ok) {
if (SDL_RenderSetLogicalSize(display->renderer,
icon_novideo->w, icon_novideo->h)) {
LOGW("Could not set renderer logical size: %s", SDL_GetError());
// don't fail
}
@@ -35,13 +31,16 @@ sc_display_init_novideo_icon(struct sc_display *display,
bool
sc_display_init(struct sc_display *display, SDL_Window *window,
SDL_Surface *icon_novideo, bool mipmaps) {
display->renderer = SDL_CreateRenderer(window, NULL);
display->renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!display->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
return false;
}
const char *renderer_name = SDL_GetRendererName(display->renderer);
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
display->mipmaps = false;
@@ -57,11 +56,8 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
// Persuade macOS to give us something better than OpenGL 2.1.
// If we create a Core Profile context, we get the best OpenGL version.
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE);
if (!ok) {
LOGW("Could not set a GL Core Profile Context");
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE);
LOGD("Creating OpenGL Core Profile context");
display->gl_context = SDL_GL_CreateContext(window);
@@ -98,13 +94,14 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
display->texture = NULL;
display->pending.flags = 0;
display->pending.frame = NULL;
display->has_frame = false;
if (icon_novideo) {
// Without video, set a static scrcpy icon as window content
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
if (!ok) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DestroyContext(display->gl_context);
SDL_GL_DeleteContext(display->gl_context);
#endif
SDL_DestroyRenderer(display->renderer);
return false;
@@ -120,7 +117,7 @@ sc_display_destroy(struct sc_display *display) {
av_frame_free(&display->pending.frame);
}
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DestroyContext(display->gl_context);
SDL_GL_DeleteContext(display->gl_context);
#endif
if (display->texture) {
SDL_DestroyTexture(display->texture);
@@ -128,63 +125,13 @@ sc_display_destroy(struct sc_display *display) {
SDL_DestroyRenderer(display->renderer);
}
static enum SDL_Colorspace
sc_display_to_sdl_color_space(enum AVColorSpace color_space,
enum AVColorRange color_range) {
bool full_range = color_range == AVCOL_RANGE_JPEG;
switch (color_space) {
case AVCOL_SPC_BT709:
case AVCOL_SPC_RGB:
return full_range ? SDL_COLORSPACE_BT709_FULL
: SDL_COLORSPACE_BT709_LIMITED;
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
return full_range ? SDL_COLORSPACE_BT601_FULL
: SDL_COLORSPACE_BT601_LIMITED;
case AVCOL_SPC_BT2020_NCL:
case AVCOL_SPC_BT2020_CL:
return full_range ? SDL_COLORSPACE_BT2020_FULL
: SDL_COLORSPACE_BT2020_LIMITED;
default:
return SDL_COLORSPACE_JPEG;
}
}
static SDL_Texture *
sc_display_create_texture(struct sc_display *display,
struct sc_size size, enum AVColorSpace color_space,
enum AVColorRange color_range) {
SDL_PropertiesID props = SDL_CreateProperties();
if (!props) {
return NULL;
}
enum SDL_Colorspace sdl_color_space =
sc_display_to_sdl_color_space(color_space, color_range);
bool ok =
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
SDL_PIXELFORMAT_YV12);
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
SDL_TEXTUREACCESS_STREAMING);
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
size.width);
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
size.height);
ok &= SDL_SetNumberProperty(props,
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
sdl_color_space);
if (!ok) {
LOGE("Could not set texture properties");
SDL_DestroyProperties(props);
return NULL;
}
struct sc_size size) {
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
SDL_DestroyProperties(props);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
LOGD("Could not create texture: %s", SDL_GetError());
return NULL;
@@ -193,49 +140,24 @@ sc_display_create_texture(struct sc_display *display,
if (display->mipmaps) {
struct sc_opengl *gl = &display->gl;
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
if (!props) {
LOGE("Could not get texture properties: %s", SDL_GetError());
SDL_DestroyTexture(texture);
return NULL;
}
const char *renderer_name = SDL_GetRendererName(display->renderer);
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
SDL_DestroyProperties(props);
if (!texture_id) {
LOGE("Could not get texture id: %s", SDL_GetError());
SDL_DestroyTexture(texture);
return NULL;
}
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
display->texture_id = texture_id;
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
SDL_GL_BindTexture(texture, NULL, NULL);
// Enable trilinear filtering for downscaling
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
gl->BindTexture(GL_TEXTURE_2D, 0);
SDL_GL_UnbindTexture(texture);
}
return texture;
}
static inline void
sc_display_set_pending_texture(struct sc_display *display,
struct sc_size size,
enum AVColorRange color_range) {
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
assert(!display->texture);
display->pending.texture.size = size;
display->pending.texture.color_range = color_range;
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_TEXTURE;
display->pending.size = size;
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
}
static bool
@@ -248,7 +170,6 @@ 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);
@@ -260,31 +181,22 @@ 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_TEXTURE) {
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
assert(!display->texture);
display->texture =
sc_display_create_texture(display,
display->pending.texture.size,
display->pending.texture.color_space,
display->pending.texture.color_range);
sc_display_create_texture(display, display->pending.size);
if (!display->texture) {
return false;
}
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_TEXTURE;
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
}
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
assert(display->pending.frame);
bool ok = sc_display_update_texture_internal(display,
display->pending.frame);
bool ok = sc_display_update_texture(display, display->pending.frame);
if (!ok) {
return false;
}
@@ -297,18 +209,15 @@ sc_display_apply_pending(struct sc_display *display) {
}
static bool
sc_display_prepare_texture_internal(struct sc_display *display,
struct sc_size size,
enum AVColorSpace color_space,
enum AVColorRange color_range) {
sc_display_set_texture_size_internal(struct sc_display *display,
struct sc_size size) {
assert(size.width && size.height);
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
display->texture =
sc_display_create_texture(display, size, color_space, color_range);
display->texture = sc_display_create_texture(display, size);
if (!display->texture) {
return false;
}
@@ -318,13 +227,10 @@ sc_display_prepare_texture_internal(struct sc_display *display,
}
enum sc_display_result
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
enum AVColorSpace color_space,
enum AVColorRange color_range) {
bool ok = sc_display_prepare_texture_internal(display, size, color_space,
color_range);
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
bool ok = sc_display_set_texture_size_internal(display, size);
if (!ok) {
sc_display_set_pending_texture(display, size, color_range);
sc_display_set_pending_size(display, size);
return SC_DISPLAY_RESULT_PENDING;
}
@@ -332,25 +238,38 @@ sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
return SC_DISPLAY_RESULT_OK;
}
static SDL_YUV_CONVERSION_MODE
sc_display_to_sdl_color_range(enum AVColorRange color_range) {
return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG
: SDL_YUV_CONVERSION_AUTOMATIC;
}
static bool
sc_display_update_texture_internal(struct sc_display *display,
const AVFrame *frame) {
bool ok = SDL_UpdateYUVTexture(display->texture, NULL,
if (!display->has_frame) {
// First frame
display->has_frame = true;
// Configure YUV color range conversion
SDL_YUV_CONVERSION_MODE sdl_color_range =
sc_display_to_sdl_color_range(frame->color_range);
SDL_SetYUVConversionMode(sdl_color_range);
}
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (!ok) {
if (ret) {
LOGD("Could not update texture: %s", SDL_GetError());
return false;
}
if (display->mipmaps) {
assert(display->texture_id);
struct sc_opengl *gl = &display->gl;
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
gl->GenerateMipmap(GL_TEXTURE_2D);
gl->BindTexture(GL_TEXTURE_2D, 0);
SDL_GL_BindTexture(display->texture, NULL, NULL);
display->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(display->texture);
}
return true;
@@ -375,7 +294,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) {
sc_sdl_render_clear(display->renderer);
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
bool ok = sc_display_apply_pending(display);
@@ -388,10 +307,8 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
SDL_Texture *texture = display->texture;
if (orientation == SC_ORIENTATION_0) {
SDL_FRect frect;
SDL_RectToFRect(geometry, &frect);
bool ok = SDL_RenderTexture(renderer, texture, NULL, &frect);
if (!ok) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
@@ -399,27 +316,29 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
double angle = 90 * cw_rotation;
SDL_FRect frect;
const SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (sc_orientation_is_swap(orientation)) {
frect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
frect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
frect.w = geometry->h;
frect.h = geometry->w;
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
rect.w = geometry->h;
rect.h = geometry->w;
dstrect = &rect;
} else {
SDL_RectToFRect(geometry, &frect);
dstrect = geometry;
}
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
? SDL_FLIP_HORIZONTAL : 0;
bool ok = SDL_RenderTextureRotated(renderer, texture, NULL, &frect,
angle, NULL, flip);
if (!ok) {
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
NULL, flip);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
}
sc_sdl_render_present(display->renderer);
SDL_RenderPresent(display->renderer);
return SC_DISPLAY_RESULT_OK;
}

View File

@@ -6,7 +6,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <libavutil/frame.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include "coords.h"
#include "opengl.h"
@@ -26,19 +26,16 @@ struct sc_display {
#endif
bool mipmaps;
uint32_t texture_id; // only set if mipmaps is enabled
struct {
#define SC_DISPLAY_PENDING_FLAG_TEXTURE 1
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
int8_t flags;
struct {
struct sc_size size;
enum AVColorSpace color_space;
enum AVColorRange color_range;
} texture;
struct sc_size size;
AVFrame *frame;
} pending;
bool has_frame;
};
enum sc_display_result {
@@ -55,9 +52,7 @@ void
sc_display_destroy(struct sc_display *display);
enum sc_display_result
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
enum AVColorSpace color_space,
enum AVColorRange color_range);
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
enum sc_display_result
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);

View File

@@ -9,8 +9,11 @@ bool
sc_push_event_impl(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
bool ok = SDL_PushEvent(&event);
if (!ok) {
int ret = SDL_PushEvent(&event);
// ret < 0: error (queue full)
// ret == 0: event was filtered
// ret == 1: success
if (ret != 1) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
return false;
}
@@ -27,25 +30,34 @@ sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
.data2 = userdata,
},
};
bool ok = SDL_PushEvent(&event);
if (!ok) {
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
int ret = SDL_PushEvent(&event);
// ret < 0: error (queue full)
// ret == 0: event was filtered
// ret == 1: success
if (ret != 1) {
if (ret == 0) {
// if ret == 0, this is expected on exit, log in debug mode
LOGD("Could not post runnable to main thread (filtered)");
} else {
assert(ret < 0);
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
}
return false;
}
return true;
}
static bool SDLCALL
static int SDLCALL
task_event_filter(void *userdata, SDL_Event *event) {
(void) userdata;
if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) {
// Reject this event type from now on
return false;
return 0;
}
return true;
return 1;
}
void

View File

@@ -5,10 +5,10 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL_events.h>
#include <SDL2/SDL_events.h>
enum {
SC_EVENT_NEW_FRAME = SDL_EVENT_USER,
SC_EVENT_NEW_FRAME = SDL_USEREVENT,
SC_EVENT_RUN_ON_MAIN_THREAD,
SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED,
@@ -16,6 +16,7 @@ enum {
SC_EVENT_USB_DEVICE_DISCONNECTED,
SC_EVENT_DEMUXER_ERROR,
SC_EVENT_RECORDER_ERROR,
SC_EVENT_SCREEN_INIT_SIZE,
SC_EVENT_TIME_LIMIT_REACHED,
SC_EVENT_CONTROLLER_ERROR,
SC_EVENT_AOA_OPEN_ERROR,

View File

@@ -9,7 +9,7 @@
#include "hid/hid_event.h"
#include "input_events.h"
// See "SDL3/SDL_scancode.h".
// See "SDL2/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.

View File

@@ -166,12 +166,6 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
return c;
}
void
sc_hid_mouse_init(struct sc_hid_mouse *hid) {
hid->residual_hscroll = 0;
hid->residual_vscroll = 0;
}
void
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
const struct sc_mouse_motion_event *event) {
@@ -198,37 +192,22 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
data[4] = 0; // no horizontal scrolling
}
static int8_t
consume_scroll_integer(float *scroll) {
float value = CLAMP(*scroll, -127, 127);
int8_t consume = value; // truncate towards 0
float residual = value - consume;
*scroll = residual;
return consume;
}
bool
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
struct sc_hid_input *hid_input,
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_input_init(hid_input);
hid->residual_hscroll += event->hscroll;
hid->residual_vscroll += event->vscroll;
int8_t hscroll = consume_scroll_integer(&hid->residual_hscroll);
int8_t vscroll = consume_scroll_integer(&hid->residual_vscroll);
if (!hscroll && !vscroll) {
// Not enough scrolling to inject a scroll event
if (!event->vscroll_int && !event->hscroll_int) {
// Need a full integral value for HID
return false;
}
sc_hid_mouse_input_init(hid_input);
uint8_t *data = hid_input->data;
data[0] = 0; // buttons state irrelevant (and unknown)
data[1] = 0; // no x motion
data[2] = 0; // no y motion
data[3] = vscroll;
data[4] = hscroll;
data[3] = CLAMP(event->vscroll_int, -127, 127);
data[4] = CLAMP(event->hscroll_int, -127, 127);
return true;
}

View File

@@ -8,13 +8,6 @@
#define SC_HID_ID_MOUSE 2
struct sc_hid_mouse {
float residual_hscroll;
float residual_vscroll;
};
void sc_hid_mouse_init(struct sc_hid_mouse *hid);
void
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
@@ -30,8 +23,7 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
const struct sc_mouse_click_event *event);
bool
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
struct sc_hid_input *hid_input,
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
const struct sc_mouse_scroll_event *event);
#endif

View File

@@ -10,7 +10,7 @@
#include <libavutil/avutil.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include "config.h"
#include "util/env.h"
@@ -156,7 +156,13 @@ free_ctx:
return result;
}
static SDL_PixelFormat
#if !SDL_VERSION_ATLEAST(2, 0, 10)
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
// versions.
typedef int SDL_PixelFormatEnum;
#endif
static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) {
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
@@ -166,11 +172,13 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_XRGB1555;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_XBGR1555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_XRGB4444;
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_XBGR4444;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
#if SDL_VERSION_ATLEAST(2, 0, 12)
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
#endif
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN;
}
@@ -195,16 +203,20 @@ load_from_path(const char *path) {
goto error;
}
SDL_PixelFormat format = to_sdl_pixel_format(frame->format);
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
if (format == SDL_PIXELFORMAT_UNKNOWN) {
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
frame->format);
goto error;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
SDL_Surface *surface =
SDL_CreateSurfaceFrom(frame->width, frame->height, format,
frame->data[0], frame->linesize[0]);
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
frame->width, frame->height,
bits_per_pixel,
frame->linesize[0],
format);
if (!surface) {
LOGE("Could not create icon surface");
@@ -236,35 +248,17 @@ load_from_path(const char *path) {
#endif
}
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
if (!palette) {
LOGE("Could not create palette");
SDL_DestroySurface(surface);
goto error;
}
bool ok = SDL_SetPaletteColors(palette, colors, 0, 256);
if (!ok) {
SDL_Palette *palette = surface->format->palette;
assert(palette);
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
if (ret) {
LOGE("Could not set palette colors");
SDL_DestroySurface(surface);
SDL_FreeSurface(surface);
goto error;
}
}
SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
if (!props) {
LOGE("Could not get surface properties: %s", SDL_GetError());
SDL_DestroySurface(surface);
goto error;
}
// frame owns the data
bool ok = SDL_SetPointerProperty(props, "sc_frame", frame);
if (!ok) {
LOGE("Could not set pointer property: %s", SDL_GetError());
SDL_DestroySurface(surface);
goto error;
}
surface->userdata = frame; // frame owns the data
return surface;
@@ -287,10 +281,8 @@ scrcpy_icon_load(void) {
void
scrcpy_icon_destroy(SDL_Surface *icon) {
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
assert(props);
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);
AVFrame *frame = icon->userdata;
assert(frame);
av_frame_free(&frame);
SDL_DestroySurface(icon);
SDL_FreeSurface(icon);
}

View File

@@ -3,7 +3,7 @@
#include "common.h"
#include <SDL3/SDL_surface.h>
#include <SDL2/SDL_surface.h>
SDL_Surface *
scrcpy_icon_load(void);

View File

@@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL_events.h>
#include <SDL2/SDL_events.h>
#include "coords.h"
@@ -14,8 +14,7 @@
* for simplicity.
*
* This scrcpy input events API is designed to be consumed by input event
* processors (sc_key_processor, sc_mouse_processor and sc_gamepad_processor,
* see app/src/trait/).
* processors (sc_key_processor and sc_mouse_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
@@ -44,17 +43,17 @@
*/
enum sc_mod {
SC_MOD_LSHIFT = SDL_KMOD_LSHIFT,
SC_MOD_RSHIFT = SDL_KMOD_RSHIFT,
SC_MOD_LCTRL = SDL_KMOD_LCTRL,
SC_MOD_RCTRL = SDL_KMOD_RCTRL,
SC_MOD_LALT = SDL_KMOD_LALT,
SC_MOD_RALT = SDL_KMOD_RALT,
SC_MOD_LGUI = SDL_KMOD_LGUI,
SC_MOD_RGUI = SDL_KMOD_RGUI,
SC_MOD_LSHIFT = KMOD_LSHIFT,
SC_MOD_RSHIFT = KMOD_RSHIFT,
SC_MOD_LCTRL = KMOD_LCTRL,
SC_MOD_RCTRL = KMOD_RCTRL,
SC_MOD_LALT = KMOD_LALT,
SC_MOD_RALT = KMOD_RALT,
SC_MOD_LGUI = KMOD_LGUI,
SC_MOD_RGUI = KMOD_RGUI,
SC_MOD_NUM = SDL_KMOD_NUM,
SC_MOD_CAPS = SDL_KMOD_CAPS,
SC_MOD_NUM = KMOD_NUM,
SC_MOD_CAPS = KMOD_CAPS,
};
enum sc_action {
@@ -71,12 +70,12 @@ enum sc_keycode {
SC_KEYCODE_TAB = SDLK_TAB,
SC_KEYCODE_SPACE = SDLK_SPACE,
SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM,
SC_KEYCODE_QUOTEDBL = SDLK_DBLAPOSTROPHE,
SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL,
SC_KEYCODE_HASH = SDLK_HASH,
SC_KEYCODE_PERCENT = SDLK_PERCENT,
SC_KEYCODE_DOLLAR = SDLK_DOLLAR,
SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND,
SC_KEYCODE_QUOTE = SDLK_APOSTROPHE,
SC_KEYCODE_QUOTE = SDLK_QUOTE,
SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN,
SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN,
SC_KEYCODE_ASTERISK = SDLK_ASTERISK,
@@ -108,33 +107,33 @@ enum sc_keycode {
SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET,
SC_KEYCODE_CARET = SDLK_CARET,
SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE,
SC_KEYCODE_BACKQUOTE = SDLK_GRAVE,
SC_KEYCODE_a = SDLK_A,
SC_KEYCODE_b = SDLK_B,
SC_KEYCODE_c = SDLK_C,
SC_KEYCODE_d = SDLK_D,
SC_KEYCODE_e = SDLK_E,
SC_KEYCODE_f = SDLK_F,
SC_KEYCODE_g = SDLK_G,
SC_KEYCODE_h = SDLK_H,
SC_KEYCODE_i = SDLK_I,
SC_KEYCODE_j = SDLK_J,
SC_KEYCODE_k = SDLK_K,
SC_KEYCODE_l = SDLK_L,
SC_KEYCODE_m = SDLK_M,
SC_KEYCODE_n = SDLK_N,
SC_KEYCODE_o = SDLK_O,
SC_KEYCODE_p = SDLK_P,
SC_KEYCODE_q = SDLK_Q,
SC_KEYCODE_r = SDLK_R,
SC_KEYCODE_s = SDLK_S,
SC_KEYCODE_t = SDLK_T,
SC_KEYCODE_u = SDLK_U,
SC_KEYCODE_v = SDLK_V,
SC_KEYCODE_w = SDLK_W,
SC_KEYCODE_x = SDLK_X,
SC_KEYCODE_y = SDLK_Y,
SC_KEYCODE_z = SDLK_Z,
SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE,
SC_KEYCODE_a = SDLK_a,
SC_KEYCODE_b = SDLK_b,
SC_KEYCODE_c = SDLK_c,
SC_KEYCODE_d = SDLK_d,
SC_KEYCODE_e = SDLK_e,
SC_KEYCODE_f = SDLK_f,
SC_KEYCODE_g = SDLK_g,
SC_KEYCODE_h = SDLK_h,
SC_KEYCODE_i = SDLK_i,
SC_KEYCODE_j = SDLK_j,
SC_KEYCODE_k = SDLK_k,
SC_KEYCODE_l = SDLK_l,
SC_KEYCODE_m = SDLK_m,
SC_KEYCODE_n = SDLK_n,
SC_KEYCODE_o = SDLK_o,
SC_KEYCODE_p = SDLK_p,
SC_KEYCODE_q = SDLK_q,
SC_KEYCODE_r = SDLK_r,
SC_KEYCODE_s = SDLK_s,
SC_KEYCODE_t = SDLK_t,
SC_KEYCODE_u = SDLK_u,
SC_KEYCODE_v = SDLK_v,
SC_KEYCODE_w = SDLK_w,
SC_KEYCODE_x = SDLK_x,
SC_KEYCODE_y = SDLK_y,
SC_KEYCODE_z = SDLK_z,
SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK,
@@ -316,40 +315,43 @@ enum sc_scancode {
// to avoid unnecessary conversions (and confusion).
enum sc_mouse_button {
SC_MOUSE_BUTTON_UNKNOWN = 0,
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON_MASK(SDL_BUTTON_LEFT),
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON_MASK(SDL_BUTTON_RIGHT),
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE),
SC_MOUSE_BUTTON_X1 = SDL_BUTTON_MASK(SDL_BUTTON_X1),
SC_MOUSE_BUTTON_X2 = SDL_BUTTON_MASK(SDL_BUTTON_X2),
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT),
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT),
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE),
SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1),
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(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,
SC_GAMEPAD_AXIS_LEFTY = SDL_GAMEPAD_AXIS_LEFTY,
SC_GAMEPAD_AXIS_RIGHTX = SDL_GAMEPAD_AXIS_RIGHTX,
SC_GAMEPAD_AXIS_RIGHTY = SDL_GAMEPAD_AXIS_RIGHTY,
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
SC_GAMEPAD_AXIS_LEFTX = SDL_CONTROLLER_AXIS_LEFTX,
SC_GAMEPAD_AXIS_LEFTY = SDL_CONTROLLER_AXIS_LEFTY,
SC_GAMEPAD_AXIS_RIGHTX = SDL_CONTROLLER_AXIS_RIGHTX,
SC_GAMEPAD_AXIS_RIGHTY = SDL_CONTROLLER_AXIS_RIGHTY,
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
};
enum sc_gamepad_button {
SC_GAMEPAD_BUTTON_UNKNOWN = -1,
SC_GAMEPAD_BUTTON_SOUTH = SDL_GAMEPAD_BUTTON_SOUTH,
SC_GAMEPAD_BUTTON_EAST = SDL_GAMEPAD_BUTTON_EAST,
SC_GAMEPAD_BUTTON_WEST = SDL_GAMEPAD_BUTTON_WEST,
SC_GAMEPAD_BUTTON_NORTH = SDL_GAMEPAD_BUTTON_NORTH,
SC_GAMEPAD_BUTTON_BACK = SDL_GAMEPAD_BUTTON_BACK,
SC_GAMEPAD_BUTTON_GUIDE = SDL_GAMEPAD_BUTTON_GUIDE,
SC_GAMEPAD_BUTTON_START = SDL_GAMEPAD_BUTTON_START,
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_GAMEPAD_BUTTON_LEFT_STICK,
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_GAMEPAD_BUTTON_RIGHT_STICK,
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_GAMEPAD_BUTTON_DPAD_UP,
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_GAMEPAD_BUTTON_DPAD_DOWN,
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_GAMEPAD_BUTTON_DPAD_LEFT,
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
SC_GAMEPAD_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A,
SC_GAMEPAD_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B,
SC_GAMEPAD_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X,
SC_GAMEPAD_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y,
SC_GAMEPAD_BUTTON_BACK = SDL_CONTROLLER_BUTTON_BACK,
SC_GAMEPAD_BUTTON_GUIDE = SDL_CONTROLLER_BUTTON_GUIDE,
SC_GAMEPAD_BUTTON_START = SDL_CONTROLLER_BUTTON_START,
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_CONTROLLER_BUTTON_LEFTSTICK,
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP,
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
};
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
@@ -391,6 +393,8 @@ struct sc_mouse_scroll_event {
struct sc_position position;
float hscroll;
float vscroll;
int32_t hscroll_int;
int32_t vscroll_int;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
@@ -409,9 +413,10 @@ struct sc_touch_event {
float pressure;
};
// As documented in <https://wiki.libsdl.org/SDL3/SDL_JoystickID>:
// The value 0 is an invalid ID.
#define SC_GAMEPAD_ID_INVALID 0
// 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)
struct sc_gamepad_device_event {
uint32_t gamepad_id;
@@ -446,8 +451,8 @@ sc_scancode_from_sdl(SDL_Scancode scancode) {
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_EVENT_KEY_DOWN || type == SDL_EVENT_KEY_UP);
if (type == SDL_EVENT_KEY_DOWN) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
@@ -455,8 +460,8 @@ sc_action_from_sdl_keyboard_type(uint32_t type) {
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_EVENT_MOUSE_BUTTON_DOWN || type == SDL_EVENT_MOUSE_BUTTON_UP);
if (type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
@@ -464,12 +469,12 @@ sc_action_from_sdl_mousebutton_type(uint32_t type) {
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_EVENT_FINGER_MOTION || type == SDL_EVENT_FINGER_DOWN ||
type == SDL_EVENT_FINGER_UP);
if (type == SDL_EVENT_FINGER_MOTION) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_EVENT_FINGER_DOWN) {
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
@@ -479,7 +484,7 @@ static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON_MASK(button);
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
@@ -495,9 +500,9 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
static inline enum sc_gamepad_axis
sc_gamepad_axis_from_sdl(uint8_t axis) {
if (axis <= SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
// SC_GAMEPAD_AXIS_* constants are initialized from
// SDL_GAMEPAD_AXIS_*
// SDL_CONTROLLER_AXIS_*
return axis;
}
return SC_GAMEPAD_AXIS_UNKNOWN;
@@ -505,18 +510,18 @@ sc_gamepad_axis_from_sdl(uint8_t axis) {
static inline enum sc_gamepad_button
sc_gamepad_button_from_sdl(uint8_t button) {
if (button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
// SC_GAMEPAD_BUTTON_* constants are initialized from
// SDL_GAMEPAD_BUTTON_*
// SDL_CONTROLLER_BUTTON_*
return button;
}
return SC_GAMEPAD_BUTTON_UNKNOWN;
}
static inline enum sc_action
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) {
sc_action_from_sdl_controllerbutton_type(uint32_t type) {
assert(type == SDL_CONTROLLERBUTTONDOWN || type == SDL_CONTROLLERBUTTONUP);
if (type == SDL_CONTROLLERBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;

View File

@@ -3,7 +3,7 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include "android/input.h"
#include "android/keycodes.h"
@@ -11,7 +11,6 @@
#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,
@@ -375,11 +374,11 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool paused = im->screen->paused;
bool video = im->screen->video;
SDL_Keycode sdl_keycode = event->key;
uint16_t mod = event->mod;
bool down = event->type == SDL_EVENT_KEY_DOWN;
bool ctrl = event->mod & SDL_KMOD_CTRL;
bool shift = event->mod & SDL_KMOD_SHIFT;
SDL_Keycode sdl_keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat;
// Either the modifier includes a shortcut modifier, or the key
@@ -403,39 +402,39 @@ sc_input_manager_process_key(struct sc_input_manager *im,
if (is_shortcut) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (sdl_keycode) {
case SDLK_H:
case SDLK_h:
if (im->kp && !shift && !repeat && !paused) {
action_home(im, action);
}
return;
case SDLK_B: // fall-through
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (im->kp && !shift && !repeat && !paused) {
action_back(im, action);
}
return;
case SDLK_S:
case SDLK_s:
if (im->kp && !shift && !repeat && !paused) {
action_app_switch(im, action);
}
return;
case SDLK_M:
case SDLK_m:
if (im->kp && !shift && !repeat && !paused) {
action_menu(im, action);
}
return;
case SDLK_P:
case SDLK_p:
if (im->kp && !shift && !repeat && !paused) {
action_power(im, action);
}
return;
case SDLK_O:
case SDLK_o:
if (control && !repeat && down && !paused) {
bool on = shift;
set_display_power(im, on);
}
return;
case SDLK_Z:
case SDLK_z:
if (video && down && !repeat) {
sc_screen_set_paused(im->screen, !shift);
}
@@ -484,17 +483,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
return;
case SDLK_C:
case SDLK_c:
if (im->kp && !shift && !repeat && down && !paused) {
get_device_clipboard(im, SC_COPY_KEY_COPY);
}
return;
case SDLK_X:
case SDLK_x:
if (im->kp && !shift && !repeat && down && !paused) {
get_device_clipboard(im, SC_COPY_KEY_CUT);
}
return;
case SDLK_V:
case SDLK_v:
if (im->kp && !repeat && down && !paused) {
if (shift || im->legacy_paste) {
// inject the text as input events
@@ -506,27 +505,27 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
return;
case SDLK_F:
case SDLK_f:
if (video && !shift && !repeat && down) {
sc_screen_toggle_fullscreen(im->screen);
}
return;
case SDLK_W:
case SDLK_w:
if (video && !shift && !repeat && down) {
sc_screen_resize_to_fit(im->screen);
}
return;
case SDLK_G:
case SDLK_g:
if (video && !shift && !repeat && down) {
sc_screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_I:
case SDLK_i:
if (video && !shift && !repeat && down) {
switch_fps_counter_state(im);
}
return;
case SDLK_N:
case SDLK_n:
if (control && !repeat && down && !paused) {
if (shift) {
collapse_panels(im);
@@ -537,7 +536,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
return;
case SDLK_R:
case SDLK_r:
if (control && !repeat && down && !paused) {
if (shift) {
reset_video(im);
@@ -546,7 +545,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
return;
case SDLK_K:
case SDLK_k:
if (control && !shift && !repeat && down && !paused
&& im->kp && im->kp->hid) {
// Only if the current keyboard is hid
@@ -563,7 +562,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_V && down && !repeat;
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_v && down && !repeat;
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
@@ -596,7 +595,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
enum sc_scancode scancode = sc_scancode_from_sdl(event->scancode);
enum sc_scancode scancode = sc_scancode_from_sdl(event->keysym.scancode);
if (scancode == SC_SCANCODE_UNKNOWN) {
return;
}
@@ -606,7 +605,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
.keycode = keycode,
.scancode = scancode,
.repeat = event->repeat,
.mods_state = sc_mods_state_from_sdl(event->mod),
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
};
assert(im->kp->ops->process_key);
@@ -673,12 +672,13 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
return;
}
struct sc_size drawable_size =
sc_sdl_get_window_size_in_pixels(im->screen->window);
int dw;
int dh;
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = event->x * (int32_t) drawable_size.width;
int32_t y = event->y * (int32_t) drawable_size.height;
int32_t x = event->x * dw;
int32_t y = event->y * dh;
struct sc_touch_event evt = {
.position = {
@@ -687,7 +687,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
},
.action = sc_touch_action_from_sdl(event->type),
.pointer_id = event->fingerID,
.pointer_id = event->fingerId,
.pressure = event->pressure,
};
@@ -723,7 +723,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool control = im->controller;
bool paused = im->screen->paused;
bool down = event->type == SDL_EVENT_MOUSE_BUTTON_DOWN;
bool down = event->type == SDL_MOUSEBUTTONDOWN;
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
@@ -736,8 +736,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
}
SDL_Keymod keymod = SDL_GetModState();
bool ctrl_pressed = keymod & SDL_KMOD_CTRL;
bool shift_pressed = keymod & SDL_KMOD_SHIFT;
bool ctrl_pressed = keymod & KMOD_CTRL;
bool shift_pressed = keymod & KMOD_SHIFT;
if (control && !paused) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
@@ -889,15 +889,22 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
}
// mouse_x and mouse_y are expressed in pixels relative to the window
float mouse_x;
float mouse_y;
int mouse_x;
int mouse_y;
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
(void) buttons; // Actual buttons are tracked manually to ignore shortcuts
struct sc_mouse_scroll_event evt = {
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
#if SDL_VERSION_ATLEAST(2, 0, 18)
.hscroll = event->preciseX,
.vscroll = event->preciseY,
#else
.hscroll = event->x,
.vscroll = event->y,
#endif
.hscroll_int = event->x,
.vscroll_int = event->y,
.buttons_state = im->mouse_buttons_state,
};
@@ -906,31 +913,31 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
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 *sdl_gamepad = SDL_OpenGamepad(event->which);
if (!sdl_gamepad) {
LOGW("Could not open gamepad");
const SDL_ControllerDeviceEvent *event) {
if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) {
LOGW("Could not open game controller");
return;
}
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
if (!joystick) {
LOGW("Could not get gamepad joystick");
SDL_CloseGamepad(sdl_gamepad);
LOGW("Could not get controller joystick");
SDL_GameControllerClose(gc);
return;
}
struct sc_gamepad_device_event evt = {
.gamepad_id = SDL_GetJoystickID(joystick),
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
im->gp->ops->process_gamepad_added(im->gp, &evt);
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
SDL_JoystickID id = event->which;
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
if (sdl_gamepad) {
SDL_CloseGamepad(sdl_gamepad);
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
SDL_GameControllerClose(gc);
} else {
LOGW("Unknown gamepad device removed");
}
@@ -947,7 +954,7 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
const SDL_GamepadAxisEvent *event) {
const SDL_ControllerAxisEvent *event) {
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
return;
@@ -963,7 +970,7 @@ sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
const SDL_GamepadButtonEvent *event) {
const SDL_ControllerButtonEvent *event) {
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
return;
@@ -971,7 +978,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_gamepad_button_type(event->type),
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = button,
};
im->gp->ops->process_gamepad_button(im->gp, &evt);
@@ -986,8 +993,8 @@ is_apk(const char *file) {
static void
sc_input_manager_process_file(struct sc_input_manager *im,
const SDL_DropEvent *event) {
assert(event->type == SDL_EVENT_DROP_FILE);
char *file = strdup(event->data);
char *file = strdup(event->file);
SDL_free(event->file);
if (!file) {
LOG_OOM();
return;
@@ -1011,66 +1018,66 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
bool control = im->controller;
bool paused = im->screen->paused;
switch (event->type) {
case SDL_EVENT_TEXT_INPUT:
case SDL_TEXTINPUT:
if (!im->kp || paused) {
break;
}
sc_input_manager_process_text_input(im, &event->text);
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
sc_input_manager_process_key(im, &event->key);
break;
case SDL_EVENT_MOUSE_MOTION:
case SDL_MOUSEMOTION:
if (!im->mp || paused) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_EVENT_MOUSE_WHEEL:
case SDL_MOUSEWHEEL:
if (!im->mp || paused) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
sc_input_manager_process_mouse_button(im, &event->button);
break;
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP:
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!im->mp || paused) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Handle device added or removed even if paused
if (!im->gp) {
break;
}
sc_input_manager_process_gamepad_device(im, &event->gdevice);
sc_input_manager_process_gamepad_device(im, &event->cdevice);
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_CONTROLLERAXISMOTION:
if (!im->gp || paused) {
break;
}
sc_input_manager_process_gamepad_axis(im, &event->gaxis);
sc_input_manager_process_gamepad_axis(im, &event->caxis);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (!im->gp || paused) {
break;
}
sc_input_manager_process_gamepad_button(im, &event->gbutton);
sc_input_manager_process_gamepad_button(im, &event->cbutton);
break;
case SDL_EVENT_DROP_FILE: {
case SDL_DROPFILE: {
if (!control) {
break;
}

View File

@@ -5,8 +5,8 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keycode.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include "controller.h"
#include "file_pusher.h"

View File

@@ -5,14 +5,13 @@
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
#include <SDL3/SDL.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#ifdef HAVE_USB
# include "usb/scrcpy_otg.h"
#endif
#include "usb/scrcpy_otg.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"

View File

@@ -20,11 +20,14 @@ bool
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
const SDL_Event *event) {
switch (event->type) {
case SDL_EVENT_WINDOW_FOCUS_LOST:
sc_mouse_capture_set_active(mc, false);
return true;
case SDL_EVENT_KEY_DOWN: {
SDL_Keycode key = event->key.key;
case SDL_WINDOWEVENT:
if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
sc_mouse_capture_set_active(mc, false);
return true;
}
break;
case SDL_KEYDOWN: {
SDL_Keycode key = event->key.keysym.sym;
if (sc_mouse_capture_is_capture_key(mc, key)) {
if (!mc->mouse_capture_key_pressed) {
mc->mouse_capture_key_pressed = key;
@@ -38,8 +41,8 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
}
break;
}
case SDL_EVENT_KEY_UP: {
SDL_Keycode key = event->key.key;
case SDL_KEYUP: {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = mc->mouse_capture_key_pressed;
mc->mouse_capture_key_pressed = 0;
if (sc_mouse_capture_is_capture_key(mc, key)) {
@@ -53,24 +56,24 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
}
break;
}
case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (!sc_mouse_capture_is_active(mc)) {
// The mouse will be captured on SDL_EVENT_MOUSE_BUTTON_UP, so
// consume the event
// The mouse will be captured on SDL_MOUSEBUTTONUP, so consume
// the event
return true;
}
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_MOUSEBUTTONUP:
if (!sc_mouse_capture_is_active(mc)) {
sc_mouse_capture_set_active(mc, true);
return true;
}
break;
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP:
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
// Touch events are not compatible with relative mode
// (coordinates are not relative), so consume the event
return true;
@@ -81,8 +84,27 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
void
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
bool ok = SDL_SetWindowRelativeMouseMode(mc->window, capture);
if (!ok) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(mc->window, &x, &y);
SDL_GetWindowSize(mc->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(mc->window, w / 2, h / 2);
}
}
#else
(void) mc;
#endif
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
}
@@ -90,7 +112,8 @@ sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
bool
sc_mouse_capture_is_active(struct sc_mouse_capture *mc) {
return SDL_GetWindowRelativeMouseMode(mc->window);
(void) mc;
return SDL_GetRelativeMouseMode();
}
void

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
struct sc_mouse_capture {
SDL_Window *window;

View File

@@ -3,29 +3,21 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
void
sc_opengl_init(struct sc_opengl *gl) {
gl->GetString = (const GLubyte *(*)(GLenum))
SDL_GL_GetProcAddress("glGetString");
gl->GetString = SDL_GL_GetProcAddress("glGetString");
assert(gl->GetString);
gl->BindTexture = (void (*)(GLenum, GLuint))
SDL_GL_GetProcAddress("glBindTexture");
assert(gl->BindTexture);
gl->TexParameterf = (void (*)(GLenum, GLenum, GLfloat))
SDL_GL_GetProcAddress("glTexParameterf");
gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf");
assert(gl->TexParameterf);
gl->TexParameteri = (void (*)(GLenum, GLenum, GLint))
SDL_GL_GetProcAddress("glTexParameteri");
gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri");
assert(gl->TexParameteri);
// optional
gl->GenerateMipmap = (void (*)(GLenum))
SDL_GL_GetProcAddress("glGenerateMipmap");
gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap");
const char *version = (const char *) gl->GetString(GL_VERSION);
assert(version);

View File

@@ -4,7 +4,7 @@
#include "common.h"
#include <stdbool.h>
#include <SDL3/SDL_opengl.h>
#include <SDL2/SDL_opengl.h>
struct sc_opengl {
const char *version;
@@ -15,9 +15,6 @@ struct sc_opengl {
const GLubyte *
(*GetString)(GLenum name);
void
(*BindTexture)(GLenum target, GLuint texture);
void
(*TexParameterf)(GLenum target, GLenum pname, GLfloat param);

View File

@@ -2,8 +2,7 @@
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <SDL3/SDL_clipboard.h>
#include <SDL2/SDL_clipboard.h>
#include "device_msg.h"
#include "events.h"
@@ -54,12 +53,8 @@ task_set_clipboard(void *userdata) {
if (same) {
LOGD("Computer clipboard unchanged");
} else {
bool ok = SDL_SetClipboardText(text);
if (ok) {
LOGI("Device clipboard copied");
} else {
LOGE("Could not set clipboard: %s", SDL_GetError());
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(text);
}
free(text);

View File

@@ -6,7 +6,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
@@ -94,7 +94,7 @@ struct scrcpy {
#ifdef _WIN32
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT || ctrl_type == CTRL_BREAK_EVENT) {
sc_push_event(SDL_EVENT_QUIT);
sc_push_event(SDL_QUIT);
return TRUE;
}
return FALSE;
@@ -108,26 +108,41 @@ sdl_set_hints(const char *render_driver) {
}
// App name used in various contexts (such as PulseAudio)
#if defined(SCRCPY_SDL_HAS_HINT_APP_NAME)
if (!SDL_SetHint(SDL_HINT_APP_NAME, "scrcpy")) {
LOGW("Could not set app name");
}
#elif defined(SCRCPY_SDL_HAS_HINT_AUDIO_DEVICE_APP_NAME)
if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "scrcpy")) {
LOGW("Could not set audio device app name");
}
#endif
// Linear filtering
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering");
}
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
// better not to generate them in the first place.
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
LOGW("Could not disable synthetic mouse events");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
LOGW("Could not disable X11 compositor bypass");
}
#endif
// Do not minimize on focus loss
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
@@ -154,15 +169,9 @@ sdl_configure(bool video_playback, bool disable_screensaver) {
}
if (disable_screensaver) {
bool ok = SDL_DisableScreenSaver();
if (!ok) {
LOGW("Could not disable screen saver");
}
SDL_DisableScreenSaver();
} else {
bool ok = SDL_EnableScreenSaver();
if (!ok) {
LOGW("Could not enable screen saver");
}
SDL_EnableScreenSaver();
}
}
@@ -189,7 +198,7 @@ event_loop(struct scrcpy *s, bool has_screen) {
case SC_EVENT_TIME_LIMIT_REACHED:
LOGI("Time limit reached");
return SCRCPY_EXIT_SUCCESS;
case SDL_EVENT_QUIT:
case SDL_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
case SC_EVENT_RUN_ON_MAIN_THREAD: {
@@ -229,7 +238,7 @@ await_for_server(bool *connected) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
case SDL_QUIT:
if (connected) {
*connected = false;
}
@@ -355,21 +364,14 @@ scrcpy_generate_scid(void) {
static void
init_sdl_gamepads(void) {
// Trigger a SDL_EVENT_GAMEPAD_ADDED event for all gamepads already
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
// connected
int count;
SDL_JoystickID *joysticks = SDL_GetJoysticks(&count);
if (!joysticks) {
LOGE("Could not list joysticks: %s", SDL_GetError());
return;
}
for (int i = 0; i < count; ++i) {
SDL_JoystickID joystick = joysticks[i];
if (SDL_IsGamepad(joystick)) {
int num_joysticks = SDL_NumJoysticks();
for (int i = 0; i < num_joysticks; ++i) {
if (SDL_IsGameController(i)) {
SDL_Event event;
event.gdevice.type = SDL_EVENT_GAMEPAD_ADDED;
event.gdevice.which = i;
event.cdevice.type = SDL_CONTROLLERDEVICEADDED;
event.cdevice.which = i;
SDL_PushEvent(&event);
}
}
@@ -385,7 +387,7 @@ scrcpy(struct scrcpy_options *options) {
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
if (!SDL_Init(SDL_INIT_EVENTS)) {
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError());
return SCRCPY_EXIT_FAILURE;
}
@@ -511,7 +513,7 @@ scrcpy(struct scrcpy_options *options) {
// --no-video-playback is passed so that clipboard synchronization
// still works.
// <https://github.com/Genymobile/scrcpy/issues/4418>
if (!SDL_Init(SDL_INIT_VIDEO)) {
if (SDL_Init(SDL_INIT_VIDEO)) {
// If it fails, it is an error only if video playback is enabled
if (options->video_playback) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
@@ -523,14 +525,14 @@ scrcpy(struct scrcpy_options *options) {
}
if (options->audio_playback) {
if (!SDL_Init(SDL_INIT_AUDIO)) {
if (SDL_Init(SDL_INIT_AUDIO)) {
LOGE("Could not initialize SDL audio: %s", SDL_GetError());
goto end;
}
}
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
goto end;
}

View File

@@ -2,13 +2,12 @@
#include <assert.h>
#include <string.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.h"
#include "options.h"
#include "util/log.h"
#include "util/sdl.h"
#define DISPLAY_MARGINS 96
@@ -27,25 +26,45 @@ get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
return oriented_size;
}
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 window size in a struct sc_size
static struct sc_size
get_window_size(const struct sc_screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
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;
SDL_GetWindowPosition(screen->window, &x, &y);
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);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
}
// get the preferred display bounds (i.e. the screen bounds with some margins)
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) {
if (SDL_GetDisplayUsableBounds(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false;
}
@@ -147,10 +166,13 @@ static void
sc_screen_update_content_rect(struct sc_screen *screen) {
assert(screen->video);
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct sc_size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct sc_size drawable_size =
sc_sdl_get_window_size_in_pixels(screen->window);
struct sc_size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect;
@@ -186,7 +208,6 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
assert(screen->video);
assert(screen->has_video_window);
if (update_content_rect) {
sc_screen_update_content_rect(screen);
@@ -214,18 +235,18 @@ sc_screen_render_novideo(struct sc_screen *screen) {
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static bool
static int
event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data;
assert(screen->video);
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_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);
}
return true;
return 0;
}
#endif
@@ -236,7 +257,6 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
(void) ctx;
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
if (ctx->width <= 0 || ctx->width > 0xFFFF
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
@@ -244,6 +264,19 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
return false;
}
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
// screen->frame_size is never used before the event is pushed, and the
// event acts as a memory barrier so it is safe without mutex
screen->frame_size.width = ctx->width;
screen->frame_size.height = ctx->height;
// Post the event on the UI thread (the texture must be created from there)
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
if (!ok) {
return false;
}
#ifndef NDEBUG
screen->open = true;
#endif
@@ -294,7 +327,9 @@ sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
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;
@@ -325,7 +360,7 @@ sc_screen_init(struct sc_screen *screen,
}
}
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
@@ -359,24 +394,15 @@ sc_screen_init(struct sc_screen *screen,
}
// The window will be positioned and sized on first video frame
screen->window =
sc_sdl_create_window(title, x, y, width, height, window_flags);
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
}
ok = SDL_StartTextInput(screen->window);
if (!ok) {
LOGE("Could not enable text input: %s", SDL_GetError());
goto error_destroy_window;
}
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
if (!SDL_SetWindowIcon(screen->window, icon)) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
SDL_SetWindowIcon(screen->window, icon);
} else if (params->video) {
// just a warning
LOGW("Could not load icon");
@@ -423,11 +449,7 @@ sc_screen_init(struct sc_screen *screen,
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (screen->video) {
ok = SDL_AddEventWatch(event_watcher, screen);
if (!ok) {
LOGW("Could not add event watcher for continuous resizing: %s",
SDL_GetError());
}
SDL_AddEventWatch(event_watcher, screen);
}
#endif
@@ -468,18 +490,13 @@ 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);
assert(is_windowed(screen));
sc_sdl_set_window_size(screen->window, window_size);
sc_sdl_set_window_position(screen->window, position);
set_window_size(screen, window_size);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_toggle_fullscreen(screen);
@@ -489,13 +506,13 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_fps_counter_start(&screen->fps_counter);
}
sc_sdl_show_window(screen->window);
SDL_ShowWindow(screen->window);
sc_screen_update_content_rect(screen);
}
void
sc_screen_hide_window(struct sc_screen *screen) {
sc_sdl_hide_window(screen->window);
SDL_HideWindow(screen->window);
}
void
@@ -525,7 +542,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 = sc_sdl_get_window_size(screen->window);
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width,
@@ -533,15 +550,14 @@ 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);
assert(is_windowed(screen));
sc_sdl_set_window_size(screen->window, target_size);
set_window_size(screen, target_size);
}
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
assert(screen->video);
if (is_windowed(screen)) {
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
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
@@ -557,7 +573,9 @@ static void
apply_pending_resize(struct sc_screen *screen) {
assert(screen->video);
assert(is_windowed(screen));
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
if (screen->resize_pending) {
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
@@ -585,6 +603,44 @@ sc_screen_set_orientation(struct sc_screen *screen,
sc_screen_render(screen, true);
}
static bool
sc_screen_init_size(struct sc_screen *screen) {
// Before first frame
assert(!screen->has_frame);
// The requested size is passed via screen->frame_size
struct sc_size content_size =
get_oriented_size(screen->frame_size, screen->orientation);
screen->content_size = content_size;
enum sc_display_result res =
sc_display_set_texture_size(&screen->display, screen->frame_size);
return res != SC_DISPLAY_RESULT_ERROR;
}
// recreate the texture and resize the window if the frame size has changed
static enum sc_display_result
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
assert(screen->video);
if (screen->frame_size.width == new_frame_size.width
&& screen->frame_size.height == new_frame_size.height) {
return SC_DISPLAY_RESULT_OK;
}
// frame dimension changed
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
return sc_display_set_texture_size(&screen->display, screen->frame_size);
}
static bool
sc_screen_apply_frame(struct sc_screen *screen) {
assert(screen->video);
@@ -593,38 +649,7 @@ sc_screen_apply_frame(struct sc_screen *screen) {
AVFrame *frame = screen->frame;
struct sc_size new_frame_size = {frame->width, frame->height};
if (!screen->has_frame
|| screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
if (screen->has_frame) {
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
} else {
// This is the first frame
screen->has_frame = true;
screen->content_size = new_content_size;
}
enum sc_display_result res =
sc_display_prepare_texture(&screen->display, screen->frame_size,
frame->colorspace, frame->color_range);
if (res == SC_DISPLAY_RESULT_ERROR) {
return false;
}
if (res == SC_DISPLAY_RESULT_PENDING) {
// Not an error, but do not continue
return true;
}
}
enum sc_display_result res =
sc_display_update_texture(&screen->display, frame);
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
if (res == SC_DISPLAY_RESULT_ERROR) {
return false;
}
@@ -633,9 +658,17 @@ sc_screen_apply_frame(struct sc_screen *screen) {
return true;
}
assert(screen->has_frame);
if (!screen->has_video_window) {
screen->has_video_window = true;
res = sc_display_update_texture(&screen->display, frame);
if (res == SC_DISPLAY_RESULT_ERROR) {
return false;
}
if (res == SC_DISPLAY_RESULT_PENDING) {
// Not an error, but do not continue
return true;
}
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
@@ -705,28 +738,31 @@ void
sc_screen_toggle_fullscreen(struct sc_screen *screen) {
assert(screen->video);
bool req_fullscreen =
!(SDL_GetWindowFlags(screen->window) & SDL_WINDOW_FULLSCREEN);
bool ok = SDL_SetWindowFullscreen(screen->window, req_fullscreen);
if (!ok) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
return;
}
LOGD("Requested %s mode", req_fullscreen ? "fullscreen" : "windowed");
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);
}
void
sc_screen_resize_to_fit(struct sc_screen *screen) {
assert(screen->video);
if (!is_windowed(screen)) {
if (screen->fullscreen || screen->maximized || screen->minimized) {
return;
}
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_point point = get_window_position(screen);
struct sc_size window_size = get_window_size(screen);
struct sc_size optimal_size =
get_optimal_size(window_size, screen->content_size, false);
@@ -734,14 +770,11 @@ 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;
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);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
@@ -750,21 +783,33 @@ void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
assert(screen->video);
if (!is_windowed(screen)) {
if (screen->fullscreen || screen->minimized) {
return;
}
if (screen->maximized) {
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct sc_size content_size = screen->content_size;
sc_sdl_set_window_size(screen->window, content_size);
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
}
bool
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
// !video implies !has_video_window
assert(screen->video || !screen->has_video_window);
switch (event->type) {
case SC_EVENT_SCREEN_INIT_SIZE: {
// The initial size is passed via screen->frame_size
bool ok = sc_screen_init_size(screen);
if (!ok) {
LOGE("Could not initialize screen size");
return false;
}
return true;
}
case SC_EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
if (!ok) {
@@ -773,34 +818,45 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
}
return true;
}
case SDL_EVENT_WINDOW_EXPOSED:
if (!screen->video) {
case SDL_WINDOWEVENT:
if (!screen->video
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
sc_screen_render_novideo(screen);
} else if (screen->has_video_window) {
sc_screen_render(screen, true);
}
return true;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
if (screen->has_video_window) {
sc_screen_render(screen, true);
// !video implies !has_frame
assert(screen->video || !screen->has_frame);
if (!screen->has_frame) {
// Do nothing
return true;
}
return true;
case SDL_EVENT_WINDOW_RESTORED:
if (screen->has_video_window && is_windowed(screen)) {
apply_pending_resize(screen);
sc_screen_render(screen, true);
}
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);
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_MINIMIZED:
screen->minimized = true;
break;
case SDL_WINDOWEVENT_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).
break;
}
screen->maximized = false;
screen->minimized = false;
apply_pending_resize(screen);
sc_screen_render(screen, true);
break;
}
return true;
}
@@ -881,15 +937,9 @@ 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) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
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;
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// scale for HiDPI (64 bits for intermediate multiplications)
*x = (int64_t) *x * dw / ww;

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/pixfmt.h>
@@ -61,7 +61,9 @@ struct sc_screen {
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
bool has_video_window;
bool fullscreen;
bool maximized;
bool minimized;
AVFrame *frame;

View File

@@ -6,11 +6,11 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL_keycode.h>
#include <SDL2/SDL_keycode.h>
#include "options.h"
#define SC_SDL_SHORTCUT_MODS_MASK (SDL_KMOD_CTRL | SDL_KMOD_ALT | SDL_KMOD_GUI)
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
// input: OR of enum sc_shortcut_mod
// output: OR of SDL_Keymod
@@ -18,22 +18,22 @@ static inline uint16_t
sc_shortcut_mods_to_sdl(uint8_t shortcut_mods) {
uint16_t sdl_mod = 0;
if (shortcut_mods & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= SDL_KMOD_LCTRL;
sdl_mod |= KMOD_LCTRL;
}
if (shortcut_mods & SC_SHORTCUT_MOD_RCTRL) {
sdl_mod |= SDL_KMOD_RCTRL;
sdl_mod |= KMOD_RCTRL;
}
if (shortcut_mods & SC_SHORTCUT_MOD_LALT) {
sdl_mod |= SDL_KMOD_LALT;
sdl_mod |= KMOD_LALT;
}
if (shortcut_mods & SC_SHORTCUT_MOD_RALT) {
sdl_mod |= SDL_KMOD_RALT;
sdl_mod |= KMOD_RALT;
}
if (shortcut_mods & SC_SHORTCUT_MOD_LSUPER) {
sdl_mod |= SDL_KMOD_LGUI;
sdl_mod |= KMOD_LGUI;
}
if (shortcut_mods & SC_SHORTCUT_MOD_RSUPER) {
sdl_mod |= SDL_KMOD_RGUI;
sdl_mod |= KMOD_RGUI;
}
return sdl_mod;
}
@@ -50,12 +50,12 @@ sc_shortcut_mods_is_shortcut_mod(uint16_t sdl_shortcut_mods, uint16_t sdl_mod) {
static inline bool
sc_shortcut_mods_is_shortcut_key(uint16_t sdl_shortcut_mods,
SDL_Keycode keycode) {
return (sdl_shortcut_mods & SDL_KMOD_LCTRL && keycode == SDLK_LCTRL)
|| (sdl_shortcut_mods & SDL_KMOD_RCTRL && keycode == SDLK_RCTRL)
|| (sdl_shortcut_mods & SDL_KMOD_LALT && keycode == SDLK_LALT)
|| (sdl_shortcut_mods & SDL_KMOD_RALT && keycode == SDLK_RALT)
|| (sdl_shortcut_mods & SDL_KMOD_LGUI && keycode == SDLK_LGUI)
|| (sdl_shortcut_mods & SDL_KMOD_RGUI && keycode == SDLK_RGUI);
return (sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL)
|| (sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL)
|| (sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT)
|| (sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT)
|| (sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI)
|| (sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
}
#endif

View File

@@ -3,7 +3,7 @@
#include <assert.h>
#include <inttypes.h>
#include <string.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL2/SDL_gamecontroller.h>
#include "hid/hid_gamepad.h"
#include "input_events.h"
@@ -74,9 +74,10 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
return;
}
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(event->gamepad_id);
assert(sdl_gamepad);
const char *name = SDL_GetGamepadName(sdl_gamepad);
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(event->gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
sc_gamepad_uhid_send_open(gamepad, &hid_open);

View File

@@ -2,8 +2,8 @@
#include <assert.h>
#include <string.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_keycode.h>
#include <SDL2/SDL_keyboard.h>
#include <SDL2/SDL_keycode.h>
#include "util/log.h"
#include "util/thread.h"

View File

@@ -55,8 +55,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
if (!sc_hid_mouse_generate_input_from_scroll(&mouse->hid, &hid_input,
event)) {
if (!sc_hid_mouse_generate_input_from_scroll(&hid_input, event)) {
return;
}
@@ -66,8 +65,6 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
bool
sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
struct sc_controller *controller) {
sc_hid_mouse_init(&mouse->hid);
mouse->controller = controller;
static const struct sc_mouse_processor_ops ops = {

View File

@@ -4,13 +4,11 @@
#include <stdbool.h>
#include "controller.h"
#include "hid/hid_mouse.h"
#include "trait/mouse_processor.h"
struct sc_mouse_uhid {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_hid_mouse hid;
struct sc_controller *controller;
};

View File

@@ -42,8 +42,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
if (!sc_hid_mouse_generate_input_from_scroll(&mouse->hid, &hid_input,
event)) {
if (!sc_hid_mouse_generate_input_from_scroll(&hid_input, event)) {
return;
}
@@ -65,8 +64,6 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
return false;
}
sc_hid_mouse_init(&mouse->hid);
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,

View File

@@ -6,13 +6,11 @@
#include <stdbool.h>
#include "usb/aoa_hid.h"
#include "hid/hid_mouse.h"
#include "trait/mouse_processor.h"
struct sc_mouse_aoa {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_hid_mouse hid;
struct sc_aoa *aoa;
};

View File

@@ -3,7 +3,7 @@
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#ifdef _WIN32
# include "adb/adb.h"
@@ -45,7 +45,7 @@ event_loop(struct scrcpy_otg *s) {
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SDL_EVENT_QUIT:
case SDL_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
default:
@@ -63,19 +63,23 @@ scrcpy_otg(struct scrcpy_options *options) {
const char *serial = options->serial;
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering");
}
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
LOGW("Could not allow joystick background events");
}
// Minimal SDL initialization
if (!SDL_Init(SDL_INIT_EVENTS)) {
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError());
return SCRCPY_EXIT_FAILURE;
}
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
// Not fatal, keyboard/mouse should still work
}
}

View File

@@ -7,19 +7,14 @@
#include "options.h"
#include "util/acksync.h"
#include "util/log.h"
#include "util/sdl.h"
static void
sc_screen_otg_render(struct sc_screen_otg *screen) {
sc_sdl_render_clear(screen->renderer);
SDL_RenderClear(screen->renderer);
if (screen->texture) {
bool ok =
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
if (!ok) {
LOGW("Could not render texture: %s", SDL_GetError());
}
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
}
sc_sdl_render_present(screen->renderer);
SDL_RenderPresent(screen->renderer);
}
bool
@@ -39,7 +34,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
int width = params->window_width ? params->window_width : 256;
int height = params->window_height ? params->window_height : 256;
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
@@ -47,14 +42,13 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
window_flags |= SDL_WINDOW_BORDERLESS;
}
screen->window =
sc_sdl_create_window(title, x, y, width, height, window_flags);
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError());
return false;
}
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
screen->renderer = SDL_CreateRenderer(screen->window, -1, 0);
if (!screen->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window;
@@ -63,15 +57,9 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
bool ok = SDL_SetWindowIcon(screen->window, icon);
if (!ok) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
SDL_SetWindowIcon(screen->window, icon);
ok = SDL_SetRenderLogicalPresentation(screen->renderer, icon->w,
icon->h,
SDL_LOGICAL_PRESENTATION_LETTERBOX);
if (!ok) {
if (SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) {
LOGW("Could not set renderer logical size: %s", SDL_GetError());
// don't fail
}
@@ -120,10 +108,10 @@ sc_screen_otg_process_key(struct sc_screen_otg *screen,
struct sc_key_event evt = {
.action = sc_action_from_sdl_keyboard_type(event->type),
.keycode = sc_keycode_from_sdl(event->key),
.scancode = sc_scancode_from_sdl(event->scancode),
.keycode = sc_keycode_from_sdl(event->keysym.sym),
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
.repeat = event->repeat,
.mods_state = sc_mods_state_from_sdl(event->mod),
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
};
assert(kp->ops->process_key);
@@ -176,8 +164,15 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
struct sc_mouse_scroll_event evt = {
// .position not used for HID events
#if SDL_VERSION_ATLEAST(2, 0, 18)
.hscroll = event->preciseX,
.vscroll = event->preciseY,
#else
.hscroll = event->x,
.vscroll = event->y,
#endif
.hscroll_int = event->x,
.vscroll_int = event->y,
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
};
@@ -187,34 +182,34 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
static void
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
const SDL_GamepadDeviceEvent *event) {
const SDL_ControllerDeviceEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
SDL_Gamepad *sdl_gamepad = SDL_OpenGamepad(event->which);
if (!sdl_gamepad) {
LOGW("Could not open gamepad");
if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) {
LOGW("Could not open game controller");
return;
}
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
if (!joystick) {
LOGW("Could not get gamepad joystick");
SDL_CloseGamepad(sdl_gamepad);
LOGW("Could not get controller joystick");
SDL_GameControllerClose(gc);
return;
}
struct sc_gamepad_device_event evt = {
.gamepad_id = SDL_GetJoystickID(joystick),
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
gp->ops->process_gamepad_added(gp, &evt);
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
SDL_JoystickID id = event->which;
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
if (sdl_gamepad) {
SDL_CloseGamepad(sdl_gamepad);
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
SDL_GameControllerClose(gc);
} else {
LOGW("Unknown gamepad device removed");
}
@@ -228,7 +223,7 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
static void
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
const SDL_GamepadAxisEvent *event) {
const SDL_ControllerAxisEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
@@ -247,7 +242,7 @@ sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
static void
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
const SDL_GamepadButtonEvent *event) {
const SDL_ControllerButtonEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
@@ -258,7 +253,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_gamepad_button_type(event->type),
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = button,
};
gp->ops->process_gamepad_button(gp, &evt);
@@ -272,55 +267,59 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
}
switch (event->type) {
case SDL_EVENT_WINDOW_EXPOSED:
sc_screen_otg_render(screen);
break;
case SDL_EVENT_KEY_DOWN:
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
sc_screen_otg_render(screen);
break;
}
return;
case SDL_KEYDOWN:
if (screen->keyboard) {
sc_screen_otg_process_key(screen, &event->key);
}
break;
case SDL_EVENT_KEY_UP:
case SDL_KEYUP:
if (screen->keyboard) {
sc_screen_otg_process_key(screen, &event->key);
}
break;
case SDL_EVENT_MOUSE_MOTION:
case SDL_MOUSEMOTION:
if (screen->mouse) {
sc_screen_otg_process_mouse_motion(screen, &event->motion);
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_MOUSEBUTTONDOWN:
if (screen->mouse) {
sc_screen_otg_process_mouse_button(screen, &event->button);
}
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_MOUSEBUTTONUP:
if (screen->mouse) {
sc_screen_otg_process_mouse_button(screen, &event->button);
}
break;
case SDL_EVENT_MOUSE_WHEEL:
case SDL_MOUSEWHEEL:
if (screen->mouse) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
}
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Handle device added or removed even if paused
if (screen->gamepad) {
sc_screen_otg_process_gamepad_device(screen, &event->gdevice);
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
}
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_CONTROLLERAXISMOTION:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_axis(screen, &event->gaxis);
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
}
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_button(screen, &event->gbutton);
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
}
break;
}

View File

@@ -5,7 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL3/SDL.h>
#include <SDL2/SDL.h>
#include "mouse_capture.h"
#include "usb/gamepad_aoa.h"

View File

@@ -50,13 +50,13 @@ log_level_sdl_to_sc(SDL_LogPriority priority) {
void
sc_set_log_level(enum sc_log_level level) {
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
SDL_SetLogPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
SDL_LogSetPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log);
}
enum sc_log_level
sc_get_log_level(void) {
SDL_LogPriority sdl_log = SDL_GetLogPriority(SDL_LOG_CATEGORY_APPLICATION);
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
return log_level_sdl_to_sc(sdl_log);
}
@@ -128,7 +128,7 @@ sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
free(local_fmt);
}
static const char *const sc_sdl_log_priority_names[SDL_LOG_PRIORITY_COUNT] = {
static const char *const sc_sdl_log_priority_names[SDL_NUM_LOG_PRIORITIES] = {
[SDL_LOG_PRIORITY_VERBOSE] = "VERBOSE",
[SDL_LOG_PRIORITY_DEBUG] = "DEBUG",
[SDL_LOG_PRIORITY_INFO] = "INFO",
@@ -144,14 +144,14 @@ sc_sdl_log_print(void *userdata, int category, SDL_LogPriority priority,
(void) category;
FILE *out = priority < SDL_LOG_PRIORITY_WARN ? stdout : stderr;
assert(priority < SDL_LOG_PRIORITY_COUNT);
assert(priority < SDL_NUM_LOG_PRIORITIES);
const char *prio_name = sc_sdl_log_priority_names[priority];
fprintf(out, "%s: %s\n", prio_name, message);
}
void
sc_log_configure(void) {
SDL_SetLogOutputFunction(sc_sdl_log_print, NULL);
SDL_LogSetOutputFunction(sc_sdl_log_print, NULL);
// Redirect FFmpeg logs to SDL logs
av_log_set_callback(sc_av_log_callback);
}

View File

@@ -3,7 +3,7 @@
#include "common.h"
#include <SDL3/SDL_log.h>
#include <SDL2/SDL_log.h>
#include "options.h"

View File

@@ -1,149 +0,0 @@
#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");
}
}

View File

@@ -1,43 +0,0 @@
#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

View File

@@ -4,7 +4,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/log.h"
@@ -31,7 +31,11 @@ static SDL_ThreadPriority
to_sdl_thread_priority(enum sc_thread_priority priority) {
switch (priority) {
case SC_THREAD_PRIORITY_TIME_CRITICAL:
#ifdef SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
return SDL_THREAD_PRIORITY_TIME_CRITICAL;
#else
// fall through
#endif
case SC_THREAD_PRIORITY_HIGH:
return SDL_THREAD_PRIORITY_HIGH;
case SC_THREAD_PRIORITY_NORMAL:
@@ -47,8 +51,8 @@ to_sdl_thread_priority(enum sc_thread_priority priority) {
bool
sc_thread_set_priority(enum sc_thread_priority priority) {
SDL_ThreadPriority sdl_priority = to_sdl_thread_priority(priority);
bool ok = SDL_SetCurrentThreadPriority(sdl_priority);
if (!ok) {
int r = SDL_SetThreadPriority(sdl_priority);
if (r) {
LOGD("Could not set thread priority: %s", SDL_GetError());
return false;
}
@@ -63,7 +67,7 @@ sc_thread_join(sc_thread *thread, int *status) {
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_Mutex *sdl_mutex = SDL_CreateMutex();
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
LOG_OOM();
return false;
@@ -85,25 +89,40 @@ void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
SDL_LockMutex(mutex->mutex);
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGE("Could not lock mutex: %s", SDL_GetError());
abort();
}
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
memory_order_relaxed);
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
assert(sc_mutex_held(mutex));
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
atomic_store_explicit(&mutex->locker, 0, memory_order_relaxed);
#endif
SDL_UnlockMutex(mutex->mutex);
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGE("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_GetCurrentThreadID();
return SDL_ThreadID();
}
#ifndef NDEBUG
@@ -117,7 +136,7 @@ sc_mutex_held(struct sc_mutex *mutex) {
bool
sc_cond_init(sc_cond *cond) {
SDL_Condition *sdl_cond = SDL_CreateCondition();
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
LOG_OOM();
return false;
@@ -129,15 +148,22 @@ sc_cond_init(sc_cond *cond) {
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCondition(cond->cond);
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
SDL_WaitCondition(cond->cond, mutex->mutex);
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGE("Could not wait on condition: %s", SDL_GetError());
abort();
}
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
memory_order_relaxed);
#else
(void) r;
#endif
}
@@ -151,22 +177,44 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
// Round up to the next millisecond to guarantee that the deadline is
// reached when returning due to timeout
uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1);
bool signaled = SDL_WaitConditionTimeout(cond->cond, mutex->mutex, ms);
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
memory_order_relaxed);
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
// The deadline is reached on timeout
assert(signaled || sc_tick_now() >= deadline);
return signaled;
assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
SDL_SignalCondition(cond->cond);
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGE("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
SDL_BroadcastCondition(cond->cond);
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGE("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

View File

@@ -10,8 +10,8 @@
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_Mutex SDL_Mutex;
typedef struct SDL_Condition SDL_Condition;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned sc_thread_id;
@@ -29,14 +29,14 @@ enum sc_thread_priority {
};
typedef struct sc_mutex {
SDL_Mutex *mutex;
SDL_mutex *mutex;
#ifndef NDEBUG
sc_atomic_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_Condition *cond;
SDL_cond *cond;
} sc_cond;
extern sc_thread_id SC_MAIN_THREAD_ID;

View File

@@ -10,20 +10,17 @@
#ifdef HAVE_USB
# include <libusb-1.0/libusb.h>
#endif
#include <SDL3/SDL_version.h>
#include <SDL2/SDL_version.h>
void
scrcpy_print_version(void) {
printf("\nDependencies (compiled / linked):\n");
int sdl = SDL_GetVersion();
SDL_version sdl;
SDL_GetVersion(&sdl);
printf(" - SDL: %u.%u.%u / %u.%u.%u\n",
SDL_MAJOR_VERSION,
SDL_MINOR_VERSION,
SDL_MICRO_VERSION,
SDL_VERSIONNUM_MAJOR(sdl),
SDL_VERSIONNUM_MINOR(sdl),
SDL_VERSIONNUM_MICRO(sdl));
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
(unsigned) sdl.major, (unsigned) sdl.minor, (unsigned) sdl.patch);
unsigned avcodec = avcodec_version();
printf(" - libavcodec: %u.%u.%u / %u.%u.%u\n",

View File

@@ -411,26 +411,6 @@ 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,
@@ -468,7 +448,6 @@ 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;
}

View File

@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.android.tools.build:gradle:8.7.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,73 +0,0 @@
# Build with:
# podman build -t scrcpy-builder .
#
# Run with:
# podman run \
# -v PATH_TO_SCRCPY:/home/debian/scrcpy \
# --userns=keep-id \
# -it scrcpy-builder \
# bash
FROM debian:trixie
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
sudo wget unzip gcc git pkg-config meson ninja-build cmake \
libsdl3-dev libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libswresample-dev libusb-1.0-0-dev openjdk-21-jdk \
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
RUN groupadd -g 1000 debian \
&& useradd -m -u 1000 -g 1000 -s /bin/bash debian
RUN echo "debian ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
ENV ANDROID_HOME=/opt/android/sdk
RUN mkdir -p "$ANDROID_HOME" \
&& chown debian:debian "$ANDROID_HOME"
USER debian
WORKDIR /home/debian
ENV CMDLINETOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip
ENV CMDLINETOOLS_SHA256=7ec965280a073311c339e571cd5de778b9975026cfcbe79f2b1cdcb1e15317ee
RUN wget -q "$CMDLINETOOLS_URL" -O cmdlinetools.zip \
&& echo "$CMDLINETOOLS_SHA256 cmdlinetools.zip" | sha256sum -c
RUN mkdir tmp \
&& unzip -q cmdlinetools.zip -d tmp \
&& mkdir -p "$ANDROID_HOME/cmdline-tools" \
&& mv tmp/cmdline-tools "$ANDROID_HOME/cmdline-tools/latest" \
&& rmdir tmp \
&& rm cmdlinetools.zip
# To get the licence hash, run manually: "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses
# Build-tools 35 is still needed for the current AGP version
RUN mkdir -p "$ANDROID_HOME/licenses" \
&& echo 24333f8a63b6825ea9c5514f83c2829b004d1fee > "$ANDROID_HOME/licenses/android-sdk-license" \
&& $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-36" "build-tools;35.0.0" "build-tools;36.0.0"
# For scrcpy build scripts
ENV GRADLE_VERSION=8.14.3
ENV GRADLE_HOME=/opt/gradle-$GRADLE_VERSION
ENV GRADLE_URL=https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip
ENV GRADLE_SHA256=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
ENV GRADLE=gradle
ENV PATH=$GRADLE_HOME/bin:$PATH
USER root
RUN wget -q "$GRADLE_URL" -O gradle.zip \
&& echo "$GRADLE_SHA256 gradle.zip" | sha256sum -c
RUN unzip -q gradle.zip -d /opt \
&& rm gradle.zip
USER debian
# Pre-download gradle dependencies for scrcpy
RUN mkdir -p /home/debian/fake-scrcpy/app
COPY fake.gradle /home/debian/fake-scrcpy/build.gradle
COPY fake_app.gradle /home/debian/fake-scrcpy/app/build.gradle
RUN echo "include ':app'" > /home/debian/fake-scrcpy/settings.gradle \
&& gradle -p /home/debian/fake-scrcpy dependencies androidDependencies --no-daemon \
&& rm -rf /home/debian/fake-scrcpy

View File

@@ -1,17 +0,0 @@
// Fake build.gradle to pre-download gradle dependencies
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}

View File

@@ -1,21 +0,0 @@
// Fake app/build.gradle to pre-download gradle dependencies
apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
android {
buildToolsVersion = "36.0.0"
namespace = "com.genymobile.scrcpy"
compileSdk 36
defaultConfig {
minSdkVersion 21
targetSdkVersion 36
}
}
checkstyle {
toolVersion = '10.12.5'
}
dependencies {
testImplementation 'junit:junit:4.13.2'
}

View File

@@ -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 [SDL]. Just follow the instructions.
The client requires [FFmpeg] and [LibSDL2]. 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
[SDL]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
[LibSDL2]: 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 libsdl3-0 adb libusb-1.0-0
sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl3-dev \
sudo apt install gcc git pkg-config meson ninja-build libsdl2-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 SDL3-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
sudo dnf install SDL2-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-sdl3 \
pacman -S mingw-w64-x86_64-SDL2 \
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-sdl3 \
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg \
mingw-w64-i686-libusb
@@ -162,7 +162,7 @@ Install the packages with [Homebrew]:
```bash
# runtime dependencies
brew install sdl3 ffmpeg libusb
brew install sdl2 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.4`][direct-scrcpy-server]
<sub>SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e`</sub>
- [`scrcpy-server-v3.3.1`][direct-scrcpy-server]
<sub>SHA-256: `a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
@@ -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/scrcpy.png` (app icon)
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)

View File

@@ -6,11 +6,11 @@
Download a static build of the [latest release]:
- [`scrcpy-linux-x86_64-v3.3.4.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `0305d98c06178c67e12427bbf340c436d0d58c9e2a39bf9ffbbf8f54d7ef95a5`</sub>
- [`scrcpy-linux-x86_64-v3.3.1.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `bbfe54c6b178adafeaffbbfbbc1548a74486553170c63e63bdd41863ad123422`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-linux-x86_64-v3.3.4.tar.gz
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-linux-x86_64-v3.3.1.tar.gz
and extract it.
@@ -39,8 +39,8 @@ First, you need to install the required packages:
```bash
# for Debian/Ubuntu
sudo apt install ffmpeg libsdl3-0 adb wget \
gcc git pkg-config meson ninja-build libsdl3-dev \
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
```

View File

@@ -6,14 +6,15 @@
Download a static build of the [latest release]:
- [`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>
- [`scrcpy-macos-aarch64-v3.3.1.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `907b925900ebd8499c1e47acc9689a95bd3a6f9930eb1d7bdfbca8375ae4f139`</sub>
- [`scrcpy-macos-x86_64-v3.3.1.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `69772491dad718eea82fc65c8e89febff7d41c4ce6faff02f4789a588d10fd7d`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[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
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-aarch64-v3.3.1.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-macos-x86_64-v3.3.1.tar.gz
and extract it.

View File

@@ -6,14 +6,14 @@
Download the [latest release]:
- [`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>
- [`scrcpy-win64-v3.3.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `4fcad494772a3ae5de9a133149f8856d2fc429b41795f7cf7c754e0c1bb6fbc0`</sub>
- [`scrcpy-win32-v3.3.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ccdf1b4f5d19dfe760446a107e55b0a010a00e097d46533a161499c9333a20a6`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[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
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win64-v3.3.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win32-v3.3.1.zip
and extract it.

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
# https://gradle.org/release-checksums/
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4
PREBUILT_SERVER_SHA256=8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-server-v3.3.1
PREBUILT_SERVER_SHA256=a0f70b20aa4998fbf658c94118cd6c8dab6abbb0647a3bdab344d70bc1ebcbb8
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '3.3.4',
version: '3.3.2',
meson_version: '>= 0.49',
default_options: [
'c_std=c11',

View File

@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
namespace = 'com.genymobile.scrcpy'
compileSdk 36
namespace 'com.genymobile.scrcpy'
compileSdk 35
defaultConfig {
applicationId = "com.genymobile.scrcpy"
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 36
versionCode 30304
versionName "3.3.4"
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
targetSdkVersion 35
versionCode 30302
versionName "3.3.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -18,11 +18,8 @@ android {
}
}
buildFeatures {
buildConfig = true
aidl = true
}
lint {
disable 'UseRequiresApi'
buildConfig true
aidl true
}
}

View File

@@ -12,10 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.3.4
SCRCPY_VERSION_NAME=3.3.2
PLATFORM=${ANDROID_PLATFORM:-36}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0}
PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.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" \

View File

@@ -48,4 +48,27 @@ 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<Rect> restricted, in List<Rect> 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);
}

View File

@@ -196,7 +196,6 @@ public final class CleanUp {
// Needed for workarounds
prepareMainLooper();
Workarounds.apply();
int displayId = Integer.parseInt(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);

View File

@@ -109,10 +109,8 @@ public final class FakeContext extends ContextWrapper {
}
// "semclipboard" is a Samsung-internal service
// 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)) {
// See <https://github.com/Genymobile/scrcpy/issues/6224>
if (Context.CLIPBOARD_SERVICE.equals(name) || "semclipboard".equals(name)) {
try {
Field field = service.getClass().getDeclaredField("mContext");
field.setAccessible(true);

View File

@@ -414,7 +414,6 @@ public class Options {
if (!value.isEmpty()) {
options.audioEncoder = value;
}
break;
case "power_off_on_close":
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
break;

View File

@@ -27,7 +27,6 @@ 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;
@@ -226,16 +225,10 @@ 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);
@@ -276,17 +269,4 @@ 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);
}
}
}

View File

@@ -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,7 +103,10 @@ public final class Workarounds {
private static void fillAppContext() {
try {
Application app = Instrumentation.newApplication(Application.class, FakeContext.get());
Application app = new Application();
Field baseField = ContextWrapper.class.getDeclaredField("mBase");
baseField.setAccessible(true);
baseField.set(app, FakeContext.get());
// activityThread.mInitialApplication = app;
Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication");

View File

@@ -1,7 +1,6 @@
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;
@@ -16,7 +15,6 @@ 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 {
@@ -82,17 +80,31 @@ 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 {
Threads.executeSynchronouslyOn(handler, new Callable<Void>() {
@Override
public Void call() throws Exception {
run(inputSize, outputSize, outputSurface);
return null;
}
});
} catch (Throwable throwable) {
sem.acquire();
} catch (InterruptedException e) {
// Behave as if this method call was synchronous
Thread.currentThread().interrupt();
}
Throwable throwable = throwableRef[0];
if (throwable != null) {
if (throwable instanceof OpenGLException) {
throw (OpenGLException) throwable;
}

View File

@@ -74,11 +74,9 @@ public final class Ln {
public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) {
Log.w(TAG, message, throwable);
synchronized (CONSOLE_ERR) {
CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
}
}
@@ -90,11 +88,9 @@ public final class Ln {
public static void e(String message, Throwable throwable) {
if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable);
synchronized (CONSOLE_ERR) {
CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n');
if (throwable != null) {
throwable.printStackTrace(CONSOLE_ERR);
}
}
}

View File

@@ -1,43 +0,0 @@
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];
}
}

View File

@@ -25,7 +25,6 @@ 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;
@@ -170,7 +169,6 @@ 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;

View File

@@ -139,13 +139,7 @@ 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;
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;
}
String uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo);
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);

View File

@@ -1,12 +1,11 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.util.Ln;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.RemoteException;
import android.graphics.Rect;
import android.view.IDisplayWindowListener;
import java.util.List;
public class DisplayWindowListener extends IDisplayWindowListener.Stub {
@Override
public void onDisplayAdded(int displayId) {
@@ -24,14 +23,32 @@ public class DisplayWindowListener extends IDisplayWindowListener.Stub {
}
@Override
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;
}
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<Rect> restricted, List<Rect> 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
}
}