Compare commits

..

17 Commits
dev4 ... noroot

Author SHA1 Message Date
Romain Vimont
81b90bf58b Drop root privileges on startup
On rooted devices, switch to user 2000 during startup.

Copy-paste does not work as root (user 0), so switching to 2000 fixes
the issue.

Fixes #6224 <https://github.com/Genymobile/scrcpy/issues/6224>

Suggested-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2025-12-29 18:30:50 +01:00
Romain Vimont
db2f72a58a Merge branch 'master' into dev 2025-12-17 20:27:11 +01:00
Romain Vimont
fb6381f5b9 Upgrade links to 3.3.4 2025-12-17 20:21:05 +01:00
Romain Vimont
06fd3b4786 Bump version to 3.3.4 2025-12-17 19:57:50 +01:00
Romain Vimont
1d1a4103cc Replace macos-13 runner with macos-15-intel
Refs <https://github.com/actions/runner-images/issues/13046>
Refs #5526 <https://github.com/Genymobile/scrcpy/pull/5526>
2025-12-17 19:57:50 +01:00
Romain Vimont
5b51396a8c Fix permission denial error after Android upgrade
Assign the FakeContext instance to ActivityManager.mContext to avoid a
permission error:

    Permission Denial: package=android does not belong to uid=2000

Fixes #6523 <https://github.com/Genymobile/scrcpy/issues/6523>
2025-12-09 20:24:42 +01:00
Romain Vimont
7e66062086 Extract function to execute code on a handler
Extract function to synchronously execute code on a handler, waiting for
the execution to complete.
2025-11-28 22:42:17 +01:00
Romain Vimont
6f9eb31d52 Add missing test for START_APP serialization
A test for Java deserialization of the START_APP control message was
already present, but the corresponding C-side serialization test was
missing.

Refs 13ce277e1f
2025-11-25 00:13:13 +01:00
Romain Vimont
9cfa5b197a Create Application instance via instrumentation
This fixes an issue on certain Meizu devices.

Fixes #6480 <https://github.com/Genymobile/scrcpy/issues/6480>
2025-11-23 15:56:31 +01:00
paradoxskin
b08093d1c0 Fix incorrect icon filename in build documentation
The installed icon was listed as `icon.png`, but the actual
filename is `scrcpy.png`.

PR #6490 <https://github.com/Genymobile/scrcpy/pull/6490>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2025-11-19 17:13:40 +01:00
valord577
7dd9bcaf60 Prevent error log interleaving
Between the calls to CONSOLE_ERR.print() and
printStackTrace(CONSOLE_ERR), logs from other threads may be inserted.

Synchronizing access to CONSOLE_ERR ensures that logs from different
threads do not mix.

PR #6487 <https://github.com/Genymobile/scrcpy/pull/6487>

Signed-off-by: valord577 <valord577@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2025-11-18 11:17:17 +01:00
Romain Vimont
3530851071 Fix uncaught exception handler
The default handler was mistakenly retrieved after our custom handler
was set, causing it to reference itself. As a result, this led to
infinite recursion.

Bug introduced by eee3f24739.
2025-11-04 20:35:39 +01:00
Romain Vimont
d0047b2110 Do not fail when uniqueId field is missing
On some devices, DisplayInfo does not have a "uniqueId" field. This
field is only used for correct UHID behavior on virtual displays, so its
absence should not prevent scrcpy from working.

Refs #6009 <https://github.com/Genymobile/scrcpy/pull/6009>
Fixes #6461 <https://github.com/Genymobile/scrcpy/issues/6461>
2025-11-01 00:34:20 +01:00
Romain Vimont
3281fda6ef Set URL explicitly in dependency build scripts
Explicitly set the URL of each dependency at the beginning of its
script.

PROJECT_DIR and FILENAME are internal details.
2025-10-30 22:31:16 +01:00
Romain Vimont
925949d54a Refactor dependency build scripts initialization
Rename "common" to "_init" because it not only exposes common functions
but also initializes environment variables.

Call _init in a single line in all dependency build scripts.
2025-10-30 22:27:17 +01:00
Yan
f3d4fde15b Fix handling of non-integer ANDROID_PLATFORM
ANDROID_PLATFORM is not always an integer; it can also be a value like
"36.1". Handle such cases properly.

This fixes the following error:

    server/build_without_gradle.sh: line 89:
    [[: 36.1: syntax error: invalid arithmetic operator (error token is ".1")

PR #6408 <https://github.com/Genymobile/scrcpy/pull/6408>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2025-10-19 21:00:05 +02:00
Romain Vimont
eee3f24739 Upgrade Gradle and use Android SDK 36 2025-10-19 21:00:05 +02:00
87 changed files with 1084 additions and 1426 deletions

View File

@@ -80,16 +80,8 @@ jobs:
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
libv4l-dev
# SDL3 is not available in Ubuntu yet
- name: Install SDL3
run: |
app/deps/sdl.sh linux native shared
- 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
@@ -237,7 +229,7 @@ jobs:
path: release/work/build-macos-aarch64/dist-tar/
build-macos-x86_64:
runs-on: macos-13
runs-on: macos-15-intel
steps:
- name: Check architecture
run: |

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.3)
# scrcpy (v3.3.4)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

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

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
VERSION=36.0.0
FILENAME=platform-tools_r$VERSION-linux.zip
PROJECT_DIR=platform-tools-$VERSION-linux
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-linux.zip"
SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8
PROJECT_DIR="platform-tools-$VERSION-linux"
FILENAME="$PROJECT_DIR.zip"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
VERSION=36.0.0
FILENAME=platform-tools_r$VERSION-darwin.zip
PROJECT_DIR=platform-tools-$VERSION-darwin
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-darwin.zip"
SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd
PROJECT_DIR="platform-tools-$VERSION-darwin"
FILENAME="$PROJECT_DIR.zip"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
VERSION=36.0.0
FILENAME=platform-tools_r$VERSION-win.zip
PROJECT_DIR=platform-tools-$VERSION-windows
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-win.zip"
SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c
PROJECT_DIR="platform-tools-$VERSION-windows"
FILENAME="$PROJECT_DIR.zip"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init
process_args "$@"
VERSION=1.5.0
FILENAME=dav1d-$VERSION.tar.gz
PROJECT_DIR=dav1d-$VERSION
URL="https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/dav1d-$VERSION.tar.gz"
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
PROJECT_DIR="dav1d-$VERSION"
FILENAME="$PROJECT_DIR.tar.gz"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init
process_args "$@"
VERSION=7.1.1
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
URL="https://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz"
SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1
PROJECT_DIR="ffmpeg-$VERSION"
FILENAME="$PROJECT_DIR.tar.xz"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,22 +1,22 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init
process_args "$@"
VERSION=1.0.29
FILENAME=libusb-$VERSION.tar.gz
PROJECT_DIR=libusb-$VERSION
URL="https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz"
SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74
PROJECT_DIR="libusb-$VERSION"
FILENAME="$PROJECT_DIR.tar.gz"
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
get_file "$URL" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
. $(dirname ${BASH_SOURCE[0]})/_init
process_args "$@"
VERSION=3.2.18
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=51539fa13e546bc50c632beed3f34257de2baa38a4c642048de56377903b4265
VERSION=2.32.8
URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz"
SHA256SUM=dd35e05644ae527848d02433bec24dd0ea65db59faecf1a0e5d1880c533dac2c
PROJECT_DIR="sdl-$VERSION"
FILENAME="$PROJECT_DIR.tar.gz"
cd "$SOURCES_DIR"
@@ -16,8 +16,9 @@ if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
get_file "$URL" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "SDL-release-$VERSION"
mv "SDL-release-$VERSION" "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
@@ -35,48 +36,45 @@ else
cd "$DIRNAME"
conf=(
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
--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

@@ -63,7 +63,6 @@ src = [
'src/util/thread.c',
'src/util/tick.c',
'src/util/timeout.c',
'src/util/window.c',
]
conf = configuration_data()
@@ -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

View File

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

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
@@ -47,10 +30,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
static bool
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
const AVCodecContext *ctx) {
struct sc_audio_player *ap = DOWNCAST(sink);
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
@@ -81,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);
@@ -128,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;
}
@@ -144,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

