Compare commits

..

15 Commits

Author SHA1 Message Date
Romain Vimont
77998f543f Rename sc_display to sc_texture
The sc_display component now only handles a texture. Rendering has been
moved to sc_screen.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:57 +01:00
Romain Vimont
c43cc2ddc3 Set display texture from a frame
Add a function to set a texture from an AVFrame on a sc_display.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:52 +01:00
Romain Vimont
1617979549 Set display texture from a surface
Add a function to set a texture from a surface on a sc_display.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:48 +01:00
Romain Vimont
58818dc860 Use floating-point for content location
In SDL3, texture rendering uses SDL_FRect, unlike SDL2 which used
SDL_Rect.

Compute content location using floating-point coordinates from the
start.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:44 +01:00
Romain Vimont
3ef51e691e Factorize icon rendering
Replace SDL "logical rendering" with explicit computation of icon
location, and unify rendering of video frames and icons using the same
common code.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:41 +01:00
Romain Vimont
fb77f8556a Extract function to compute content location
PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:38 +01:00
Romain Vimont
e3f0c21f2a Use render output size to compute content location
The coordinates of the content to render depend on the render output
size, not the window size in pixels. In theory, the two could differ.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:35 +01:00
Romain Vimont
930169af4e Move rendering from sc_display to sc_screen
Three components are involved in displaying device content on screen:
 - a window
 - a renderer
 - a texture

Originally, all three were handled by sc_screen.

Commit 051b74c883 later extracted the
renderer and texture into a separate component, sc_display.

However, the split was a bit awkward because the window size and
rendering location were managed by separate components.

Move rendering back to sc_screen, keeping only texture management
separated.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:31 +01:00
Romain Vimont
3ce5feb5cb 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.

PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:29 +01:00
Romain Vimont
42e2264d67 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
that specific rendering should fail.

Refs SDL/#7651 <https://github.com/libsdl-org/SDL/issues/7651>
Refs #3947 <https://github.com/Genymobile/scrcpy/issues/3947>
Refs 6298ef095f
PR #6651 <https://github.com/Genymobile/scrcpy/pull/6651>
2026-02-09 18:33:18 +01:00
Romain Vimont
c497718a3e Report recording error on write error
The error flag was mistakenly set to false instead of true.
2026-02-05 18:27:05 +01:00
Romain Vimont
07f056353b Use common sc_screen for normal and OTG modes
Originally, the default scrcpy window always displayed the video stream.
Since the OTG mode window only contains the scrcpy logo, it was
implemented as a separate component [1].

Later, the --no-video option was added [2] to control the device without
mirroring (while still using an adb connection, unlike OTG mode). To
support this, sc_screen gained the ability to display a window
containing only the scrcpy logo, like in OTG mode.

As a result, the main sc_screen component can now be reused in OTG mode,
allowing removal of the OTG-specific duplicate implementation
(sc_screen_otg).

[1] commit 91418c79ab
[2] commit 8c650e53cd

