Compare commits

..

19 Commits

Author SHA1 Message Date
Romain Vimont
ac82761932 icon-from-filename 2026-01-31 22:14:05 +01:00
Romain Vimont
0f93b7fea1 SCRCPY_ICON_DIR 2026-01-31 22:11:10 +01:00
Romain Vimont
aa2f826a9a sc_file_build_path 2026-01-31 22:11:10 +01:00
Romain Vimont
9fb194c3ad Rename icon.png to scrcpy.png
This makes the icon name consistent everywhere.
2026-01-31 22:11:10 +01:00
Romain Vimont
cf47819c18 sc_texture-in-otg 2026-01-31 22:11:10 +01:00
Romain Vimont
4c32823852 extract-rect-util 2026-01-31 22:11:10 +01:00
Romain Vimont
19c015dd91 do-not-fail-if-icon-fails 2026-01-31 22:11:08 +01:00
Romain Vimont
66dda394d7 rename display to texture 2026-01-31 21:38:32 +01:00
Romain Vimont
123df8e20e set_texture_from_frame 2026-01-31 21:37:43 +01:00
Romain Vimont
ffc7847608 set_texture_from_surface 2026-01-31 21:37:40 +01:00
Romain Vimont
b8d25586bf frect 2026-01-31 21:33:48 +01:00
Romain Vimont
73b503427c render icon using common code can_upscale 2026-01-31 21:33:48 +01:00
Romain Vimont
57acb665c0 update_rect 2026-01-31 21:33:48 +01:00
Romain Vimont
665c586d76 render_output_size 2026-01-31 21:33:48 +01:00
Romain Vimont
b5580dd2e1 render_from_screen 2026-01-31 21:33:48 +01:00
Romain Vimont
71305012a0 fix_macos 2026-01-31 21:33:48 +01:00
Romain Vimont
9ca85aadef 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-31 21:33:48 +01:00
Romain Vimont
3b13a68c7a 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-31 21:33:48 +01:00
Romain Vimont
362df5cc84 fix_window_condition 2026-01-31 21:33:40 +01:00
22 changed files with 571 additions and 162 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,6 +57,7 @@ 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',
@@ -104,6 +105,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
@@ -190,8 +192,7 @@ executable('scrcpy', src,
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1')
install_data('data/icon.png',
rename: 'scrcpy.png',
install_data('data/scrcpy.png',
install_dir: datadir / 'icons/hicolor/256x256/apps')
install_data('data/zsh-completion/_scrcpy',
install_dir: datadir / 'zsh/site-functions')
@@ -282,6 +283,6 @@ endif
if meson.version().version_compare('>= 0.58.0')
devenv = environment()
devenv.set('SCRCPY_ICON_PATH', meson.current_source_dir() / 'data/icon.png')
devenv.set('SCRCPY_ICON_DIR', meson.current_source_dir() / 'data')
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_PATH
Path to the program icon.
.B SCRCPY_ICON_DIR
Path to the icon directory.
.TP
.B SCRCPY_SERVER_PATH

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -8,6 +8,7 @@
#include "icon.h"
#include "options.h"
#include "util/log.h"
#include "util/rect.h"
#include "util/sdl.h"
#define DISPLAY_MARGINS 96
@@ -55,16 +56,6 @@ 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)
@@ -90,7 +81,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 (is_optimal_size(window_size, content_size)) {
if (sc_rect_is_optimal_size(window_size, content_size)) {
return window_size;
}
@@ -143,44 +134,6 @@ 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
@@ -188,8 +141,8 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
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);
sc_rect_get_content_location(render_size, screen->content_size, can_upscale,
&screen->rect);
}
// render the texture to the renderer
@@ -457,33 +410,31 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_texture;
}
SDL_Surface *icon = scrcpy_icon_load();
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_SCRCPY);
if (icon) {
if (!SDL_SetWindowIcon(screen->window, icon)) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
} else if (params->video) {
// just a warning
LOGW("Could not load icon");
} 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;
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());
}
}
}
if (icon) {
scrcpy_icon_destroy(icon);
sc_icon_destroy(icon);
} else {
// not fatal
LOGE("Could not load icon");
if (!params->video) {
// Make sure the content size is initialized
screen->content_size.width = 256;
screen->content_size.height = 256;
}
}
screen->frame = av_frame_alloc();

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

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

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

53
app/src/usb/screen_otg.h Normal file
View File

@@ -0,0 +1,53 @@
#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,6 +5,25 @@
#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();
@@ -25,24 +44,9 @@ sc_file_get_local_path(const char *name) {
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
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);
char *file_path = sc_file_build_path(dir, name);
free(executable_path);
return file_path;
}

View File

@@ -40,6 +40,15 @@ 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
*/

52
app/src/util/rect.c Normal file
View File

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

24
app/src/util/rect.h Normal file
View File

@@ -0,0 +1,24 @@
#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/icon.png "$LINUX_BUILD_DIR/dist/"
cp app/data/scrcpy.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/icon.png "$MACOS_BUILD_DIR/dist/"
cp app/data/scrcpy.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/icon.png "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy.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_PATH="app/data/icon.png" \
SCRCPY_ICON_DIR="app/data" \
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"