@@ -10,30 +10,20 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
static bool
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOG_OOM();
return false;
}
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx, session)) {
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
av_frame_free(&decoder->frame);
return false;
}
decoder->ctx = ctx;
// A video stream must have a session
assert(session || ctx->codec_type != AVMEDIA_TYPE_VIDEO);
if (session) {
decoder->session = *session;
}
memset(&decoder->frame_size, 0, sizeof(decoder->frame_size));
return true;
}
@@ -71,32 +61,6 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
}
// a frame was received
if (decoder->ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
assert(decoder->frame->width >= 0);
assert(decoder->frame->height >= 0);
struct sc_size frame_size = {
.width = decoder->frame->width,
.height = decoder->frame->height,
};
if (decoder->frame_size.width != frame_size.width
|| decoder->frame_size.height != frame_size.height) {
// The frame size has changed, check if it matches the session
uint32_t sw = decoder->session.video.width;
uint32_t sh = decoder->session.video.height;
if (frame_size.width != sw || frame_size.height != sh) {
LOGW("Unexpected video size: %" PRIu32 "x%" PRIu32
" (expected %" PRIu32 "x%" PRIu32 ")",
frame_size.width, frame_size.height, sw, sh);
LOGW("The encoder did not respect the requested size, "
"please retry with a lower resolution (-m/--max-size)");
}
}
decoder->frame_size = frame_size;
}
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
decoder->frame);
av_frame_unref(decoder->frame);
@@ -110,17 +74,9 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
}
static bool
sc_decoder_push_session(struct sc_decoder *decoder,
const struct sc_stream_session *session) {
decoder->session = *session;
return sc_frame_source_sinks_push_session(&decoder->frame_source, session);
}
static bool
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
struct sc_decoder *decoder = DOWNCAST(sink);
return sc_decoder_open(decoder, ctx, session);
return sc_decoder_open(decoder, ctx);
}
static void
@@ -136,14 +92,6 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
return sc_decoder_push(decoder, packet);
}
static bool
sc_decoder_packet_sink_push_session(struct sc_packet_sink *sink,
const struct sc_stream_session *session) {
struct sc_decoder *decoder = DOWNCAST(sink);
return sc_decoder_push_session(decoder, session);
}
void
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
decoder->name = name; // statically allocated
@@ -153,7 +101,6 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
.open = sc_decoder_packet_sink_open,
.close = sc_decoder_packet_sink_close,
.push = sc_decoder_packet_sink_push,
.push_session = sc_decoder_packet_sink_push_session,
};
decoder->packet_sink.ops = &ops;

View File

@@ -5,7 +5,6 @@
#include <libavcodec/avcodec.h>
#include "coords.h"
#include "trait/frame_source.h"
#include "trait/packet_sink.h"
@@ -17,9 +16,6 @@ struct sc_decoder {
AVCodecContext *ctx;
AVFrame *frame;
struct sc_stream_session session; // only initialized for video stream
struct sc_size frame_size;
};
// The name must be statically allocated (e.g. a string literal)

View File

