Compare commits

..

16 Commits

Author SHA1 Message Date
Romain Vimont
c2e9aa85e3 rename display to texture 2026-01-24 22:41:34 +01:00
Romain Vimont
82c0ffc69b set_texture_from_frame 2026-01-24 22:41:34 +01:00
Romain Vimont
01cef27bea set_texture_from_surface 2026-01-24 22:41:34 +01:00
Romain Vimont
2f54970f60 frect 2026-01-24 22:41:34 +01:00
Romain Vimont
665fce0707 render icon using common code can_upscale 2026-01-24 22:41:34 +01:00
Romain Vimont
cea820a057 update_rect 2026-01-24 22:41:34 +01:00
Romain Vimont
6f29e9427e use render_output_size 2026-01-24 22:41:34 +01:00
Romain Vimont
8803f452a3 render_output_size 2026-01-24 22:41:33 +01:00
Romain Vimont
0fcf332b37 render_from_screen 2026-01-24 22:40:56 +01:00
Romain Vimont
0cd5c35f19 Move renderer from sc_display to sc_screen
Make sc_screen the owner of both the SDL window and the SDL renderer.
This is the first step toward limiting the role of sc_display to texture
management.
2026-01-24 22:40:54 +01:00
Romain Vimont
63961db125 Simplify texture failure handling
When the scrcpy window is minimized on Windows with D3D9, texture
creation and updates fail.

As a workaround, a mechanism was implemented to reattempt applying the
requested changes.

Since SDL3 defaults to the D3D11 backend, remove this workaround,
which adds a lot of complexity for a backend that should almost never
be used.

However, do not close scrcpy when texture creation or updates fail; only
the frame should be lost.

Refs SDL/#7651 <https://github.com/libsdl-org/SDL/issues/7651>
Refs #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
Refs 6298ef095f
2026-01-24 22:38:06 +01:00
Romain Vimont
503ff8ac79 icon 2026-01-24 22:19:31 +01:00
Romain Vimont
089c62b1a2 fix_window_condition 2026-01-24 22:19:14 +01:00
Romain Vimont
72a2ae3224 SCRCPY_ICON_DIR 2026-01-17 22:06:09 +01:00
Romain Vimont
d9669a2610 sc_file_build_path 2026-01-17 22:00:31 +01:00
Romain Vimont
f1cb2213b2 Rename icon.png to scrcpy.png
This makes the icon name consistent everywhere.
2026-01-17 21:40:53 +01:00
19 changed files with 507 additions and 451 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -15,7 +15,6 @@ src = [
'src/delay_buffer.c',
'src/demuxer.c',
'src/device_msg.c',
'src/disconnect.c',
'src/events.c',
'src/icon.c',
'src/file_pusher.c',
@@ -105,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

View File

@@ -1,89 +0,0 @@
#include "disconnect.h"
#include <assert.h>
#include "icon.h"
#include "util/log.h"
static int
run(void *userdata) {
struct sc_disconnect *d = userdata;
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_DISCONNECTED);
if (icon) {
d->cbs->on_icon_loaded(d, icon, d->cbs_userdata);
} else {
LOGE("Could not load disconnected icon");
}
if (d->deadline != SC_TICK_NONE) {
sc_mutex_lock(&d->mutex);
bool timed_out = false;
while (!d->interrupted && !timed_out) {
timed_out = !sc_cond_timedwait(&d->cond, &d->mutex, d->deadline);
}
sc_mutex_unlock(&d->mutex);
if (!d->interrupted) {
d->cbs->on_timeout(d, d->cbs_userdata);
}
}
return 0;
}
bool
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
const struct sc_disconnect_callbacks *cbs,
void *cbs_userdata) {
bool ok = sc_mutex_init(&d->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&d->cond);
if (!ok) {
goto error_destroy_mutex;
}
ok = sc_thread_create(&d->thread, run, "scrcpy-dis", d);
if (!ok) {
goto error_destroy_cond;
}
d->deadline = deadline;
d->interrupted = false;
assert(cbs && cbs->on_icon_loaded && cbs->on_timeout);
d->cbs = cbs;
d->cbs_userdata = cbs_userdata;
return true;
error_destroy_mutex:
sc_mutex_destroy(&d->mutex);
error_destroy_cond:
sc_cond_destroy(&d->cond);
return false;
}
void
sc_disconnect_interrupt(struct sc_disconnect *d) {
sc_mutex_lock(&d->mutex);
d->interrupted = true;
sc_mutex_unlock(&d->mutex);
// wake up blocking wait
sc_cond_signal(&d->cond);
}
void
sc_disconnect_join(struct sc_disconnect *d) {
sc_thread_join(&d->thread, NULL);
}
void
sc_disconnect_destroy(struct sc_disconnect *d) {
sc_cond_destroy(&d->cond);
sc_mutex_destroy(&d->mutex);
}

