mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-06 12:14:28 +01:00
Compare commits
28 Commits
sc-texture
...
camera_con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bacd1a0c5f | ||
|
|
30203b527d | ||
|
|
58e4905261 | ||
|
|
31fbd9bfb1 | ||
|
|
ce5a2aa224 | ||
|
|
8cb601f602 | ||
|
|
dc628cc433 | ||
|
|
13e2e3d36b | ||
|
|
0a81dd52ea | ||
|
|
70ab117904 | ||
|
|
05379133a4 | ||
|
|
b04032d6f7 | ||
|
|
8e4f38c401 | ||
|
|
c21b36c255 | ||
|
|
5560e39559 | ||
|
|
7840c48043 | ||
|
|
599c17a5a2 | ||
|
|
db5c9e83ef | ||
|
|
1c602f88e5 | ||
|
|
d9791c506f | ||
|
|
4fd28755e0 | ||
|
|
5853adc860 | ||
|
|
7ec2e611dd | ||
|
|
8d4770a6c1 | ||
|
|
a0b4b3d36e | ||
|
|
ba6fa134af | ||
|
|
ee8534dccd | ||
|
|
750bb1a61b |
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@@ -72,19 +72,13 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Same as build-linux-x86_64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
|
||||
# SDL3 is not available in Ubuntu yet
|
||||
- name: Install SDL3
|
||||
@@ -112,19 +106,13 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_linux.sh x86_64
|
||||
@@ -152,7 +140,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -181,7 +171,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -245,7 +237,7 @@ jobs:
|
||||
path: release/work/build-macos-aarch64/dist-tar/
|
||||
|
||||
build-macos-x86_64:
|
||||
runs-on: macos-15-intel
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v3.3.4)
|
||||
# scrcpy (v3.3.3)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
@@ -129,7 +129,7 @@ Here are just some common examples.
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
- Control the device using gamepads plugged into the computer:
|
||||
- Control the device using gamepad controllers plugged into the computer:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
|
||||
@@ -3,9 +3,9 @@ set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=3.4.0
|
||||
VERSION=3.2.18
|
||||
URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz"
|
||||
SHA256SUM=9614b9696abc4597ffce6b888829dc6537ae500423474c342ac4a67222c5654c
|
||||
SHA256SUM=51539fa13e546bc50c632beed3f34257de2baa38a4c642048de56377903b4265
|
||||
|
||||
PROJECT_DIR="sdl-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
@@ -37,7 +37,6 @@ else
|
||||
|
||||
conf=(
|
||||
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
|
||||
-DSDL_TESTS=OFF
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
|
||||
@@ -15,6 +15,7 @@ src = [
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
@@ -32,7 +33,6 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/texture.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
@@ -104,6 +104,7 @@ if usb_support
|
||||
'src/usb/keyboard_aoa.c',
|
||||
'src/usb/mouse_aoa.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
'src/usb/screen_otg.c',
|
||||
'src/usb/usb.c',
|
||||
]
|
||||
endif
|
||||
@@ -275,7 +276,7 @@ if get_option('buildtype') == 'debug'
|
||||
exe = executable(t[0], sources,
|
||||
include_directories: src_dir,
|
||||
dependencies: dependencies,
|
||||
c_args: ['-DSC_TEST'])
|
||||
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
|
||||
test(t[0], exe)
|
||||
endforeach
|
||||
endif
|
||||
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "3.3.4"
|
||||
VALUE "ProductVersion", "3.3.3"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
12
app/scrcpy.1
12
app/scrcpy.1
@@ -824,21 +824,13 @@ Install APK from computer
|
||||
Push file to device (see \fB\-\-push\-target\fR)
|
||||
|
||||
.TP
|
||||
.B MOD+t
|
||||
.B MOD+l
|
||||
Turn on the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+t
|
||||
.B MOD+Shift+l
|
||||
Turn off the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Up
|
||||
Zoom camera in (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Down
|
||||
Zoom camera out (camera mode only)
|
||||
|
||||
|
||||
.SH Environment variables
|
||||
|
||||
|
||||
@@ -120,6 +120,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
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);
|
||||
if (!ok) {
|
||||
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH);
|
||||
(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());
|
||||
|
||||
@@ -1221,21 +1221,13 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.text = "Push file to device (see --push-target)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+t" },
|
||||
.shortcuts = { "MOD+l" },
|
||||
.text = "Turn on the camera torch (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+t" },
|
||||
.shortcuts = { "MOD+Shift+l" },
|
||||
.text = "Turn off the camera torch (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Up" },
|
||||
.text = "Zoom camera in (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Down" },
|
||||
.text = "Zoom camera out (camera mode only)",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct sc_envvar envvars[] = {
|
||||
|
||||
425
app/src/display.c
Normal file
425
app/src/display.c
Normal file
@@ -0,0 +1,425 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static bool
|
||||
sc_display_init_novideo_icon(struct sc_display *display,
|
||||
SDL_Surface *icon_novideo) {
|
||||
assert(icon_novideo);
|
||||
|
||||
bool ok = SDL_SetRenderLogicalPresentation(display->renderer,
|
||||
icon_novideo->w,
|
||||
icon_novideo->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
display->texture = SDL_CreateTextureFromSurface(display->renderer,
|
||||
icon_novideo);
|
||||
if (!display->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||
display->renderer = SDL_CreateRenderer(window, NULL);
|
||||
if (!display->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#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");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
display->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
|
||||
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);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display) {
|
||||
if (display->pending.frame) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_texture(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorRange color_range) {
|
||||
assert(!display->texture);
|
||||
display->pending.texture.size = size;
|
||||
display->pending.texture.color_range = color_range;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
if (!display->pending.frame) {
|
||||
display->pending.frame = av_frame_alloc();
|
||||
if (!display->pending.frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame);
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_TEXTURE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display,
|
||||
display->pending.texture.size,
|
||||
display->pending.texture.color_space,
|
||||
display->pending.texture.color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture_internal(display,
|
||||
display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_prepare_texture_internal(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture =
|
||||
sc_display_create_texture(display, size, color_space, color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_texture(display, size, color_range);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
bool ok = 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) {
|
||||
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);
|
||||
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
bool ok = sc_display_update_texture_internal(display, frame);
|
||||
if (!ok) {
|
||||
ok = sc_display_set_pending_frame(display, frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pending frame");
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
sc_sdl_render_clear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
if (!ok) {
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
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) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
SDL_FRect frect;
|
||||
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;
|
||||
} else {
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
bool ok = SDL_RenderTextureRotated(renderer, texture, NULL, &frect,
|
||||
angle, NULL, flip);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
sc_sdl_render_present(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
69
app/src/display.h
Normal file
69
app/src/display.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
#include "options.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_display {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#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_FRAME 2
|
||||
int8_t flags;
|
||||
struct {
|
||||
struct sc_size size;
|
||||
enum AVColorSpace color_space;
|
||||
enum AVColorRange color_range;
|
||||
} texture;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
SC_DISPLAY_RESULT_OK,
|
||||
SC_DISPLAY_RESULT_PENDING,
|
||||
SC_DISPLAY_RESULT_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps);
|
||||
|
||||
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);
|
||||
|
||||
enum sc_display_result
|
||||
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);
|
||||
|
||||
#endif
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
// See "SDL3/SDL_scancode.h".
|
||||
// See "SDL2/SDL_scancode.h".
|
||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||
// HID protocol.
|
||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||
|
||||
@@ -261,7 +261,7 @@ load_from_path(const char *path) {
|
||||
// frame owns the data
|
||||
bool ok = SDL_SetPointerProperty(props, "sc_frame", frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pointer property: %s", SDL_GetError());
|
||||
LOGE("Could not get surface properties: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* for simplicity.
|
||||
*
|
||||
* This scrcpy input events API is designed to be consumed by input event
|
||||
* processors (sc_key_processor, sc_mouse_processor and sc_gamepad_processor,
|
||||
* see app/src/trait/).
|
||||
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
|
||||
*
|
||||
* One major semantic difference between SDL input events and scrcpy input
|
||||
* events is their frame of reference (for mouse and touch events): SDL events
|
||||
@@ -323,6 +322,9 @@ enum sc_mouse_button {
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON_MASK(SDL_BUTTON_X2),
|
||||
};
|
||||
|
||||
// Use the naming from SDL3 for gamepad axis and buttons:
|
||||
// <https://wiki.libsdl.org/SDL3/README/migration>
|
||||
|
||||
enum sc_gamepad_axis {
|
||||
SC_GAMEPAD_AXIS_UNKNOWN = -1,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_GAMEPAD_AXIS_LEFTX,
|
||||
@@ -409,9 +411,10 @@ struct sc_touch_event {
|
||||
float pressure;
|
||||
};
|
||||
|
||||
// As documented in <https://wiki.libsdl.org/SDL3/SDL_JoystickID>:
|
||||
// The value 0 is an invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID 0
|
||||
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
||||
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||
// invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||
|
||||
struct sc_gamepad_device_event {
|
||||
uint32_t gamepad_id;
|
||||
@@ -497,7 +500,7 @@ static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
|
||||
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_GAMEPAD_AXIS_*
|
||||
// SDL_CONTROLLER_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||
@@ -507,14 +510,14 @@ static inline enum sc_gamepad_button
|
||||
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
if (button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
||||
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||
// SDL_GAMEPAD_BUTTON_*
|
||||
// SDL_CONTROLLER_BUTTON_*
|
||||
return button;
|
||||
}
|
||||
return SC_GAMEPAD_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_gamepad_button_type(uint32_t type) {
|
||||
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) {
|
||||
return SC_ACTION_DOWN;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params) {
|
||||
// A key/mouse processor may not be present if there is no controller
|
||||
assert((!params->kp && !params->mp && !params->gp) || params->controller);
|
||||
// A processor must have ops initialized
|
||||
assert(!params->kp || params->kp->ops);
|
||||
assert(!params->mp || params->mp->ops);
|
||||
@@ -618,7 +620,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
|
||||
if (control && im->camera) {
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_T:
|
||||
case SDLK_L:
|
||||
if (!repeat && down) {
|
||||
camera_set_torch(im, !shift);
|
||||
}
|
||||
@@ -890,7 +892,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_FRect *r = &im->screen->rect;
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
@@ -1018,16 +1020,16 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
}
|
||||
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *sdl_gamepad = SDL_OpenGamepad(event->which);
|
||||
if (!sdl_gamepad) {
|
||||
LOGW("Could not open gamepad");
|
||||
SDL_Gamepad *gc = SDL_OpenGamepad(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get gamepad joystick");
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_CloseGamepad(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1038,9 +1040,9 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
|
||||
if (sdl_gamepad) {
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_CloseGamepad(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -1089,7 +1091,7 @@ sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_gamepad_button_type(event->type),
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||
@@ -1164,8 +1166,8 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
sc_input_manager_process_gamepad_button(im, &event->gbutton);
|
||||
break;
|
||||
case SDL_EVENT_DROP_FILE:
|
||||
case SDL_EVENT_DROP_FILE: {
|
||||
sc_input_manager_process_file(im, &event->drop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#ifdef HAVE_V4L2
|
||||
# include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/scrcpy_otg.h"
|
||||
#endif
|
||||
#include "usb/scrcpy_otg.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
@@ -57,8 +57,8 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
// The mouse will be captured on SDL_EVENT_MOUSE_BUTTON_UP, so
|
||||
// consume the event
|
||||
// The mouse will be captured on SDL_MOUSEBUTTONUP, so consume
|
||||
// the event
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -444,7 +444,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
error = true;
|
||||
error = false;
|
||||
}
|
||||
|
||||
end:
|
||||
|
||||
@@ -355,7 +355,7 @@ scrcpy_generate_scid(void) {
|
||||
|
||||
static void
|
||||
init_sdl_gamepads(void) {
|
||||
// Trigger a SDL_EVENT_GAMEPAD_ADDED event for all gamepads already
|
||||
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||
// connected
|
||||
int count;
|
||||
SDL_JoystickID *joysticks = SDL_GetJoysticks(&count);
|
||||
@@ -564,7 +564,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
struct sc_file_pusher *fp = NULL;
|
||||
|
||||
if (options->window && options->control) {
|
||||
if (options->video_playback && options->control) {
|
||||
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
||||
options->push_target)) {
|
||||
goto end;
|
||||
@@ -949,7 +949,7 @@ aoa_complete:
|
||||
terminate_event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
if (options->window) {
|
||||
if (options->video_playback) {
|
||||
// Close the window immediately on closing, because screen_destroy()
|
||||
// may only be called once the video demuxer thread is joined (it may
|
||||
// take time)
|
||||
|
||||
321
app/src/screen.c
321
app/src/screen.c
@@ -27,11 +27,13 @@ get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
|
||||
return oriented_size;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_windowed(struct sc_screen *screen) {
|
||||
return !(SDL_GetWindowFlags(screen->window) & (SDL_WINDOW_FULLSCREEN
|
||||
| SDL_WINDOW_MINIMIZED
|
||||
| SDL_WINDOW_MAXIMIZED));
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static inline void
|
||||
assert_not_fullscreen(struct sc_screen *screen) {
|
||||
(void) screen;
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
@@ -39,11 +41,6 @@ static bool
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
SDL_Rect rect;
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
if (!display) {
|
||||
LOGW("Could not get primary display: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = SDL_GetDisplayUsableBounds(display, &rect);
|
||||
if (!ok) {
|
||||
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
||||
@@ -144,110 +141,64 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
compute_content_rect(struct sc_size render_size, struct sc_size content_size,
|
||||
bool can_upscale, SDL_FRect *rect) {
|
||||
if (is_optimal_size(render_size, content_size)) {
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
if (is_optimal_size(drawable_size, content_size)) {
|
||||
rect->x = 0;
|
||||
rect->y = 0;
|
||||
rect->w = render_size.width;
|
||||
rect->h = render_size.height;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_upscale && content_size.width <= render_size.width
|
||||
&& content_size.height <= render_size.height) {
|
||||
// Center without upscaling
|
||||
rect->x = (render_size.width - content_size.width) / 2.f;
|
||||
rect->y = (render_size.height - content_size.height) / 2.f;
|
||||
rect->w = content_size.width;
|
||||
rect->h = content_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * render_size.height
|
||||
> content_size.height * render_size.width;
|
||||
bool keep_width = content_size.width * drawable_size.height
|
||||
> content_size.height * drawable_size.width;
|
||||
if (keep_width) {
|
||||
rect->x = 0;
|
||||
rect->w = render_size.width;
|
||||
rect->h = (float) render_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (render_size.height - rect->h) / 2.f;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (drawable_size.height - rect->h) / 2;
|
||||
} else {
|
||||
rect->y = 0;
|
||||
rect->h = render_size.height;
|
||||
rect->w = (float) render_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (render_size.width - rect->w) / 2.f;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = drawable_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (drawable_size.width - rect->w) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
// Only upscale video frames, not icon
|
||||
bool can_upscale = screen->video;
|
||||
|
||||
struct sc_size render_size =
|
||||
sc_sdl_get_render_output_size(screen->renderer);
|
||||
compute_content_rect(render_size, screen->content_size, can_upscale,
|
||||
&screen->rect);
|
||||
}
|
||||
|
||||
// render the texture to the renderer
|
||||
//
|
||||
// Set the update_content_rect flag if the window or content size may have
|
||||
// changed, so that the content rectangle is recomputed
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(!screen->video || screen->has_video_window);
|
||||
assert(screen->video);
|
||||
assert(screen->has_video_window);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
sc_sdl_render_clear(renderer);
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, &screen->rect, screen->orientation);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
SDL_Texture *texture = screen->tex.texture;
|
||||
if (!texture) {
|
||||
LOGW("No texture to render");
|
||||
goto end;
|
||||
}
|
||||
|
||||
SDL_FRect *geometry = &screen->rect;
|
||||
enum sc_orientation orientation = screen->orientation;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
ok = SDL_RenderTexture(renderer, texture, NULL, geometry);
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_FRect *dstrect = NULL;
|
||||
SDL_FRect rect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
ok = SDL_RenderTextureRotated(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
end:
|
||||
sc_sdl_render_present(renderer);
|
||||
static void
|
||||
sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
@@ -265,7 +216,7 @@ event_watcher(void *data, SDL_Event *event) {
|
||||
struct sc_screen *screen = data;
|
||||
assert(screen->video);
|
||||
|
||||
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
|
||||
if (event->type == SDL_EVENT_WINDOW_EXPOSED) {
|
||||
// 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);
|
||||
@@ -343,6 +294,9 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->resize_pending = false;
|
||||
screen->has_frame = false;
|
||||
screen->has_video_window = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
screen->paused = false;
|
||||
screen->resume_frame = NULL;
|
||||
screen->orientation = SC_ORIENTATION_0;
|
||||
@@ -374,8 +328,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
}
|
||||
|
||||
// Always create the window hidden to prevent blinking during initialization
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_HIDDEN;
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
@@ -384,7 +337,8 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
if (params->video) {
|
||||
// The window will be shown on first frame
|
||||
window_flags |= SDL_WINDOW_RESIZABLE;
|
||||
window_flags |= SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
const char *title = params->window_title;
|
||||
@@ -415,46 +369,10 @@ sc_screen_init(struct sc_screen *screen,
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
screen->gl_context = NULL;
|
||||
|
||||
// starts with "opengl"
|
||||
const char *renderer_name = SDL_GetRendererName(screen->renderer);
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
// 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");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
screen->gl_context = SDL_GL_CreateContext(screen->window);
|
||||
if (!screen->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mipmaps = params->video;
|
||||
ok = sc_texture_init(&screen->tex, screen->renderer, mipmaps);
|
||||
if (!ok) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
goto error_destroy_texture;
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
@@ -468,28 +386,24 @@ sc_screen_init(struct sc_screen *screen,
|
||||
} else {
|
||||
// without video, the icon is used as window content, it must be present
|
||||
LOGE("Could not load icon");
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
if (!params->video) {
|
||||
assert(icon);
|
||||
screen->content_size.width = icon->w;
|
||||
screen->content_size.height = icon->h;
|
||||
ok = sc_texture_set_from_surface(&screen->tex, icon);
|
||||
if (!ok) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
bool mipmaps = params->video && params->mipmaps;
|
||||
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
|
||||
mipmaps);
|
||||
if (icon) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
}
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
if (!screen->frame) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_texture;
|
||||
goto error_destroy_display;
|
||||
}
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
@@ -533,27 +447,15 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->open = false;
|
||||
#endif
|
||||
|
||||
if (!screen->video) {
|
||||
// Show the window immediately
|
||||
sc_sdl_show_window(screen->window);
|
||||
|
||||
if (sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
if (!screen->video && sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_texture:
|
||||
sc_texture_destroy(&screen->tex);
|
||||
error_destroy_renderer:
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
if (screen->gl_context) {
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
}
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
error_destroy_display:
|
||||
sc_display_destroy(&screen->display);
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
@@ -579,7 +481,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
get_initial_optimal_size(screen->content_size, screen->req.width,
|
||||
screen->req.height);
|
||||
|
||||
assert(is_windowed(screen));
|
||||
assert_not_fullscreen(screen);
|
||||
sc_sdl_set_window_size(screen->window, window_size);
|
||||
sc_sdl_set_window_position(screen->window, position);
|
||||
|
||||
@@ -615,12 +517,8 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
#ifndef NDEBUG
|
||||
assert(!screen->open);
|
||||
#endif
|
||||
sc_texture_destroy(&screen->tex);
|
||||
sc_display_destroy(&screen->display);
|
||||
av_frame_free(&screen->frame);
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
sc_frame_buffer_destroy(&screen->fb);
|
||||
@@ -639,7 +537,7 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
/ old_content_size.height,
|
||||
};
|
||||
target_size = get_optimal_size(target_size, new_content_size, true);
|
||||
assert(is_windowed(screen));
|
||||
assert_not_fullscreen(screen);
|
||||
sc_sdl_set_window_size(screen->window, target_size);
|
||||
}
|
||||
|
||||
@@ -647,7 +545,7 @@ static void
|
||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (is_windowed(screen)) {
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
// Store the windowed size to be able to compute the optimal size once
|
||||
@@ -663,7 +561,9 @@ static void
|
||||
apply_pending_resize(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
assert(is_windowed(screen));
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
if (screen->resize_pending) {
|
||||
resize_for_content(screen, screen->windowed_content_size,
|
||||
screen->content_size);
|
||||
@@ -703,7 +603,6 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
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;
|
||||
|
||||
@@ -717,12 +616,28 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = sc_texture_set_from_frame(&screen->tex, frame);
|
||||
if (!ok) {
|
||||
enum sc_display_result 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;
|
||||
}
|
||||
|
||||
assert(screen->has_frame);
|
||||
if (!screen->has_video_window) {
|
||||
@@ -778,10 +693,7 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
bool ok = sc_screen_apply_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Resume frame update failed");
|
||||
}
|
||||
sc_screen_apply_frame(screen);
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
@@ -799,23 +711,26 @@ void
|
||||
sc_screen_toggle_fullscreen(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
bool req_fullscreen =
|
||||
!(SDL_GetWindowFlags(screen->window) & SDL_WINDOW_FULLSCREEN);
|
||||
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, req_fullscreen);
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, !screen->fullscreen);
|
||||
if (!ok) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("Requested %s mode", req_fullscreen ? "fullscreen" : "windowed");
|
||||
screen->fullscreen = !screen->fullscreen;
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
apply_pending_resize(screen);
|
||||
}
|
||||
|
||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!is_windowed(screen)) {
|
||||
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -844,10 +759,15 @@ void
|
||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!is_windowed(screen)) {
|
||||
if (screen->fullscreen || screen->minimized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
sc_sdl_restore_window(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
sc_sdl_set_window_size(screen->window, content_size);
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
@@ -863,11 +783,14 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Frame update failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
if (!screen->video || screen->has_video_window) {
|
||||
if (!screen->video) {
|
||||
sc_screen_render_novideo(screen);
|
||||
} else if (screen->has_video_window) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
@@ -876,20 +799,24 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
screen->maximized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
screen->minimized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
if (screen->has_video_window && is_windowed(screen)) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
if (screen->fullscreen) {
|
||||
// On Windows, in maximized+fullscreen, disabling
|
||||
// fullscreen mode unexpectedly triggers the "restored"
|
||||
// then "maximized" events, leaving the window in a
|
||||
// weird state (maximized according to the events, but
|
||||
// not maximized visually).
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||
LOGD("Switched to fullscreen mode");
|
||||
assert(screen->has_video_window);
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||
LOGD("Switched to windowed mode");
|
||||
assert(screen->has_video_window);
|
||||
if (is_windowed(screen)) {
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
if (screen->has_video_window) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
|
||||
@@ -12,20 +12,16 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "display.h"
|
||||
#include "fps_counter.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "input_manager.h"
|
||||
#include "mouse_capture.h"
|
||||
#include "options.h"
|
||||
#include "texture.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_screen {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
@@ -36,7 +32,7 @@ struct sc_screen {
|
||||
bool video;
|
||||
bool camera;
|
||||
|
||||
struct sc_texture tex;
|
||||
struct sc_display display;
|
||||
struct sc_input_manager im;
|
||||
struct sc_mouse_capture mc; // only used in mouse relative mode
|
||||
struct sc_frame_buffer fb;
|
||||
@@ -53,11 +49,6 @@ struct sc_screen {
|
||||
} req;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
|
||||
@@ -69,9 +60,12 @@ struct sc_screen {
|
||||
// client orientation
|
||||
enum sc_orientation orientation;
|
||||
// rectangle of the content (excluding black borders)
|
||||
struct SDL_FRect rect;
|
||||
struct SDL_Rect rect;
|
||||
bool has_frame;
|
||||
bool has_video_window;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
|
||||
AVFrame *frame;
|
||||
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
|
||||
const char *renderer_name = SDL_GetRendererName(renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
tex->mipmaps = false;
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
tex->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
tex->renderer = renderer;
|
||||
tex->texture = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_texture_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_texture_create_frame_texture(struct sc_texture *tex,
|
||||
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_texture_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;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = tex->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
struct sc_opengl *gl = &tex->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(tex->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
|
||||
tex->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame) {
|
||||
|
||||
struct sc_size size = {frame->width, frame->height};
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (!tex->texture
|
||||
|| tex->texture_type != SC_TEXTURE_TYPE_FRAME
|
||||
|| tex->texture_size.width != size.width
|
||||
|| tex->texture_size.height != size.height) {
|
||||
// Incompatible texture, recreate it
|
||||
enum AVColorSpace color_space = frame->colorspace;
|
||||
enum AVColorRange color_range = frame->color_range;
|
||||
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = sc_texture_create_frame_texture(tex, size, color_space,
|
||||
color_range);
|
||||
if (!tex->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size = size;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_FRAME;
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
}
|
||||
|
||||
assert(tex->texture);
|
||||
assert(tex->texture_type == SC_TEXTURE_TYPE_FRAME);
|
||||
|
||||
bool ok = SDL_UpdateYUVTexture(tex->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
assert(tex->texture_id);
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = SDL_CreateTextureFromSurface(tex->renderer, surface);
|
||||
if (!tex->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size.width = surface->w;
|
||||
tex->texture_size.height = surface->h;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_ICON;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
|
||||
enum sc_texture_type {
|
||||
SC_TEXTURE_TYPE_FRAME,
|
||||
SC_TEXTURE_TYPE_ICON,
|
||||
};
|
||||
|
||||
struct sc_texture {
|
||||
SDL_Renderer *renderer; // owned by the caller
|
||||
SDL_Texture *texture;
|
||||
// Only valid if texture != NULL
|
||||
struct sc_size texture_size;
|
||||
enum sc_texture_type texture_type;
|
||||
|
||||
struct sc_opengl gl;
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
};
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface);
|
||||
|
||||
#endif
|
||||
@@ -74,9 +74,10 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(sdl_gamepad);
|
||||
const char *name = SDL_GetGamepadName(sdl_gamepad);
|
||||
SDL_Gamepad * game_controller =
|
||||
SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(game_controller);
|
||||
const char *name = SDL_GetGamepadName(game_controller);
|
||||
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
|
||||
|
||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# include "adb/adb.h"
|
||||
#endif
|
||||
#include "events.h"
|
||||
#include "screen.h"
|
||||
#include "usb/screen_otg.h"
|
||||
#include "usb/aoa_hid.h"
|
||||
#include "usb/gamepad_aoa.h"
|
||||
#include "usb/keyboard_aoa.h"
|
||||
@@ -23,7 +23,7 @@ struct scrcpy_otg {
|
||||
struct sc_mouse_aoa mouse;
|
||||
struct sc_gamepad_aoa gamepad;
|
||||
|
||||
struct sc_screen screen;
|
||||
struct sc_screen_otg screen_otg;
|
||||
};
|
||||
|
||||
static void
|
||||
@@ -49,7 +49,7 @@ event_loop(struct scrcpy_otg *s) {
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
default:
|
||||
sc_screen_handle_event(&s->screen, &event);
|
||||
sc_screen_otg_handle_event(&s->screen_otg, &event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
||||
// Not fatal, keyboard/mouse should still work
|
||||
}
|
||||
}
|
||||
@@ -88,14 +88,13 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
|
||||
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
struct sc_gamepad_processor *gp = NULL;
|
||||
struct sc_keyboard_aoa *keyboard = NULL;
|
||||
struct sc_mouse_aoa *mouse = NULL;
|
||||
struct sc_gamepad_aoa *gamepad = NULL;
|
||||
bool usb_device_initialized = false;
|
||||
bool usb_connected = false;
|
||||
bool aoa_started = false;
|
||||
bool aoa_initialized = false;
|
||||
bool screen_initialized = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, only one process could open a USB device
|
||||
@@ -158,7 +157,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
kp = &s->keyboard.key_processor;
|
||||
keyboard = &s->keyboard;
|
||||
}
|
||||
|
||||
if (enable_mouse) {
|
||||
@@ -166,12 +165,12 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
mp = &s->mouse.mouse_processor;
|
||||
mouse = &s->mouse;
|
||||
}
|
||||
|
||||
if (enable_gamepad) {
|
||||
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
||||
gp = &s->gamepad.gamepad_processor;
|
||||
gamepad = &s->gamepad;
|
||||
}
|
||||
|
||||
ok = sc_aoa_start(&s->aoa);
|
||||
@@ -185,18 +184,10 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
window_title = usb_device.product ? usb_device.product : "scrcpy";
|
||||
}
|
||||
|
||||
struct sc_screen_params params = {
|
||||
.video = false,
|
||||
.camera = false,
|
||||
.controller = false,
|
||||
.fp = NULL,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.gp = gp,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.legacy_paste = false,
|
||||
.clipboard_autosync = false,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
struct sc_screen_otg_params params = {
|
||||
.keyboard = keyboard,
|
||||
.mouse = mouse,
|
||||
.gamepad = gamepad,
|
||||
.window_title = window_title,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
@@ -204,17 +195,13 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
.window_width = options->window_width,
|
||||
.window_height = options->window_height,
|
||||
.window_borderless = options->window_borderless,
|
||||
.orientation = SC_ORIENTATION_0,
|
||||
.mipmaps = options->mipmaps,
|
||||
.fullscreen = false,
|
||||
.start_fps_counter = false,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
};
|
||||
|
||||
ok = sc_screen_init(&s->screen, ¶ms);
|
||||
ok = sc_screen_otg_init(&s->screen_otg, ¶ms);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
// usb_device not needed anymore
|
||||
sc_usb_device_destroy(&usb_device);
|
||||
@@ -229,17 +216,13 @@ end:
|
||||
}
|
||||
sc_usb_stop(&s->usb);
|
||||
|
||||
if (screen_initialized) {
|
||||
sc_screen_interrupt(&s->screen);
|
||||
}
|
||||
|
||||
if (mp) {
|
||||
if (mouse) {
|
||||
sc_mouse_aoa_destroy(&s->mouse);
|
||||
}
|
||||
if (kp) {
|
||||
if (keyboard) {
|
||||
sc_keyboard_aoa_destroy(&s->keyboard);
|
||||
}
|
||||
if (gp) {
|
||||
if (gamepad) {
|
||||
sc_gamepad_aoa_destroy(&s->gamepad);
|
||||
}
|
||||
|
||||
@@ -260,10 +243,5 @@ end:
|
||||
|
||||
sc_usb_destroy(&s->usb);
|
||||
|
||||
if (screen_initialized) {
|
||||
sc_screen_join(&s->screen);
|
||||
sc_screen_destroy(&s->screen);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
327
app/src/usb/screen_otg.c
Normal file
327
app/src/usb/screen_otg.c
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "screen_otg.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
static void
|
||||
sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
sc_sdl_render_clear(screen->renderer);
|
||||
if (screen->texture) {
|
||||
bool ok =
|
||||
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
|
||||
if (!ok) {
|
||||
LOGW("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
sc_sdl_render_present(screen->renderer);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
const struct sc_screen_otg_params *params) {
|
||||
screen->keyboard = params->keyboard;
|
||||
screen->mouse = params->mouse;
|
||||
screen->gamepad = params->gamepad;
|
||||
|
||||
const char *title = params->window_title;
|
||||
assert(title);
|
||||
|
||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
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;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
if (params->window_borderless) {
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
screen->window =
|
||||
sc_sdl_create_window(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);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
ok = SDL_SetRenderLogicalPresentation(screen->renderer, icon->w,
|
||||
icon->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
|
||||
scrcpy_icon_destroy(icon);
|
||||
if (!screen->texture) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
} else {
|
||||
screen->texture = NULL;
|
||||
LOGW("Could not load icon");
|
||||
}
|
||||
|
||||
sc_mouse_capture_init(&screen->mc, screen->window, params->shortcut_mods);
|
||||
|
||||
if (screen->mouse) {
|
||||
// Capture mouse on start
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_renderer:
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_otg_destroy(struct sc_screen_otg *screen) {
|
||||
if (screen->texture) {
|
||||
SDL_DestroyTexture(screen->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_key(struct sc_screen_otg *screen,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
assert(screen->keyboard);
|
||||
struct sc_key_processor *kp = &screen->keyboard->key_processor;
|
||||
|
||||
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),
|
||||
.repeat = event->repeat,
|
||||
.mods_state = sc_mods_state_from_sdl(event->mod),
|
||||
};
|
||||
|
||||
assert(kp->ops->process_key);
|
||||
kp->ops->process_key(kp, &evt, SC_SEQUENCE_INVALID);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
struct sc_mouse_motion_event evt = {
|
||||
// .position not used for HID events
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_motion);
|
||||
mp->ops->process_mouse_motion(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
struct sc_mouse_click_event evt = {
|
||||
// .position not used for HID events
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_click);
|
||||
mp->ops->process_mouse_click(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
// .position not used for HID events
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_scroll);
|
||||
mp->ops->process_mouse_scroll(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
const SDL_GamepadDeviceEvent *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 (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_CloseGamepad(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = SDL_GetJoystickID(joystick),
|
||||
};
|
||||
gp->ops->process_gamepad_added(gp, &evt);
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_CloseGamepad(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = id,
|
||||
};
|
||||
gp->ops->process_gamepad_removed(gp, &evt);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
const SDL_GamepadAxisEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = axis,
|
||||
.value = event->value,
|
||||
};
|
||||
gp->ops->process_gamepad_axis(gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
const SDL_GamepadButtonEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
gp->ops->process_gamepad_button(gp, &evt);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||
if (sc_mouse_capture_handle_event(&screen->mc, event)) {
|
||||
// The mouse capture handler consumed the event
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
sc_screen_otg_render(screen);
|
||||
break;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_KEY_UP:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_motion(screen, &event->motion);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_device(screen, &event->gdevice);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_axis(screen, &event->gaxis);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_button(screen, &event->gbutton);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
52
app/src/usb/screen_otg.h
Normal file
52
app/src/usb/screen_otg.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef SC_SCREEN_OTG_H
|
||||
#define SC_SCREEN_OTG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "mouse_capture.h"
|
||||
#include "usb/gamepad_aoa.h"
|
||||
#include "usb/keyboard_aoa.h"
|
||||
#include "usb/mouse_aoa.h"
|
||||
|
||||
struct sc_screen_otg {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_mouse_capture mc;
|
||||
};
|
||||
|
||||
struct sc_screen_otg_params {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
const char *window_title;
|
||||
bool always_on_top;
|
||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
bool window_borderless;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
};
|
||||
|
||||
bool
|
||||
sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
const struct sc_screen_otg_params *params);
|
||||
|
||||
void
|
||||
sc_screen_otg_destroy(struct sc_screen_otg *screen);
|
||||
|
||||
void
|
||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event);
|
||||
|
||||
#endif
|
||||
@@ -130,23 +130,13 @@ sc_sdl_hide_window(SDL_Window *window) {
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetRenderOutputSize(renderer, &width, &height);
|
||||
void
|
||||
sc_sdl_restore_window(SDL_Window *window) {
|
||||
bool ok = SDL_RestoreWindow(window);
|
||||
if (!ok) {
|
||||
LOGE("Could not get render output size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
LOGE("Could not restore window: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -16,12 +16,12 @@ sc_sdl_create_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_set_window_size(SDL_Window *window, struct sc_size size);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window);
|
||||
|
||||
struct sc_point
|
||||
sc_sdl_get_window_position(SDL_Window *window);
|
||||
|
||||
@@ -34,8 +34,8 @@ sc_sdl_show_window(SDL_Window *window);
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer);
|
||||
void
|
||||
sc_sdl_restore_window(SDL_Window *window);
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer);
|
||||
|
||||
24
doc/build.md
24
doc/build.md
@@ -30,13 +30,13 @@ the following files to a directory accessible from your `PATH`:
|
||||
|
||||
It is also available in scrcpy releases.
|
||||
|
||||
The client requires [FFmpeg] and [SDL]. Just follow the instructions.
|
||||
The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions.
|
||||
|
||||
[adb]: https://developer.android.com/studio/command-line/adb.html
|
||||
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
|
||||
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
|
||||
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
|
||||
[SDL]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
|
||||
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
|
||||
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ Install the required packages from your package manager.
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
sudo apt install ffmpeg libsdl3-0 adb libusb-1.0-0
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
|
||||
|
||||
# client build dependencies
|
||||
sudo apt install gcc git pkg-config meson ninja-build libsdl3-dev \
|
||||
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libswresample-dev libusb-1.0-0-dev
|
||||
|
||||
@@ -77,7 +77,7 @@ pip3 install meson
|
||||
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
||||
|
||||
# client build dependencies
|
||||
sudo dnf install SDL3-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
|
||||
sudo dnf install SDL2-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
|
||||
|
||||
# server build dependencies
|
||||
sudo dnf install java-devel
|
||||
@@ -121,7 +121,7 @@ install the required packages:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-x86_64-sdl3 \
|
||||
pacman -S mingw-w64-x86_64-SDL2 \
|
||||
mingw-w64-x86_64-ffmpeg \
|
||||
mingw-w64-x86_64-libusb
|
||||
|
||||
@@ -136,7 +136,7 @@ For a 32 bits version, replace `x86_64` by `i686`:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-i686-sdl3 \
|
||||
pacman -S mingw-w64-i686-SDL2 \
|
||||
mingw-w64-i686-ffmpeg \
|
||||
mingw-w64-i686-libusb
|
||||
|
||||
@@ -162,7 +162,7 @@ Install the packages with [Homebrew]:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
brew install sdl3 ffmpeg libusb
|
||||
brew install sdl2 ffmpeg libusb
|
||||
|
||||
# client build dependencies
|
||||
brew install pkg-config meson
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v3.3.4`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e`</sub>
|
||||
- [`scrcpy-server-v3.3.3`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
@@ -271,7 +271,7 @@ This installs several files:
|
||||
- `/usr/local/bin/scrcpy` (main app)
|
||||
- `/usr/local/share/scrcpy/scrcpy-server` (server to push to the device)
|
||||
- `/usr/local/share/man/man1/scrcpy.1` (manpage)
|
||||
- `/usr/local/share/icons/hicolor/256x256/apps/scrcpy.png` (app icon)
|
||||
- `/usr/local/share/icons/hicolor/256x256/apps/icon.png` (app icon)
|
||||
- `/usr/local/share/zsh/site-functions/_scrcpy` (zsh completion)
|
||||
- `/usr/local/share/bash-completion/completions/scrcpy` (bash completion)
|
||||
|
||||
|
||||
@@ -165,30 +165,6 @@ scrcpy --video-source=camera --camera-facing=back --camera-ar=16:9 --camera-high
|
||||
[brace expansion]: https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html
|
||||
|
||||
|
||||
## Torch
|
||||
|
||||
The camera torch can be turned on at startup by `--camera-torch`:
|
||||
|
||||
```
|
||||
scrcpy --video-source=camera --camera-torch
|
||||
```
|
||||
|
||||
It can also be turned on and off dynamically with <kbd>MOD</kbd>+<kbd>t</kbd>
|
||||
and <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>t</kbd>, respectively.
|
||||
|
||||
|
||||
## Zoom
|
||||
|
||||
The camera zoom can be set with `--camera-zoom=`:
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --camera-zoom=1.5
|
||||
```
|
||||
|
||||
It can also be adjusted dynamically using <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_ and
|
||||
<kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_.
|
||||
|
||||
|
||||
## Webcam
|
||||
|
||||
Combined with the [V4L2](v4l2.md) feature on Linux, the Android device camera
|
||||
|
||||
10
doc/linux.md
10
doc/linux.md
@@ -6,11 +6,11 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-linux-x86_64-v3.3.4.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `0305d98c06178c67e12427bbf340c436d0d58c9e2a39bf9ffbbf8f54d7ef95a5`</sub>
|
||||
- [`scrcpy-linux-x86_64-v3.3.3.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-linux-x86_64-v3.3.4.tar.gz
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
@@ -39,8 +39,8 @@ First, you need to install the required packages:
|
||||
|
||||
```bash
|
||||
# for Debian/Ubuntu
|
||||
sudo apt install ffmpeg libsdl3-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl3-dev \
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
|
||||
```
|
||||
|
||||
13
doc/macos.md
13
doc/macos.md
@@ -6,14 +6,15 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-macos-aarch64-v3.3.4.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `8fef43520405dd523c74e1530ac68febcc5a405ea89712c874936675da8513dd`</sub>
|
||||
- [`scrcpy-macos-x86_64-v3.3.4.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `cf9b3453a33279b6009dfb256b1a84c374bd4c30a71edd74bacab28d72a5d929`</sub>
|
||||
- [`scrcpy-macos-aarch64-v3.3.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>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-aarch64-v3.3.4.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-macos-x86_64-v3.3.4.tar.gz
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-aarch64-v3.3.3.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-x86_64-v3.3.3.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
@@ -58,10 +58,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
| Tilt horizontally (slide with 2 fingers) | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+_click-and-move_
|
||||
| Drag & drop APK file | Install APK from computer
|
||||
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)
|
||||
| Turn on the camera torch (camera mode only) | <kbd>MOD</kbd>+<kbd>t</kbd>
|
||||
| Turn off the camera torch (camera mode only)| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>t</kbd>
|
||||
| Zoom camera in (camera mode only) | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
||||
| Zoom camera out (camera mode only) | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v3.3.4.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `d8a155b7c180b7ca4cdadd40712b8750b63f3aab48cb5b8a2a39ac2d0d4c5d38`</sub>
|
||||
- [`scrcpy-win32-v3.3.4.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `393f7d5379dabd8aacc41184755c3d0df975cd2861353cb7a8d50e0835e2eb72`</sub>
|
||||
- [`scrcpy-win64-v3.3.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>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win64-v3.3.4.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-win32-v3.3.4.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win64-v3.3.3.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win32-v3.3.3.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.4/scrcpy-server-v3.3.4
|
||||
PREBUILT_SERVER_SHA256=8588238c9a5a00aa542906b6ec7e6d5541d9ffb9b5d0f6e1bc0e365e2303079e
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3
|
||||
PREBUILT_SERVER_SHA256=7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '3.3.4',
|
||||
version: '3.3.3',
|
||||
meson_version: '>= 0.49',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId = "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 36
|
||||
versionCode 30304
|
||||
versionName "3.3.4"
|
||||
versionCode 30303
|
||||
versionName "3.3.3"
|
||||
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=3.3.4
|
||||
SCRCPY_VERSION_NAME=3.3.3
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-36}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-36.0.0}
|
||||
|
||||
@@ -424,7 +424,6 @@ public class Options {
|
||||
if (!value.isEmpty()) {
|
||||
options.audioEncoder = value;
|
||||
}
|
||||
break;
|
||||
case "power_off_on_close":
|
||||
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||
break;
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.genymobile.scrcpy.video.VideoSource;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.system.Os;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -234,8 +233,6 @@ public final class Server {
|
||||
}
|
||||
});
|
||||
|
||||
dropRootPrivileges();
|
||||
|
||||
prepareMainLooper();
|
||||
|
||||
Options options = Options.parse(args);
|
||||
@@ -276,17 +273,4 @@ public final class Server {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void dropRootPrivileges() {
|
||||
try {
|
||||
if (Os.getuid() == 0) {
|
||||
// Copy-paste does not work with root user
|
||||
// <https://github.com/Genymobile/scrcpy/issues/6224>
|
||||
Os.setuid(2000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Ln.w("Cannot set UID", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
return ratio.getAspectRatio();
|
||||
}
|
||||
|
||||
@TargetApi(AndroidVersions.API_30_ANDROID_11)
|
||||
@TargetApi(AndroidVersions.API_28_ANDROID_9)
|
||||
@Override
|
||||
public void start(Surface surface) throws IOException {
|
||||
if (transform != null) {
|
||||
@@ -309,7 +309,6 @@ public class CameraCapture extends SurfaceCapture {
|
||||
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps));
|
||||
}
|
||||
if (initialTorch) {
|
||||
Ln.i("Turn camera torch on");
|
||||
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
|
||||
}
|
||||
if (zoom != 1) {
|
||||
@@ -463,7 +462,6 @@ public class CameraCapture extends SurfaceCapture {
|
||||
assertCameraThread();
|
||||
if (currentSession != null && requestBuilder != null) {
|
||||
try {
|
||||
Ln.i("Turn camera torch " + (enabled ? "on" : "off"));
|
||||
requestBuilder.set(CaptureRequest.FLASH_MODE, enabled ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);
|
||||
CaptureRequest request = requestBuilder.build();
|
||||
setRepeatingRequest(currentSession, request);
|
||||
@@ -474,7 +472,6 @@ public class CameraCapture extends SurfaceCapture {
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(AndroidVersions.API_30_ANDROID_11)
|
||||
private void zoom(boolean in) {
|
||||
cameraHandler.post(() -> {
|
||||
assertCameraThread();
|
||||
|
||||
@@ -457,7 +457,7 @@ public class ControlMessageReaderTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCameraZoomOut() throws IOException {
|
||||
public void testParseCameraZoomIn() throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos);
|
||||
dos.writeByte(ControlMessage.TYPE_CAMERA_ZOOM_OUT);
|
||||
|
||||
Reference in New Issue
Block a user