@@ -10,18 +10,16 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
static bool
sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
const AVFrame *frame) {
dpacket->type = SC_DELAYED_PACKET_TYPE_FRAME;
dpacket->frame = av_frame_alloc();
if (!dpacket->frame) {
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
dframe->frame = av_frame_alloc();
if (!dframe->frame) {
LOG_OOM();
return false;
}
if (av_frame_ref(dpacket->frame, frame)) {
if (av_frame_ref(dframe->frame, frame)) {
LOG_OOM();
av_frame_free(&dpacket->frame);
av_frame_free(&dframe->frame);
return false;
}
@@ -29,18 +27,9 @@ sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
}
static void
sc_delayed_packet_init_session(struct sc_delayed_packet *dpacket,
const struct sc_stream_session *session) {
dpacket->type = SC_DELAYED_PACKET_TYPE_SESSION;
dpacket->session = *session;
}
static void
sc_delayed_packet_destroy(struct sc_delayed_packet *dpacket) {
if (dpacket->type == SC_DELAYED_PACKET_TYPE_FRAME) {
av_frame_unref(dpacket->frame);
av_frame_free(&dpacket->frame);
}
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
av_frame_unref(dframe->frame);
av_frame_free(&dframe->frame);
}
static int
@@ -61,52 +50,43 @@ run_buffering(void *data) {
goto stopped;
}
struct sc_delayed_packet dpacket = sc_vecdeque_pop(&db->queue);
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
bool ok;
if (dpacket.type == SC_DELAYED_PACKET_TYPE_FRAME) {
sc_tick max_deadline = sc_tick_now() + db->delay;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_FROM_US(dpacket.frame->pts);
sc_tick max_deadline = sc_tick_now() + db->delay;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
bool timed_out = false;
while (!db->stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
+ db->delay;
if (deadline > max_deadline) {
deadline = max_deadline;
}
timed_out =
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
bool timed_out = false;
while (!db->stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
+ db->delay;
if (deadline > max_deadline) {
deadline = max_deadline;
}
bool stopped = db->stopped;
sc_mutex_unlock(&db->mutex);
if (stopped) {
sc_delayed_packet_destroy(&dpacket);
goto stopped;
}
#ifdef SC_BUFFERING_DEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
ok = sc_frame_source_sinks_push(&db->frame_source, dpacket.frame);
} else {
assert(dpacket.type == SC_DELAYED_PACKET_TYPE_SESSION);
sc_mutex_unlock(&db->mutex);
ok = sc_frame_source_sinks_push_session(&db->frame_source,
&dpacket.session);
timed_out =
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
}
sc_delayed_packet_destroy(&dpacket);
bool stopped = db->stopped;
sc_mutex_unlock(&db->mutex);
if (stopped) {
sc_delayed_frame_destroy(&dframe);
goto stopped;
}
#ifdef SC_BUFFERING_DEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
sc_delayed_frame_destroy(&dframe);
if (!ok) {
LOGE("Delayed packet could not be pushed, stopping");
LOGE("Delayed frame could not be pushed, stopping");
sc_mutex_lock(&db->mutex);
// Prevent to push any new packet
// Prevent to push any new frame
db->stopped = true;
sc_mutex_unlock(&db->mutex);
goto stopped;
@@ -118,8 +98,8 @@ stopped:
// Flush queue
while (!sc_vecdeque_is_empty(&db->queue)) {
struct sc_delayed_packet *dpacket = sc_vecdeque_popref(&db->queue);
sc_delayed_packet_destroy(dpacket);
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
sc_delayed_frame_destroy(dframe);
}
LOGD("Buffering thread ended");
@@ -129,11 +109,9 @@ stopped:
static bool
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
struct sc_delay_buffer *db = DOWNCAST(sink);
(void) ctx;
(void) session;
bool ok = sc_mutex_init(&db->mutex);
if (!ok) {
@@ -154,7 +132,7 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
sc_vecdeque_init(&db->queue);
db->stopped = false;
if (!sc_frame_source_sinks_open(&db->frame_source, ctx, session)) {
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
goto error_destroy_wait_cond;
}
@@ -218,56 +196,24 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
return sc_frame_source_sinks_push(&db->frame_source, frame);
}
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
if (!dpacket) {
struct sc_delayed_frame dframe;
bool ok = sc_delayed_frame_init(&dframe, frame);
if (!ok) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
bool ok = sc_delayed_packet_init_frame(dpacket, frame);
#ifdef SC_BUFFERING_DEBUG
dframe.push_date = sc_tick_now();
#endif
ok = sc_vecdeque_push(&db->queue, dframe);
if (!ok) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
#ifdef SC_BUFFERING_DEBUG
dpacket->push_date = sc_tick_now();
#endif
sc_cond_signal(&db->queue_cond);
sc_mutex_unlock(&db->mutex);
return true;
}
static bool
sc_delay_buffer_frame_sink_push_session(struct sc_frame_sink *sink,
const struct sc_stream_session *session) {
struct sc_delay_buffer *db = DOWNCAST(sink);
sc_mutex_lock(&db->mutex);
if (db->stopped) {
sc_mutex_unlock(&db->mutex);
return false;
}
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
if (!dpacket) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
sc_delayed_packet_init_session(dpacket, session);
#ifdef SC_BUFFERING_DEBUG
dpacket->push_date = sc_tick_now();
#endif
sc_cond_signal(&db->queue_cond);
sc_mutex_unlock(&db->mutex);
@@ -289,7 +235,6 @@ sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
.open = sc_delay_buffer_frame_sink_open,
.close = sc_delay_buffer_frame_sink_close,
.push = sc_delay_buffer_frame_sink_push,
.push_session = sc_delay_buffer_frame_sink_push_session,
};
db->frame_sink.ops = &ops;

View File

@@ -18,23 +18,14 @@
// forward declarations
typedef struct AVFrame AVFrame;
enum sc_delayed_packet_type {
SC_DELAYED_PACKET_TYPE_FRAME,
SC_DELAYED_PACKET_TYPE_SESSION,
};
struct sc_delayed_packet {
enum sc_delayed_packet_type type;
union {
AVFrame *frame;
struct sc_stream_session session;
};
struct sc_delayed_frame {
AVFrame *frame;
#ifdef SC_BUFFERING_DEBUG
sc_tick push_date;
#endif
};
struct sc_delayed_packet_queue SC_VECDEQUE(struct sc_delayed_packet);
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
struct sc_delay_buffer {
struct sc_frame_source frame_source; // frame source trait
@@ -49,7 +40,7 @@ struct sc_delay_buffer {
sc_cond wait_cond;
struct sc_clock clock;
struct sc_delayed_packet_queue queue;
struct sc_delayed_frame_queue queue;
bool stopped;
};

View File

@@ -11,8 +11,8 @@
#define SC_PACKET_HEADER_SIZE 12
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 62)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 61)
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
@@ -63,75 +63,48 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
return true;
}
static inline bool
sc_demuxer_recv_header(struct sc_demuxer *demuxer,
uint8_t buf[static SC_PACKET_HEADER_SIZE]) {
static bool
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
uint32_t *height) {
uint8_t data[8];
ssize_t r = net_recv_all(demuxer->socket, data, 8);
if (r < 8) {
return false;
}
*width = sc_read32be(data);
*height = sc_read32be(data + 4);
return true;
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// The video and audio streams contain a sequence of raw packets (as
// provided by MediaCodec), each prefixed with a "meta" header.
//
// The "meta" header length is 12 bytes.
//
//
// If the MSB is 1, then it is a session packet (for a video stream only),
// which only contains a 12-byte header:
//
// byte 0 byte 1 byte 2 byte 3
// 10000000 00000000 00000000 00000000
// ^<-------------------------------->
// | padding
// `- session packet flag
//
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
// ........ ........ ........ ........ ........ ........ ........ ........
// <---------------------------------> <--------------------------------->
// video width video height
//
//
// If the MSB is 0, then it is a media packet, comprised of a 12-byte header
// followed by <packet_size> bytes containing the packet/frame:
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
//
// The most significant bits of the PTS are used for packet flags:
//
// byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
// 0CK..... ........ ........ ........ ........ ........ ........ ........
// ^^^<------------------------------------------------------------------>
// ||| PTS
// || `- key frame
// | `-- config packet
// `--- media packet flag
//
// byte 8 byte 9 byte 10 byte 11
// ........ ........ ........ ........ ........ ........ . . .
// <---------------------------------> <---------------- . . .
// packet size raw packet
//
ssize_t r = net_recv_all(demuxer->socket, buf, SC_PACKET_HEADER_SIZE);
assert(r <= SC_PACKET_HEADER_SIZE);
return r == SC_PACKET_HEADER_SIZE;
}
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
// CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<------------------------------------------------------------------->
// || PTS
// | `- key frame
// `-- config packet
static bool
sc_demuxer_is_session(const uint8_t *header) {
return header[0] & 0x80;
}
uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
if (r < SC_PACKET_HEADER_SIZE) {
return false;
}
static void
sc_demuxer_parse_session(const uint8_t *header,
struct sc_stream_session *session) {
assert(sc_demuxer_is_session(header));
session->video.width = sc_read32be(&header[4]);
session->video.height = sc_read32be(&header[8]);
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
AVPacket *packet) {
assert(!sc_demuxer_is_session(header));
uint64_t pts_flags = sc_read64be(header);
uint32_t len = sc_read32be(&header[8]);
assert(len);
@@ -141,7 +114,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
return false;
}
ssize_t r = net_recv_all(demuxer->socket, packet->data, len);
r = net_recv_all(demuxer->socket, packet->data, len);
if (r < 0 || ((uint32_t) r) < len) {
av_packet_unref(packet);
return false;
@@ -214,28 +187,17 @@ run_demuxer(void *data) {
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
uint8_t header[SC_PACKET_HEADER_SIZE];
struct sc_stream_session session_data;
struct sc_stream_session *session = NULL;
if (codec->type == AVMEDIA_TYPE_VIDEO) {
bool ok = sc_demuxer_recv_header(demuxer, header);
uint32_t width;
uint32_t height;
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
if (!ok) {
goto finally_free_context;
}
if (!sc_demuxer_is_session(header)) {
LOGE("Unexpected packet (not a session header)");
goto finally_free_context;
}
session = &session_data;
sc_demuxer_parse_session(header, session);
codec_ctx->width = session_data.video.width;
codec_ctx->height = session_data.video.height;
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
} else {
// Hardcoded audio properties
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
@@ -257,8 +219,7 @@ run_demuxer(void *data) {
goto finally_free_context;
}
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx,
session)) {
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
goto finally_free_context;
}
@@ -280,39 +241,27 @@ run_demuxer(void *data) {
}
for (;;) {
bool ok = sc_demuxer_recv_header(demuxer, header);
bool ok = sc_demuxer_recv_packet(demuxer, packet);
if (!ok) {
// end of stream
status = SC_DEMUXER_STATUS_EOS;
break;
}
if (sc_demuxer_is_session(header)) {
sc_demuxer_parse_session(header, &session_data);
ok = sc_packet_source_sinks_push_session(&demuxer->packet_source,
&session_data);
if (must_merge_config_packet) {
// Prepend any config packet to the next media packet
ok = sc_packet_merger_merge(&merger, packet);
if (!ok) {
// The sink already logged its concrete error
av_packet_unref(packet);
break;
}
} else {
sc_demuxer_recv_packet(demuxer, header, packet);
}
if (must_merge_config_packet) {
// Prepend any config packet to the next media packet
ok = sc_packet_merger_merge(&merger, packet);
if (!ok) {
av_packet_unref(packet);
break;
}
}
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
av_packet_unref(packet);
if (!ok) {
// The sink already logged its concrete error
break;
}
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
av_packet_unref(packet);
if (!ok) {
// The sink already logged its concrete error
break;
}
}

View File

@@ -12,11 +12,8 @@ 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
}
@@ -34,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;
@@ -56,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);
@@ -97,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;
@@ -119,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);
@@ -127,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;
@@ -192,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
@@ -266,18 +189,15 @@ sc_display_update_texture_internal(struct sc_display *display,
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) {
@@ -296,18 +216,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;
}
@@ -317,13 +234,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;
}
@@ -331,25 +245,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);
SDL_GL_BindTexture(display->texture, NULL, NULL);
display->gl.GenerateMipmap(GL_TEXTURE_2D);
gl->BindTexture(GL_TEXTURE_2D, 0);
SDL_GL_UnbindTexture(display->texture);
}
return true;
@@ -374,9 +301,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation) {
bool always_ok = SDL_RenderClear(display->renderer);
(void) always_ok;
assert(always_ok);
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
bool ok = sc_display_apply_pending(display);
@@ -389,10 +314,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;
}
@@ -400,29 +323,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;
}
}
always_ok = SDL_RenderPresent(display->renderer);
assert(always_ok);
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