Refs #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
Refs #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
PR #6649 <https://github.com/Genymobile/scrcpy/pull/6649>
2026-02-04 20:15:31 +01:00
Romain Vimont
1a99a46b57 Fix switch-case style
Remove unnecessary braces around the switch case and add the missing
break to keep the style consistent with other cases.
2026-02-03 19:12:00 +01:00
Romain Vimont
a9de53f0cb Fix file drop handling with --no-video
The "file pusher" can be used when a window is present, but it was only
initialized when video playback was enabled, causing a segfault on file
drop when running without video playback.
2026-02-02 22:40:23 +01:00
Romain Vimont
c07500bb03 Fix window hiding condition
If a window is present, it must be hidden on quit, regardless of whether
video playback is enabled.
2026-02-02 22:40:23 +01:00
22 changed files with 161 additions and 570 deletions

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -57,7 +57,6 @@ src = [
'src/util/process.c',
'src/util/process_intr.c',
'src/util/rand.c',
'src/util/rect.c',
'src/util/sdl.c',
'src/util/strbuf.c',
'src/util/str.c',
@@ -105,7 +104,6 @@ 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
@@ -192,7 +190,8 @@ executable('scrcpy', src,
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1')
install_data('data/scrcpy.png',
install_data('data/icon.png',
rename: 'scrcpy.png',
install_dir: datadir / 'icons/hicolor/256x256/apps')
install_data('data/zsh-completion/_scrcpy',
install_dir: datadir / 'zsh/site-functions')
@@ -283,6 +282,6 @@ endif
if meson.version().version_compare('>= 0.58.0')
devenv = environment()
devenv.set('SCRCPY_ICON_DIR', meson.current_source_dir() / 'data')
devenv.set('SCRCPY_ICON_PATH', meson.current_source_dir() / 'data/icon.png')
meson.add_devenv(devenv)
endif

View File

@@ -851,8 +851,8 @@ Path to adb.
Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified.
.TP
.B SCRCPY_ICON_DIR
Path to the icon directory.
.B SCRCPY_ICON_PATH
Path to the program icon.
.TP
.B SCRCPY_SERVER_PATH

View File

@@ -1249,8 +1249,8 @@ static const struct sc_envvar envvars[] = {
"--tcpip=<addr>) is specified",
},
{
.name = "SCRCPY_ICON_DIR",
.text = "Path to the icon directory",
.name = "SCRCPY_ICON_PATH",
.text = "Path to the program icon",
},
{
.name = "SCRCPY_SERVER_PATH",

View File

@@ -14,37 +14,33 @@
#include "config.h"
#include "util/env.h"
#include "util/file.h"
#ifdef PORTABLE
# include "util/file.h"
#endif
#include "util/log.h"
#define SCRCPY_DEFAULT_ICON_DIR PREFIX "/share/icons/hicolor/256x256/apps"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_icon_path(const char *filename) {
char *icon_path;
char *icon_dir = sc_get_env("SCRCPY_ICON_DIR");
if (icon_dir) {
get_icon_path(void) {
char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
if (icon_path) {
// if the envvar is set, use it
icon_path = sc_file_build_path(icon_dir, filename);
free(icon_dir);
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using icon from SCRCPY_ICON_DIR: %s", icon_path);
LOGD("Using SCRCPY_ICON_PATH: %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_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
@@ -181,7 +177,7 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
}
static SDL_Surface *
sc_icon_load_from_full_path(const char *path) {
load_from_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
@@ -278,19 +274,19 @@ error:
}
SDL_Surface *
sc_icon_load(const char *filename) {
char *icon_path = get_icon_path(filename);
scrcpy_icon_load(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 = load_from_path(icon_path);
free(icon_path);
return icon;
}
void
sc_icon_destroy(SDL_Surface *icon) {
scrcpy_icon_destroy(SDL_Surface *icon) {
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
assert(props);
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);

View File

@@ -5,12 +5,10 @@
#include <SDL3/SDL_surface.h>
#define SC_ICON_FILENAME_SCRCPY "scrcpy.png"
SDL_Surface *
sc_icon_load(const char *filename);
scrcpy_icon_load(void);
void
sc_icon_destroy(SDL_Surface *icon);
scrcpy_icon_destroy(SDL_Surface *icon);
#endif

View File

@@ -16,8 +16,6 @@
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);
@@ -1166,8 +1164,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

@@ -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 = false;
error = true;
}
end:

View File

@@ -564,7 +564,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_file_pusher *fp = NULL;
if (options->video_playback && options->control) {
if (options->window && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
goto end;

View File

@@ -8,7 +8,6 @@
#include "icon.h"
#include "options.h"
#include "util/log.h"
#include "util/rect.h"
#include "util/sdl.h"
#define DISPLAY_MARGINS 96
@@ -56,6 +55,16 @@ get_preferred_display_bounds(struct sc_size *bounds) {
return true;
}
static bool
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
@@ -81,7 +90,7 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size,
window_size.height = MIN(current_size.height, display_size.height);
}
if (sc_rect_is_optimal_size(window_size, content_size)) {
if (is_optimal_size(window_size, content_size)) {
return window_size;
}
@@ -134,6 +143,44 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
return screen->im.mp && screen->im.mp->relative_mode;
}
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)) {
rect->x = 0;
rect->y = 0;
rect->w = render_size.width;
rect->h = render_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;
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;
} 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;
}
}
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
// Only upscale video frames, not icon
@@ -141,8 +188,8 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
struct sc_size render_size =
sc_sdl_get_render_output_size(screen->renderer);
sc_rect_get_content_location(render_size, screen->content_size, can_upscale,
&screen->rect);
compute_content_rect(render_size, screen->content_size, can_upscale,
&screen->rect);
}
// render the texture to the renderer
@@ -410,33 +457,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 = scrcpy_icon_load();
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) {
scrcpy_icon_destroy(icon);
goto error_destroy_texture;
}
}
if (icon) {
scrcpy_icon_destroy(icon);
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOG_OOM();

View File

@@ -9,7 +9,7 @@
# include "adb/adb.h"
#endif
#include "events.h"
#include "usb/screen_otg.h"
#include "screen.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_otg screen_otg;
struct sc_screen screen;
};
static void
@@ -49,7 +49,7 @@ event_loop(struct scrcpy_otg *s) {
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
default:
sc_screen_otg_handle_event(&s->screen_otg, &event);
sc_screen_handle_event(&s->screen, &event);
break;
}
}
@@ -88,13 +88,14 @@ scrcpy_otg(struct scrcpy_options *options) {
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
struct sc_keyboard_aoa *keyboard = NULL;
struct sc_mouse_aoa *mouse = NULL;
struct sc_gamepad_aoa *gamepad = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
struct sc_gamepad_processor *gp = 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
@@ -157,7 +158,7 @@ scrcpy_otg(struct scrcpy_options *options) {
if (!ok) {
goto end;
}
keyboard = &s->keyboard;
kp = &s->keyboard.key_processor;
}
if (enable_mouse) {
@@ -165,12 +166,12 @@ scrcpy_otg(struct scrcpy_options *options) {
if (!ok) {
goto end;
}
mouse = &s->mouse;
mp = &s->mouse.mouse_processor;
}
if (enable_gamepad) {
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
gamepad = &s->gamepad;
gp = &s->gamepad.gamepad_processor;
}
ok = sc_aoa_start(&s->aoa);
@@ -184,10 +185,18 @@ scrcpy_otg(struct scrcpy_options *options) {
window_title = usb_device.product ? usb_device.product : "scrcpy";
}
struct sc_screen_otg_params params = {
.keyboard = keyboard,
.mouse = mouse,
.gamepad = gamepad,
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,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
@@ -195,13 +204,17 @@ scrcpy_otg(struct scrcpy_options *options) {
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.shortcut_mods = options->shortcut_mods,
.orientation = SC_ORIENTATION_0,
.mipmaps = options->mipmaps,
.fullscreen = false,
.start_fps_counter = false,
};
ok = sc_screen_otg_init(&s->screen_otg, &params);
ok = sc_screen_init(&s->screen, &params);
if (!ok) {
goto end;
}
screen_initialized = true;
// usb_device not needed anymore
sc_usb_device_destroy(&usb_device);
@@ -216,13 +229,17 @@ end:
}
sc_usb_stop(&s->usb);
if (mouse) {
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
}
if (mp) {
sc_mouse_aoa_destroy(&s->mouse);
}
if (keyboard) {
if (kp) {
sc_keyboard_aoa_destroy(&s->keyboard);
}
if (gamepad) {
if (gp) {
sc_gamepad_aoa_destroy(&s->gamepad);
}
@@ -243,5 +260,10 @@ end:
sc_usb_destroy(&s->usb);
if (screen_initialized) {
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
}
return ret;
}

View File

@@ -1,329 +0,0 @@
#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/rect.h"
#include "util/sdl.h"
static void
sc_screen_otg_render(struct sc_screen_otg *screen) {
sc_sdl_render_clear(screen->renderer);
SDL_Texture *texture = screen->tex.texture;
if (texture) {
struct sc_size render_size =
sc_sdl_get_render_output_size(screen->renderer);
SDL_FRect rect;
sc_rect_get_content_location(render_size, screen->tex.texture_size,
false, &rect);
bool ok = SDL_RenderTexture(screen->renderer, texture, NULL, &rect);
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;
}
bool ok = sc_texture_init(&screen->tex, screen->renderer, false);
if (!ok) {
goto error_destroy_renderer;
}
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_SCRCPY);
if (icon) {
bool ok = SDL_SetWindowIcon(screen->window, icon);
if (!ok) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
ok = sc_texture_set_from_surface(&screen->tex, icon);
sc_icon_destroy(icon);
if (!ok) {
LOGE("Could not set icon: %s", SDL_GetError());
}
} else {
LOGE("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_renderer:
SDL_DestroyRenderer(screen->renderer);
error_destroy_window:
SDL_DestroyWindow(screen->window);
return false;
}
void
sc_screen_otg_destroy(struct sc_screen_otg *screen) {
sc_texture_destroy(&screen->tex);
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;
}
}

View File

@@ -1,53 +0,0 @@
#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 "texture.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;
struct sc_texture tex;
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

@@ -5,25 +5,6 @@
#include "util/log.h"
char *
sc_file_build_path(const char *dir, const char *name) {
size_t dir_len = strlen(dir);
size_t name_len = strlen(name);
size_t len = dir_len + name_len + 2; // +2: '/' and '\0'
char *path = malloc(len);
if (!path) {
LOG_OOM();
return NULL;
}
memcpy(path, dir, dir_len);
path[dir_len] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&path[dir_len + 1], name, name_len + 1);
return path;
}
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
@@ -44,9 +25,24 @@ sc_file_get_local_path(const char *name) {
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
char *file_path = sc_file_build_path(dir, name);
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOG_OOM();
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

View File

@@ -40,15 +40,6 @@ sc_file_get_executable_path(void);
char *
sc_file_get_local_path(const char *name);
/**
* Return the concatenation of dir, the path separator and the filename.
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_build_path(const char *dir, const char *filename);
/**
* Indicate if the file exists and is not a directory
*/

View File

@@ -1,52 +0,0 @@
#include "rect.h"
bool
sc_rect_is_optimal_size(struct sc_size current_size,
struct sc_size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// Compute the content location, preserving its aspect ratio
void
sc_rect_get_content_location(struct sc_size render_size,
struct sc_size content_size, bool can_upscale,
SDL_FRect *out) {
if (sc_rect_is_optimal_size(render_size, content_size)) {
out->x = 0;
out->y = 0;
out->w = render_size.width;
out->h = render_size.height;
return;
}
if (!can_upscale && content_size.width <= render_size.width
&& content_size.height <= render_size.height) {
// Center without upscaling
out->x = (render_size.width - content_size.width) / 2.f;
out->y = (render_size.height - content_size.height) / 2.f;
out->w = content_size.width;
out->h = content_size.height;
return;
}
bool keep_width = content_size.width * render_size.height
> content_size.height * render_size.width;
if (keep_width) {
out->x = 0;
out->w = render_size.width;
out->h = (float) render_size.width * content_size.height
/ content_size.width;
out->y = (render_size.height - out->h) / 2.f;
} else {
out->y = 0;
out->h = render_size.height;
out->w = (float) render_size.height * content_size.width
/ content_size.height;
out->x = (render_size.width - out->w) / 2.f;
}
}

View File

@@ -1,24 +0,0 @@
#ifndef SC_RECT_H
#define SC_RECT_H
#include "common.h"
#include <SDL3/SDL_rect.h>
#include "coords.h"
// Return whether the size is optimal
//
// It is optimal if it does not require black borders to preserve the aspect
// ratio, with rounding applied at pixel boundaries.
bool
sc_rect_is_optimal_size(struct sc_size current_size,
struct sc_size content_size);
// Compute the content location, preserving its aspect ratio
void
sc_rect_get_content_location(struct sc_size render_size,
struct sc_size content_size, bool can_upscale,
SDL_FRect *out);
#endif

View File

@@ -38,6 +38,6 @@ ninja -C "$LINUX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$LINUX_BUILD_DIR/dist"
cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$LINUX_BUILD_DIR/dist/"
cp app/data/icon.png "$LINUX_BUILD_DIR/dist/"
cp app/scrcpy.1 "$LINUX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/"

View File

@@ -38,6 +38,6 @@ ninja -C "$MACOS_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$MACOS_BUILD_DIR/dist"
cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/"
cp app/data/scrcpy.png "$MACOS_BUILD_DIR/dist/"
cp app/data/icon.png "$MACOS_BUILD_DIR/dist/"
cp app/scrcpy.1 "$MACOS_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/"

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/"

2
run
View File

@@ -20,6 +20,6 @@ then
exit 1
fi
SCRCPY_ICON_DIR="app/data" \
SCRCPY_ICON_PATH="app/data/icon.png" \
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"