View File

@@ -1,47 +0,0 @@
#ifndef SC_DISCONNECT
#define SC_DISCONNECT
#include "common.h"
#include "SDL3/SDL_surface.h"
#include "util/tick.h"
#include "util/thread.h"
// Tool to handle loading the icon and signal timeout when the device is
// unexpectedly disconnected
struct sc_disconnect {
sc_tick deadline;
struct sc_thread thread;
struct sc_mutex mutex;
struct sc_cond cond;
bool interrupted;
const struct sc_disconnect_callbacks *cbs;
void *cbs_userdata;
};
struct sc_disconnect_callbacks {
// Called when the disconnected icon is loaded
void (*on_icon_loaded)(struct sc_disconnect *d, SDL_Surface *icon,
void *userdata);
// Called when the timeout expired (the scrcpy window must be closed)
void (*on_timeout)(struct sc_disconnect *d, void *userdata);
};
bool
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
const struct sc_disconnect_callbacks *cbs,
void *cbs_userdata);
void
sc_disconnect_interrupt(struct sc_disconnect *d);
void
sc_disconnect_join(struct sc_disconnect *d);
void
sc_disconnect_destroy(struct sc_disconnect *d);
#endif

View File

@@ -6,13 +6,9 @@
#include "util/thread.h"
bool
sc_push_event_impl(uint32_t type, void* ptr, const char *name) {
SDL_Event event = {
.user = {
.type = type,
.data1 = ptr,
}
};
sc_push_event_impl(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
bool ok = SDL_PushEvent(&event);
if (!ok) {
LOGE("Could not post %s event: %s", name, SDL_GetError());

View File

@@ -13,20 +13,18 @@ enum {
SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED,
SC_EVENT_SERVER_CONNECTED,
SC_EVENT_USB_DEVICE_DISCONNECTED,
SC_EVENT_DEMUXER_ERROR,
SC_EVENT_RECORDER_ERROR,
SC_EVENT_TIME_LIMIT_REACHED,
SC_EVENT_CONTROLLER_ERROR,
SC_EVENT_AOA_OPEN_ERROR,
SC_EVENT_DISCONNECTED_ICON_LOADED,
SC_EVENT_DISCONNECTED_TIMEOUT,
};
bool
sc_push_event_impl(uint32_t type, void* ptr, const char *name);
sc_push_event_impl(uint32_t type, const char *name);
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, NULL, # TYPE)
#define sc_push_event_with_data(TYPE, PTR) sc_push_event_impl(TYPE, PTR, # TYPE)
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE)
typedef void (*sc_runnable_fn)(void *userdata);

View File

@@ -17,34 +17,32 @@
#include "util/file.h"
#include "util/log.h"
#define SCRCPY_DEFAULT_ICON_DIR PREFIX "/share/icons/hicolor/256x256/apps"
#define SCRCPY_ICON_FILENAME "scrcpy.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_icon_path(const char *filename) {
get_icon_path(void) {
char *icon_path;
char *icon_dir = sc_get_env("SCRCPY_ICON_DIR");
if (icon_dir) {
// if the envvar is set, use it
icon_path = sc_file_build_path(icon_dir, filename);
icon_path = sc_file_build_path(icon_dir, SCRCPY_ICON_FILENAME);
free(icon_dir);
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using icon from SCRCPY_ICON_DIR: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
icon_path = sc_file_build_path(SCRCPY_DEFAULT_ICON_DIR, filename);
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using icon: %s", icon_path);
#else
icon_path = sc_file_get_local_path(filename);
icon_path = sc_file_get_local_path(SCRCPY_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
@@ -180,8 +178,8 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
}
}
static SDL_Surface *
sc_icon_load_from_full_path(const char *path) {
SDL_Surface *
sc_icon_load(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
@@ -278,13 +276,13 @@ error:
}
SDL_Surface *
sc_icon_load(const char *filename) {
char *icon_path = get_icon_path(filename);
sc_icon_load_scrcpy(void) {
char *icon_path = get_icon_path();
if (!icon_path) {
return NULL;
}
SDL_Surface *icon = sc_icon_load_from_full_path(icon_path);
SDL_Surface *icon = sc_icon_load(icon_path);
free(icon_path);
return icon;
}

View File

@@ -5,11 +5,11 @@
#include <SDL3/SDL_surface.h>
#define SC_ICON_FILENAME_SCRCPY "scrcpy.png"
#define SC_ICON_FILENAME_DISCONNECTED "disconnected.png"
SDL_Surface *
sc_icon_load(const char *path);
SDL_Surface *
sc_icon_load(const char *filename);
sc_icon_load_scrcpy(void);
void
sc_icon_destroy(SDL_Surface *icon);

View File

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

View File

@@ -167,41 +167,23 @@ sdl_configure(bool video_playback, bool disable_screensaver) {
}
static enum scrcpy_exit_code
event_loop(struct scrcpy *s, bool has_screen, bool disconnected) {
event_loop(struct scrcpy *s, bool has_screen) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SC_EVENT_DEVICE_DISCONNECTED:
if (disconnected) {
break;
}
LOGW("Device disconnected");
if (has_screen && !sc_screen_handle_event(&s->screen, &event)) {
return SCRCPY_EXIT_FAILURE;
}
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_DEMUXER_ERROR:
if (disconnected) {
break;
}
LOGE("Demuxer error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_CONTROLLER_ERROR:
if (disconnected) {
break;
}
LOGE("Controller error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_RECORDER_ERROR:
if (disconnected) {
break;
}
LOGE("Recorder error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_AOA_OPEN_ERROR:
if (disconnected) {
break;
}
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_TIME_LIMIT_REACHED:
@@ -210,9 +192,6 @@ event_loop(struct scrcpy *s, bool has_screen, bool disconnected) {
case SDL_EVENT_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
case SC_EVENT_DISCONNECTED_TIMEOUT:
LOGD("Closing after device disconnection");
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_RUN_ON_MAIN_THREAD: {
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
@@ -435,7 +414,6 @@ scrcpy(struct scrcpy_options *options) {
bool screen_initialized = false;
bool timeout_initialized = false;
bool timeout_started = false;
bool disconnected = false;
struct sc_acksync *acksync = NULL;
@@ -586,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;
@@ -967,9 +945,16 @@ aoa_complete:
}
}
ret = event_loop(s, options->window, false);
ret = event_loop(s, options->window);
terminate_event_loop();
disconnected = ret == SCRCPY_EXIT_DISCONNECTED;
LOGD("quit...");
if (options->window) {
// Close the window immediately on closing, because screen_destroy()
// may only be called once the video demuxer thread is joined (it may
// take time)
sc_screen_hide_window(&s->screen);
}
end:
if (timeout_started) {
@@ -1014,25 +999,6 @@ end:
sc_server_stop(&s->server);
}
if (screen_initialized && ret != SCRCPY_EXIT_DISCONNECTED) {
assert(options->window);
// Close the window immediately, because sc_screen_destroy() may only be
// called once the video demuxer thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
}
if (screen_initialized && options->window) {
if (disconnected) {
ret = event_loop(s, options->window, true);
sc_screen_interrupt_disconnect(&s->screen);
}
LOGD("Quit...");
// Close the window immediately, because sc_screen_destroy() may only be
// called once the video demuxer thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
}
if (timeout_started) {
sc_timeout_join(&s->timeout);
}

View File

@@ -144,8 +144,8 @@ 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) {
compute_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)) {
rect->x = 0;
rect->y = 0;
@@ -184,12 +184,11 @@ compute_content_rect(struct sc_size render_size, struct sc_size content_size,
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
// Only upscale video frames, not icon
bool can_upscale = screen->video && !screen->disconnected;
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);
compute_rect(render_size, screen->content_size, can_upscale, &screen->rect);
}
// render the texture to the renderer
@@ -207,18 +206,15 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
SDL_Renderer *renderer = screen->renderer;
sc_sdl_render_clear(renderer);
bool ok = false;
SDL_Texture *texture = screen->tex.texture;
if (!texture) {
if (!screen->disconnected) {
LOGW("No texture to render");
}
goto end;
}
SDL_FRect *geometry = &screen->rect;
enum sc_orientation orientation = screen->orientation;
SDL_Texture *texture = sc_texture_get_sdl_texture(&screen->tex);
if (!texture) {
return;
}
bool ok;
if (orientation == SC_ORIENTATION_0) {
ok = SDL_RenderTexture(renderer, texture, NULL, geometry);
} else {
@@ -247,8 +243,6 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
if (!ok) {
LOGE("Could not render texture: %s", SDL_GetError());
}
end:
sc_sdl_render_present(renderer);
}
@@ -348,8 +342,6 @@ sc_screen_init(struct sc_screen *screen,
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
screen->disconnected = false;
screen->disconnect_started = false;
screen->video = params->video;
screen->camera = params->camera;
@@ -425,30 +417,6 @@ sc_screen_init(struct sc_screen *screen,
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) {
@@ -461,33 +429,35 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_texture;
}
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_SCRCPY);
SDL_Surface *icon = sc_icon_load_scrcpy();
if (icon) {
if (!SDL_SetWindowIcon(screen->window, icon)) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
if (!params->video) {
screen->content_size.width = icon->w;
screen->content_size.height = icon->h;
ok = sc_texture_set_from_surface(&screen->tex, icon);
if (!ok) {
LOGE("Could not set icon: %s", SDL_GetError());
}
}
sc_icon_destroy(icon);
} else if (params->video) {
// just a warning
LOGW("Could not load icon");
} else {
// not fatal
// 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) {
// Make sure the content size is initialized
screen->content_size.width = 256;
screen->content_size.height = 256;
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) {
sc_icon_destroy(icon);
goto error_destroy_texture;
}
}
if (icon) {
sc_icon_destroy(icon);
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOG_OOM();
@@ -550,11 +520,6 @@ sc_screen_init(struct sc_screen *screen,
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_window:
SDL_DestroyWindow(screen->window);
@@ -607,19 +572,9 @@ sc_screen_interrupt(struct sc_screen *screen) {
sc_fps_counter_interrupt(&screen->fps_counter);
}
void
sc_screen_interrupt_disconnect(struct sc_screen *screen) {
if (screen->disconnect_started) {
sc_disconnect_interrupt(&screen->disconnect);
}
}
void
sc_screen_join(struct sc_screen *screen) {
sc_fps_counter_join(&screen->fps_counter);
if (screen->disconnect_started) {
sc_disconnect_join(&screen->disconnect);
}
}
void
@@ -627,14 +582,8 @@ sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
if (screen->disconnect_started) {
sc_disconnect_destroy(&screen->disconnect);
}
sc_texture_destroy(&screen->tex);
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);
@@ -869,37 +818,12 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
content_size.height);
}
static void
sc_disconnect_on_icon_loaded(struct sc_disconnect *d, SDL_Surface *icon,
void *userdata) {
(void) d;
(void) userdata;
bool ok = sc_push_event_with_data(SC_EVENT_DISCONNECTED_ICON_LOADED, icon);
if (!ok) {
sc_icon_destroy(icon);
}
}
static void
sc_disconnect_on_timeout(struct sc_disconnect *d, void *userdata) {
(void) d;
(void) userdata;
bool ok = sc_push_event(SC_EVENT_DISCONNECTED_TIMEOUT);
(void) ok; // ignore failure
}
bool
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
// !video implies !has_video_window
assert(screen->video || !screen->has_video_window);
switch (event->type) {
case SC_EVENT_NEW_FRAME: {
if (screen->disconnected) {
// ignore
return true;
}
bool ok = sc_screen_update_frame(screen);
if (!ok) {
LOGE("Frame update failed\n");
@@ -934,44 +858,6 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
sc_screen_render(screen, true);
}
return true;
case SC_EVENT_DEVICE_DISCONNECTED:
if (screen->disconnected) {
return true;
}
screen->disconnected = true;
sc_texture_reset(&screen->tex);
sc_screen_render(screen, true);
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_SEC(2);
static const struct sc_disconnect_callbacks cbs = {
.on_icon_loaded = sc_disconnect_on_icon_loaded,
.on_timeout = sc_disconnect_on_timeout,
};
bool ok =
sc_disconnect_start(&screen->disconnect, deadline, &cbs, NULL);
if (ok) {
screen->disconnect_started = true;
}
// else not fatal
return true;
case SC_EVENT_DISCONNECTED_ICON_LOADED: {
SDL_Surface *icon_disconnected = event->user.data1;
assert(icon_disconnected);
bool ok = sc_texture_set_from_surface(&screen->tex, icon_disconnected);
if (ok) {
screen->content_size.width = icon_disconnected->w;
screen->content_size.height = icon_disconnected->h;
sc_screen_render(screen, true);
} else {
// not fatal
LOGE("Could not set disconnected icon");
}
sc_icon_destroy(icon_disconnected);
return true;
}
}
if (sc_screen_is_relative_mode(screen)

View File

@@ -12,7 +12,6 @@
#include "controller.h"
#include "coords.h"
#include "disconnect.h"
#include "fps_counter.h"
#include "frame_buffer.h"
#include "input_manager.h"
@@ -23,10 +22,6 @@
#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
@@ -55,10 +50,6 @@ struct sc_screen {
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
@@ -78,10 +69,6 @@ struct sc_screen {
bool paused;
AVFrame *resume_frame;
bool disconnected;
bool disconnect_started;
struct sc_disconnect disconnect;
};
struct sc_screen_params {
@@ -121,15 +108,10 @@ bool
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
// request to interrupt any inner thread
// must be called before sc_screen_join()
// must be called before screen_join()
void
sc_screen_interrupt(struct sc_screen *screen);
// request to interrupt the disconnected state (before closing the window)
// must be called before sc_screen_join();
void
sc_screen_interrupt_disconnect(struct sc_screen *screen);
// join any inner thread
void
sc_screen_join(struct sc_screen *screen);

View File

@@ -14,9 +14,31 @@ sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
tex->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());
return false;
}
#endif
struct sc_opengl *gl = &tex->gl;
sc_opengl_init(gl);
@@ -47,6 +69,9 @@ sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
void
sc_texture_destroy(struct sc_texture *tex) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DestroyContext(tex->gl_context);
#endif
if (tex->texture) {
SDL_DestroyTexture(tex->texture);
}
@@ -226,10 +251,7 @@ sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
return true;
}
void
sc_texture_reset(struct sc_texture *tex) {
if (tex->texture) {
SDL_DestroyTexture(tex->texture);
tex->texture = NULL;
}
SDL_Texture *
sc_texture_get_sdl_texture(struct sc_texture *tex) {
return tex->texture;
}

View File

@@ -11,6 +11,10 @@
#include "coords.h"
#include "opengl.h"
#ifdef __APPLE__
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
#endif
enum sc_texture_type {
SC_TEXTURE_TYPE_FRAME,
SC_TEXTURE_TYPE_ICON,
@@ -24,6 +28,9 @@ struct sc_texture {
enum sc_texture_type texture_type;
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
@@ -41,7 +48,7 @@ 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);
void
sc_texture_reset(struct sc_texture *tex);
SDL_Texture *
sc_texture_get_sdl_texture(struct sc_texture *tex);
#endif

View File

@@ -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
@@ -31,35 +31,25 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
}
static enum scrcpy_exit_code
event_loop(struct scrcpy_otg *s, bool disconnected) {
event_loop(struct scrcpy_otg *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SC_EVENT_DEVICE_DISCONNECTED:
if (disconnected) {
break;
}
case SC_EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
sc_screen_handle_event(&s->screen, &event);
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_AOA_OPEN_ERROR:
if (disconnected) {
break;
}
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SDL_EVENT_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
case SC_EVENT_DISCONNECTED_TIMEOUT:
LOGD("Closing after device disconnection");
return SCRCPY_EXIT_DISCONNECTED;
default:
sc_screen_handle_event(&s->screen, &event);
sc_screen_otg_handle_event(&s->screen_otg, &event);
break;
}
}
@@ -98,15 +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;
bool disconnected = false;
#ifdef _WIN32
// On Windows, only one process could open a USB device
@@ -169,7 +157,7 @@ scrcpy_otg(struct scrcpy_options *options) {
if (!ok) {
goto end;
}
kp = &s->keyboard.key_processor;
keyboard = &s->keyboard;
}
if (enable_mouse) {
@@ -177,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);
@@ -196,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,
@@ -215,24 +195,20 @@ 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, &params);
ok = sc_screen_otg_init(&s->screen_otg, &params);
if (!ok) {
goto end;
}
screen_initialized = true;
// usb_device not needed anymore
sc_usb_device_destroy(&usb_device);
usb_device_initialized = false;
ret = event_loop(s, false);
disconnected = ret == SCRCPY_EXIT_DISCONNECTED;
ret = event_loop(s);
LOGD("quit...");
end:
if (aoa_started) {
@@ -240,26 +216,13 @@ end:
}
sc_usb_stop(&s->usb);
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
if (disconnected) {
ret = event_loop(s, true);
sc_screen_interrupt_disconnect(&s->screen);
}
LOGD("Quit...");
// Close the window immediately
sc_screen_hide_window(&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);
}
@@ -280,10 +243,5 @@ end:
sc_usb_destroy(&s->usb);
if (screen_initialized) {
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
}
return ret;
}

326
app/src/usb/screen_otg.c Normal file
View File

@@ -0,0 +1,326 @@
#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 = sc_icon_load_scrcpy();
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);
sc_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 *sdl_gamepad = SDL_OpenGamepad(event->which);
if (!sdl_gamepad) {
LOGW("Could not open gamepad");
return;
}
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
if (!joystick) {
LOGW("Could not get gamepad joystick");
SDL_CloseGamepad(sdl_gamepad);
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 *sdl_gamepad = SDL_GetGamepadFromID(id);
if (sdl_gamepad) {
SDL_CloseGamepad(sdl_gamepad);
} 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_gamepad_button_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
View 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

View File

@@ -6,7 +6,6 @@
#include <stdint.h>
typedef int64_t sc_tick;
#define SC_TICK_NONE INT64_MIN
#define PRItick PRIi64
#define SC_TICK_FREQ 1000000 // microsecond

View File

@@ -47,7 +47,7 @@ mkdir -p "$WINXX_BUILD_DIR/dist"
cp "$WINXX_BUILD_DIR"/app/scrcpy.exe "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-console.bat "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-noconsole.vbs "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$WINXX_BUILD_DIR/dist/"
cp app/data/icon.png "$WINXX_BUILD_DIR/dist/"
cp app/data/open_a_terminal_here.bat "$WINXX_BUILD_DIR/dist/"
cp "$DEPS_INSTALL_DIR"/bin/*.dll "$WINXX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$WINXX_BUILD_DIR/dist/"