@@ -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 get surface properties: %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"
@@ -43,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 {
@@ -70,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,
@@ -107,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,
@@ -315,11 +315,11 @@ 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:
@@ -327,31 +327,31 @@ enum sc_mouse_button {
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),
@@ -393,6 +393,8 @@ struct sc_mouse_scroll_event {
struct sc_position position;
float hscroll;
float vscroll;
int32_t hscroll_int;
int32_t vscroll_int;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
@@ -449,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;
@@ -458,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;
@@ -467,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;
@@ -482,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;
@@ -498,7 +500,7 @@ 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_CONTROLLER_AXIS_*
return axis;
@@ -508,7 +510,7 @@ 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_CONTROLLER_BUTTON_*
return button;
@@ -518,8 +520,8 @@ sc_gamepad_button_from_sdl(uint8_t button) {
static inline enum sc_action
sc_action_from_sdl_controllerbutton_type(uint32_t type) {
assert(type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || type == SDL_EVENT_GAMEPAD_BUTTON_UP);
if (type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
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"
@@ -374,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
@@ -402,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);
}
@@ -483,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
@@ -505,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);
@@ -536,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);
@@ -545,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
@@ -562,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
@@ -595,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;
}
@@ -605,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);
@@ -674,9 +674,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
int dw;
int dh;
bool always_ok = SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
(void) always_ok;
assert(always_ok);
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = event->x * dw;
@@ -689,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,
};
@@ -725,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) {
@@ -738,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;
@@ -891,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,
};
@@ -908,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 *gc = SDL_OpenGamepad(event->which);
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(gc);
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
if (!joystick) {
LOGW("Could not get controller joystick");
SDL_CloseGamepad(gc);
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 *gc = SDL_GetGamepadFromID(id);
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
SDL_CloseGamepad(gc);
SDL_GameControllerClose(gc);
} else {
LOGW("Unknown gamepad device removed");
}
@@ -949,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;
@@ -965,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;
@@ -988,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;
@@ -1013,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,9 +5,8 @@
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
#include <SDL3/SDL_stdinc.h>
#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"

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_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

