diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 3f4c649d..7514e90d 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -80,6 +80,7 @@ _scrcpy() { --record-format= --record-orientation= --render-driver= + --render-fit= --require-audio -s --serial= -S --turn-screen-off @@ -176,6 +177,10 @@ _scrcpy() { COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur")) return ;; + --render-fit) + COMPREPLY=($(compgen -W 'letterbox disabled' -- "$cur")) + return + ;; --shortcut-mod) # Only auto-complete a single key COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur")) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index e106f8a3..40c5f495 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -85,6 +85,7 @@ arguments=( '--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)' '--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)' '--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)' + '--render-fit=[Set the render fit mode]:mode:(letterbox disabled)' '--require-audio=[Make scrcpy fail if audio is enabled but does not work]' {-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))' {-S,--turn-screen-off}'[Turn the device screen off immediately]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index a3437019..48d001af 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -514,6 +514,17 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me +.TP +.BI "\-\-render\-fit " mode +Set the render-fit mode to configure how the rendering fits the window. + +Possible values are "letterbox" and "disabled": + + - "letterbox": preserve the aspect ratio and fit the window as best as possible (black bars are added either at the top and bottom or at the sides if needed). + - "disabled": render the display at the top-left corner, without scaling. + +Default is "letterbox". + .TP .B \-\-require\-audio By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work. diff --git a/app/src/cli.c b/app/src/cli.c index 46a69231..f3631ace 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -105,6 +105,7 @@ enum { OPT_CAMERA_ZOOM, OPT_MIN_SIZE_ALIGNMENT, OPT_NO_WINDOW_ASPECT_RATIO_LOCK, + OPT_RENDER_FIT, }; struct sc_option { @@ -777,6 +778,20 @@ static const struct sc_option options[] = { "\"opengles2\", \"opengles\", \"metal\" and \"software\".\n" "", }, + { + .longopt_id = OPT_RENDER_FIT, + .longopt = "render-fit", + .argdesc = "mode", + .text = "Set the render-fit mode to configure how the rendering fits " + "the window.\n" + "Possible values are \"letterbox\" and \"disabled\".\n" + "\"letterbox\": preserve the aspect ratio and fit the window " + "as best as possible (black bars are added either at the top " + "and bottom or at the sides if needed).\n" + "\"disabled\": render the display at the top-left corner, " + "without scaling.\n" + "Default is \"letterbox\".", + }, { .longopt_id = OPT_REQUIRE_AUDIO, .longopt = "require-audio", @@ -2322,6 +2337,22 @@ parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) { return true; } +static bool +parse_render_fit(const char *optarg, enum sc_render_fit *mode) { + if (!strcmp(optarg, "letterbox")) { + *mode = SC_RENDER_FIT_LETTERBOX; + return true; + } + + if (!strcmp(optarg, "disabled")) { + *mode = SC_RENDER_FIT_DISABLED; + return true; + } + + LOGE("Unsupported render-fit: %s (expected letterbox or disabled)", optarg); + return false; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -2755,6 +2786,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_WINDOW_ASPECT_RATIO_LOCK: opts->window_aspect_ratio_lock = false; break; + case OPT_RENDER_FIT: + if (!parse_render_fit(optarg, &opts->render_fit)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index deff9b6c..77cac25a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -59,6 +59,7 @@ const struct scrcpy_options scrcpy_options_default = { .display_orientation = SC_ORIENTATION_0, .record_orientation = SC_ORIENTATION_0, .display_ime_policy = SC_DISPLAY_IME_POLICY_UNDEFINED, + .render_fit = SC_RENDER_FIT_LETTERBOX, .window_x = SC_WINDOW_POSITION_UNDEFINED, .window_y = SC_WINDOW_POSITION_UNDEFINED, .window_width = 0, diff --git a/app/src/options.h b/app/src/options.h index 752d5870..25c863d7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -220,6 +220,11 @@ enum sc_shortcut_mod { SC_SHORTCUT_MOD_RSUPER = 1 << 5, }; +enum sc_render_fit { + SC_RENDER_FIT_LETTERBOX, + SC_RENDER_FIT_DISABLED, +}; + struct sc_port_range { uint16_t first; uint16_t last; @@ -269,6 +274,7 @@ struct scrcpy_options { enum sc_orientation display_orientation; enum sc_orientation record_orientation; enum sc_display_ime_policy display_ime_policy; + enum sc_render_fit render_fit; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" uint16_t window_width; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d067b372..7af1b27f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -814,6 +814,7 @@ aoa_complete: .window_height = options->window_height, .window_aspect_ratio_lock = options->window_aspect_ratio_lock, .window_borderless = options->window_borderless, + .render_fit = options->render_fit, .orientation = options->display_orientation, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, diff --git a/app/src/screen.c b/app/src/screen.c index 7959b843..174ce166 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -158,7 +158,16 @@ 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) { + bool can_upscale, enum sc_render_fit render_fit, + SDL_FRect *rect) { + if (render_fit == SC_RENDER_FIT_DISABLED) { + rect->x = 0; + rect->y = 0; + rect->w = content_size.width; + rect->h = content_size.height; + return; + } + if (is_optimal_size(render_size, content_size)) { rect->x = 0; rect->y = 0; @@ -202,7 +211,7 @@ 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); + screen->render_fit, &screen->rect); } // render the texture to the renderer @@ -406,6 +415,7 @@ sc_screen_init(struct sc_screen *screen, screen->video = params->video; screen->camera = params->camera; screen->window_aspect_ratio_lock = params->window_aspect_ratio_lock; + screen->render_fit = params->render_fit; screen->req.x = params->window_x; screen->req.y = params->window_y; diff --git a/app/src/screen.h b/app/src/screen.h index d363c682..d8936c85 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -65,6 +65,8 @@ struct sc_screen { SDL_GLContext gl_context; #endif + enum sc_render_fit render_fit; + struct sc_size frame_size; struct sc_size content_size; // rotated frame_size @@ -118,6 +120,7 @@ struct sc_screen_params { bool window_aspect_ratio_lock; bool window_borderless; + enum sc_render_fit render_fit; enum sc_orientation orientation; bool mipmaps;