@@ -541,10 +541,7 @@ sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
static bool
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
AVCodecContext *ctx) {
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
// only written from this thread, no need to lock
assert(!recorder->video_init);
@@ -638,10 +635,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
static bool
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
AVCodecContext *ctx) {
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
assert(recorder->audio);
// only written from this thread, no need to lock

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;
}
@@ -357,19 +366,12 @@ static void
init_sdl_gamepads(void) {
// 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/window.h"
#define DISPLAY_MARGINS 96
@@ -32,9 +31,7 @@ static struct sc_size
get_window_size(const struct sc_screen *screen) {
int width;
int height;
bool always_ok = SDL_GetWindowSize(screen->window, &width, &height);
(void) always_ok;
assert(always_ok);
SDL_GetWindowSize(screen->window, &width, &height);
struct sc_size size;
size.width = width;
@@ -46,9 +43,7 @@ static struct sc_point
get_window_position(const struct sc_screen *screen) {
int x;
int y;
bool always_ok = SDL_GetWindowPosition(screen->window, &x, &y);
(void) always_ok;
assert(always_ok);
SDL_GetWindowPosition(screen->window, &x, &y);
struct sc_point point;
point.x = x;
@@ -62,19 +57,14 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
bool always_ok =
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
(void) always_ok;
assert(always_ok);
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();
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;
}
@@ -178,9 +168,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
int dw;
int dh;
bool always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
(void) always_ok;
assert(always_ok);
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
@@ -220,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);
@@ -248,31 +235,28 @@ 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_EXPOSED) {
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
static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
(void) ctx;
(void) session;
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
if (ctx->width <= 0 || ctx->width > 0xFFFF
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
@@ -280,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
@@ -330,7 +327,6 @@ 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;
@@ -364,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;
}
@@ -398,24 +394,15 @@ sc_screen_init(struct sc_screen *screen,
}
// The window will be positioned and sized on first video frame
screen->window =
sc_create_sdl_window(title, x, y, width, height, window_flags);
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");
@@ -462,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
@@ -513,12 +496,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
screen->req.height);
set_window_size(screen, window_size);
bool always_ok;
(void) always_ok;
always_ok = SDL_SetWindowPosition(screen->window, x, y);
assert(always_ok);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_toggle_fullscreen(screen);
@@ -528,17 +506,13 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_fps_counter_start(&screen->fps_counter);
}
always_ok = SDL_ShowWindow(screen->window);
assert(always_ok);
SDL_ShowWindow(screen->window);
sc_screen_update_content_rect(screen);
}
void
sc_screen_hide_window(struct sc_screen *screen) {
bool always_ok = SDL_HideWindow(screen->window);
(void) always_ok;
assert(always_ok);
SDL_HideWindow(screen->window);
}
void
@@ -629,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);
@@ -637,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;
}
@@ -677,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);
@@ -749,8 +738,8 @@ void
sc_screen_toggle_fullscreen(struct sc_screen *screen) {
assert(screen->video);
bool ok = SDL_SetWindowFullscreen(screen->window, !screen->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;
}
@@ -784,16 +773,8 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
bool always_ok;
(void) always_ok;
always_ok = SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
assert(always_ok);
always_ok = SDL_SetWindowPosition(screen->window, new_x, new_y);
assert(always_ok);
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);
}
@@ -806,29 +787,29 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
return;
}
bool always_ok;
(void) always_ok;
if (screen->maximized) {
always_ok = SDL_RestoreWindow(screen->window);
assert(always_ok);
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct sc_size content_size = screen->content_size;
always_ok = SDL_SetWindowSize(screen->window, content_size.width,
content_size.height);
assert(always_ok);
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) {
@@ -837,38 +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);
}
return true;
case SDL_EVENT_WINDOW_MAXIMIZED:
screen->maximized = true;
return true;
case SDL_EVENT_WINDOW_MINIMIZED:
screen->minimized = true;
return true;
case SDL_EVENT_WINDOW_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
// !video implies !has_frame
assert(screen->video || !screen->has_frame);
if (!screen->has_frame) {
// Do nothing
return true;
}
screen->maximized = false;
screen->minimized = false;
if (screen->has_video_window) {
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;
}
@@ -948,16 +936,10 @@ sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
bool always_ok;
(void) always_ok;
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
always_ok = SDL_GetWindowSize(screen->window, &ww, &wh);
assert(always_ok);
always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
assert(always_ok);
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,6 @@ 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;

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

@@ -6,8 +6,6 @@
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include "trait/packet_sink.h"
/**
* Frame sink trait.
*
@@ -19,16 +17,9 @@ struct sc_frame_sink {
struct sc_frame_sink_ops {
/* The codec context is valid until the sink is closed */
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx,
const struct sc_stream_session *session);
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
void (*close)(struct sc_frame_sink *sink);
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
/**
* Optional callback to be notified of a new stream session.
*/
bool (*push_session)(struct sc_frame_sink *sink,
const struct sc_stream_session *session);
};
#endif

View File

@@ -27,12 +27,11 @@ sc_frame_source_sinks_close_firsts(struct sc_frame_source *source,
bool
sc_frame_source_sinks_open(struct sc_frame_source *source,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_frame_sink *sink = source->sinks[i];
if (!sink->ops->open(sink, ctx, session)) {
if (!sink->ops->open(sink, ctx)) {
sc_frame_source_sinks_close_firsts(source, i);
return false;
}
@@ -60,18 +59,3 @@ sc_frame_source_sinks_push(struct sc_frame_source *source,
return true;
}
bool
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
const struct sc_stream_session *session) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_frame_sink *sink = source->sinks[i];
if (sink->ops->push_session &&
!sink->ops->push_session(sink, session)) {
return false;
}
}
return true;
}

View File

@@ -28,8 +28,7 @@ sc_frame_source_add_sink(struct sc_frame_source *source,
bool
sc_frame_source_sinks_open(struct sc_frame_source *source,
const AVCodecContext *ctx,
const struct sc_stream_session *session);
const AVCodecContext *ctx);
void
sc_frame_source_sinks_close(struct sc_frame_source *source);
@@ -38,8 +37,4 @@ bool
sc_frame_source_sinks_push(struct sc_frame_source *source,
const AVFrame *frame);
bool
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
const struct sc_stream_session *session);
#endif

View File

@@ -15,28 +15,12 @@ struct sc_packet_sink {
const struct sc_packet_sink_ops *ops;
};
struct sc_stream_session_video {
uint32_t width;
uint32_t height;
};
struct sc_stream_session {
struct sc_stream_session_video video;
};
struct sc_packet_sink_ops {
/* The codec context is valid until the sink is closed */
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx,
const struct sc_stream_session *session);
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx);
void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
/**
* Optional callback to be notified of a new stream session.
*/
bool (*push_session)(struct sc_packet_sink *sink,
const struct sc_stream_session *session);
/*/
* Called when the input stream has been disabled at runtime.
*

View File

@@ -27,12 +27,11 @@ sc_packet_source_sinks_close_firsts(struct sc_packet_source *source,
bool
sc_packet_source_sinks_open(struct sc_packet_source *source,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
AVCodecContext *ctx) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_packet_sink *sink = source->sinks[i];
if (!sink->ops->open(sink, ctx, session)) {
if (!sink->ops->open(sink, ctx)) {
sc_packet_source_sinks_close_firsts(source, i);
return false;
}
@@ -61,20 +60,6 @@ sc_packet_source_sinks_push(struct sc_packet_source *source,
return true;
}
bool
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
const struct sc_stream_session *session) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_packet_sink *sink = source->sinks[i];
if (!sink->ops->push_session(sink, session)) {
return false;
}
}
return true;
}
void
sc_packet_source_sinks_disable(struct sc_packet_source *source) {
assert(source->sink_count);

View File

@@ -28,8 +28,7 @@ sc_packet_source_add_sink(struct sc_packet_source *source,
bool
sc_packet_source_sinks_open(struct sc_packet_source *source,
AVCodecContext *ctx,
const struct sc_stream_session *session);
AVCodecContext *ctx);
void
sc_packet_source_sinks_close(struct sc_packet_source *source);
@@ -38,10 +37,6 @@ bool
sc_packet_source_sinks_push(struct sc_packet_source *source,
const AVPacket *packet);
bool
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
const struct sc_stream_session *session);
void
sc_packet_source_sinks_disable(struct sc_packet_source *source);

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,10 +74,10 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
return;
}
SDL_Gamepad * game_controller =
SDL_GetGamepadFromID(event->gamepad_id);
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(event->gamepad_id);
assert(game_controller);
const char *name = SDL_GetGamepadName(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,18 +63,22 @@ 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)) {
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,24 +7,14 @@
#include "options.h"
#include "util/acksync.h"
#include "util/log.h"
#include "util/window.h"
static void
sc_screen_otg_render(struct sc_screen_otg *screen) {
bool always_ok = SDL_RenderClear(screen->renderer);
(void) always_ok;
assert(always_ok);
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);
}
always_ok = SDL_RenderPresent(screen->renderer);
assert(always_ok);
SDL_RenderPresent(screen->renderer);
}
bool
@@ -44,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;
}
@@ -52,14 +42,13 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
window_flags |= SDL_WINDOW_BORDERLESS;
}
screen->window =
sc_create_sdl_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;
@@ -68,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
}
@@ -125,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);
@@ -181,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),
};
@@ -192,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 *gc = SDL_OpenGamepad(event->which);
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(gc);
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
if (!joystick) {
LOGW("Could not get controller joystick");
SDL_CloseGamepad(gc);
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 *gc = SDL_GetGamepadFromID(id);
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
SDL_CloseGamepad(gc);
SDL_GameControllerClose(gc);
} else {
LOGW("Unknown gamepad device removed");
}
@@ -233,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;
@@ -252,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;
@@ -277,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

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

@@ -1,33 +0,0 @@
#include "window.h"
SDL_Window *
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
int64_t height, int64_t flags) {
SDL_Window *window = NULL;
SDL_PropertiesID props = SDL_CreateProperties();
if (!props) {
return NULL;
}
bool ok =
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING,
title);
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
width);
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
height);
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER,
flags);
if (!ok) {
SDL_DestroyProperties(props);
return NULL;
}
window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
return window;
}

View File

@@ -1,13 +0,0 @@
#ifndef SC_WINDOW_H
#define SC_WINDOW_H
#include "common.h"
#include <stdint.h>
#include <SDL3/SDL_video.h>
SDL_Window *
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
int64_t height, int64_t flags);
#endif

View File

@@ -146,11 +146,9 @@ run_v4l2_sink(void *data) {
}
static bool
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
(void) ctx;
(void) session;
bool ok = sc_frame_buffer_init(&vs->fb);
if (!ok) {
@@ -328,10 +326,9 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
}
static bool
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) {
struct sc_v4l2_sink *vs = DOWNCAST(sink);
return sc_v4l2_sink_open(vs, ctx, session);
return sc_v4l2_sink_open(vs, ctx);
}
static void

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,6 +411,26 @@ static void test_serialize_open_hard_keyboard(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_start_app(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_START_APP,
.start_app = {
.name = "firefox",
},
};
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 9);
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_START_APP,
7, // length
'f', 'i', 'r', 'e', 'f', 'o', 'x', // app name
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_reset_video(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO,
@@ -448,6 +468,7 @@ int main(int argc, char *argv[]) {
test_serialize_uhid_input();
test_serialize_uhid_destroy();
test_serialize_open_hard_keyboard();
test_serialize_start_app();
test_serialize_reset_video();
return 0;
}

View File

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

View File

@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v3.3.3`][direct-scrcpy-server]
<sub>SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0`</sub>
- [`scrcpy-server-v3.3.4`][direct-scrcpy-server]
<sub>SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
@@ -271,7 +271,7 @@ This installs several files:
- `/usr/local/bin/scrcpy` (main app)
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
- `/usr/local/share/icons/hicolor/256x256/apps/scrcpy.png` (app icon)
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)

View File

@@ -409,11 +409,12 @@ with any client which uses the same protocol.
For simplicity, some [server-specific options] have been added to produce raw
streams easily:
- `send_device_meta=false`: disable device metadata (in practice, the device
- `send_device_meta=false`: disable the device metata (in practice, the device
name) sent on the _first_ socket
- `send_frame_meta=false`: disable the 12-byte header for each packet
- `send_dummy_byte`: disable the dummy byte sent on forward connections
- `send_stream_meta`: disable codec and video size metadata
- `send_codec_meta`: disable the codec information (and initial device size for
video)
- `raw_stream`: disable all the above
[server-specific options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L309-L329

View File

@@ -6,11 +6,11 @@
Download a static build of the [latest release]:
- [`scrcpy-linux-x86_64-v3.3.3.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638`</sub>
- [`scrcpy-linux-x86_64-v3.3.4.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `0305d98c06178c67e12427bbf340c436d0d58c9e2a39bf9ffbbf8f54d7ef95a5`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-linux-x86_64-v3.3.4.tar.gz
and extract it.

View File

@@ -6,15 +6,14 @@
Download a static build of the [latest release]:
- [`scrcpy-macos-aarch64-v3.3.3.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `b93299468f19ae89ac70f7c1453914c41f1f2bcd31f6ab530038da885c19581f`</sub>
- [`scrcpy-macos-x86_64-v3.3.3.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `c767fc1d41e4ae26e40558656570962f474739924fd22ee023d8754889ee4366`</sub>
- [`scrcpy-macos-aarch64-v3.3.4.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `8fef43520405dd523c74e1530ac68febcc5a405ea89712c874936675da8513dd`</sub>
- [`scrcpy-macos-x86_64-v3.3.4.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `cf9b3453a33279b6009dfb256b1a84c374bd4c30a71edd74bacab28d72a5d929`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-aarch64-v3.3.3.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-x86_64-v3.3.3.tar.gz
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-aarch64-v3.3.4.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-x86_64-v3.3.4.tar.gz
and extract it.

View File

@@ -6,14 +6,14 @@
Download the [latest release]:
- [`scrcpy-win64-v3.3.3.zip`][direct-win64] (64-bit)
<sub>SHA-256: `4b458d33d0436688c69875cd267cae6fa8be08aa3c17772edf3a940a3dc4b17e`</sub>
- [`scrcpy-win32-v3.3.3.zip`][direct-win32] (32-bit)
<sub>SHA-256: `e3d43e21c0bd6e070381c390c1e4cccd48a1e71ae73a8c217e6e6b8506598c79`</sub>
- [`scrcpy-win64-v3.3.4.zip`][direct-win64] (64-bit)
<sub>SHA-256: `d8a155b7c180b7ca4cdadd40712b8750b63f3aab48cb5b8a2a39ac2d0d4c5d38`</sub>
- [`scrcpy-win32-v3.3.4.zip`][direct-win32] (32-bit)
<sub>SHA-256: `393f7d5379dabd8aacc41184755c3d0df975cd2861353cb7a8d50e0835e2eb72`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win64-v3.3.3.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win32-v3.3.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win64-v3.3.4.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win32-v3.3.4.zip
and extract it.

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '3.3.3',
version: '3.3.4',
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 35
namespace = 'com.genymobile.scrcpy'
compileSdk 36
defaultConfig {
applicationId "com.genymobile.scrcpy"
applicationId = "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 35
versionCode 30303
versionName "3.3.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
targetSdkVersion 36
versionCode 30304
versionName "3.3.4"
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -18,8 +18,11 @@ android {
}
}
buildFeatures {
buildConfig true
aidl true
buildConfig = true
aidl = true
}
lint {
disable 'UseRequiresApi'
}
}

View File

@@ -12,10 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.3.3
SCRCPY_VERSION_NAME=3.3.4
PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
PLATFORM=${ANDROID_PLATFORM:-36}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0}
PLATFORM_TOOLS="$ANDROID_HOME/platforms/android-$PLATFORM"
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
@@ -86,7 +86,7 @@ javac -encoding UTF-8 -bootclasspath "$ANDROID_JAR" \
echo "Dexing..."
cd "$CLASSES_DIR"
if [[ $PLATFORM -lt 31 ]]
if [[ "${PLATFORM%%.*}" -lt 31 ]]
then
# use dx
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \

View File

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

View File

@@ -78,7 +78,7 @@ public class Options {
private boolean sendDeviceMeta = true; // send device name and size
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
private boolean sendStreamMeta = true; // write the stream metadata (codec and session)
private boolean sendCodecMeta = true; // write the codec metadata before the stream
public Ln.Level getLogLevel() {
return logLevel;
@@ -284,8 +284,8 @@ public class Options {
return sendDummyByte;
}
public boolean getSendStreamMeta() {
return sendStreamMeta;
public boolean getSendCodecMeta() {
return sendCodecMeta;
}
@SuppressWarnings("MethodLength")
@@ -500,8 +500,8 @@ public class Options {
case "send_dummy_byte":
options.sendDummyByte = Boolean.parseBoolean(value);
break;
case "send_stream_meta":
options.sendStreamMeta = Boolean.parseBoolean(value);
case "send_codec_meta":
options.sendCodecMeta = Boolean.parseBoolean(value);
break;
case "raw_stream":
boolean rawStream = Boolean.parseBoolean(value);
@@ -509,7 +509,7 @@ public class Options {
options.sendDeviceMeta = false;
options.sendFrameMeta = false;
options.sendDummyByte = false;
options.sendStreamMeta = false;
options.sendCodecMeta = false;
}
break;
default:

View File

@@ -27,6 +27,8 @@ import com.genymobile.scrcpy.video.VideoSource;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import java.io.File;
import java.io.IOException;
@@ -125,7 +127,7 @@ public final class Server {
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
}
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendStreamMeta(), options.getSendFrameMeta());
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
AsyncProcessor audioRecorder;
if (audioCodec == AudioCodec.RAW) {
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
@@ -136,7 +138,7 @@ public final class Server {
}
if (video) {
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendStreamMeta(),
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta());
SurfaceCapture surfaceCapture;
if (options.getVideoSource() == VideoSource.DISPLAY) {
@@ -225,10 +227,16 @@ public final class Server {
}
private static void internalMain(String... args) throws Exception {
Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
Ln.e("Exception on thread " + t, e);
if (defaultHandler != null) {
defaultHandler.uncaughtException(t, e);
}
});
dropRootPrivileges();
prepareMainLooper();
Options options = Options.parse(args);
@@ -269,4 +277,17 @@ public final class Server {
// Do not print stack trace, a user-friendly error-message has already been logged
}
}
@SuppressWarnings("deprecation")
private static void dropRootPrivileges() {
try {
if (Os.getuid() == 0) {
// Copy-paste does not work with root user
// <https://github.com/Genymobile/scrcpy/issues/6224>
Os.setuid(2000);
}
} catch (Exception e) {
Ln.w("Cannot set UID", e);
}
}
}

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

View File

@@ -14,13 +14,12 @@ import java.util.Arrays;
public final class Streamer {
private static final long PACKET_FLAG_SESSION = 1L << 63;
private static final long PACKET_FLAG_CONFIG = 1L << 62;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 61;
private static final long PACKET_FLAG_CONFIG = 1L << 63;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
private final FileDescriptor fd;
private final Codec codec;
private final boolean sendStreamMeta;
private final boolean sendCodecMeta;
private final boolean sendFrameMeta;
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
@@ -28,7 +27,7 @@ public final class Streamer {
public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) {
this.fd = fd;
this.codec = codec;
this.sendStreamMeta = sendCodecMeta;
this.sendCodecMeta = sendCodecMeta;
this.sendFrameMeta = sendFrameMeta;
}
@@ -37,7 +36,7 @@ public final class Streamer {
}
public void writeAudioHeader() throws IOException {
if (sendStreamMeta) {
if (sendCodecMeta) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(codec.getId());
buffer.flip();
@@ -45,10 +44,12 @@ public final class Streamer {
}
}
public void writeVideoHeader() throws IOException {
if (sendStreamMeta) {
ByteBuffer buffer = ByteBuffer.allocate(4);
public void writeVideoHeader(Size videoSize) throws IOException {
if (sendCodecMeta) {
ByteBuffer buffer = ByteBuffer.allocate(12);
buffer.putInt(codec.getId());
buffer.putInt(videoSize.getWidth());
buffer.putInt(videoSize.getHeight());
buffer.flip();
IO.writeFully(fd, buffer);
}
@@ -88,18 +89,6 @@ public final class Streamer {
writePacket(codecBuffer, pts, config, keyFrame);
}
public void writeSessionMeta(int width, int height) throws IOException {
if (sendStreamMeta) {
headerBuffer.clear();
headerBuffer.putInt((int) (PACKET_FLAG_SESSION >> 32)); // Set the first bit to 1
headerBuffer.putInt(width);
headerBuffer.putInt(height);
headerBuffer.flip();
IO.writeFully(fd, headerBuffer);
}
}
private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException {
headerBuffer.clear();

View File

@@ -1,6 +1,7 @@
package com.genymobile.scrcpy.opengl;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.Threads;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
@@ -15,6 +16,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.view.Surface;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
public final class OpenGLRunner {
@@ -80,31 +82,17 @@ public final class OpenGLRunner {
public Surface start(Size inputSize, Size outputSize, Surface outputSurface) throws OpenGLException {
initOnce();
// Simulate CompletableFuture, but working for all Android versions
final Semaphore sem = new Semaphore(0);
Throwable[] throwableRef = new Throwable[1];
// The whole OpenGL execution must be performed on a Handler, so that SurfaceTexture.setOnFrameAvailableListener() works correctly.
// See <https://github.com/Genymobile/scrcpy/issues/5444>
handler.post(() -> {
try {
run(inputSize, outputSize, outputSurface);
} catch (Throwable throwable) {
throwableRef[0] = throwable;
} finally {
sem.release();
}
});
try {
sem.acquire();
} catch (InterruptedException e) {
// Behave as if this method call was synchronous
Thread.currentThread().interrupt();
}
Throwable throwable = throwableRef[0];
if (throwable != null) {
Threads.executeSynchronouslyOn(handler, new Callable<Void>() {
@Override
public Void call() throws Exception {
run(inputSize, outputSize, outputSurface);
return null;
}
});
} catch (Throwable throwable) {
if (throwable instanceof OpenGLException) {
throw (OpenGLException) throwable;
}

View File

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

View File

@@ -0,0 +1,43 @@
package com.genymobile.scrcpy.util;
import android.os.Handler;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
public final class Threads {
private Threads() {
// not instantiable
}
public static <T> T executeSynchronouslyOn(Handler handler, Callable<T> callable) throws Throwable {
// Simulate CompletableFuture, but working for all Android versions
final Semaphore sem = new Semaphore(0);
@SuppressWarnings("unchecked")
T[] resultRef = (T[]) new Object[1];
Throwable[] throwableRef = new Throwable[1];
handler.post(() -> {
try {
resultRef[0] = callable.call();
} catch (Throwable throwable) {
throwableRef[0] = throwable;
} finally {
sem.release();
}
});
try {
sem.acquire();
} catch (InterruptedException e) {
// Behave as if this method call was synchronous
Thread.currentThread().interrupt();
}
if (throwableRef[0] != null) {
throw throwableRef[0];
}
return resultRef[0];
}
}

View File

@@ -71,13 +71,16 @@ public class SurfaceEncoder implements AsyncProcessor {
try {
boolean alive;
streamer.writeVideoHeader();
boolean headerWritten = false;
do {
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
capture.prepare();
Size size = capture.getSize();
if (!headerWritten) {
streamer.writeVideoHeader(size);
headerWritten = true;
}
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth());
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
@@ -104,7 +107,6 @@ public class SurfaceEncoder implements AsyncProcessor {
boolean resetRequested = reset.consumeReset();
if (!resetRequested) {
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
streamer.writeSessionMeta(size.getWidth(), size.getHeight());
encode(mediaCodec, streamer);
}
// The capture might have been closed internally (for example if the camera is disconnected)

View File

@@ -139,7 +139,13 @@ public final class DisplayManager {
int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo);
int flags = cls.getDeclaredField("flags").getInt(displayInfo);
int dpi = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo);
String uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo);
String uniqueId;
try {
uniqueId = (String) cls.getDeclaredField("uniqueId").get(displayInfo);
} catch (NoSuchFieldException e) {
// This field might not exist: <https://github.com/Genymobile/scrcpy/issues/6461>
uniqueId = null;
}
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);