diff --git a/app/src/cli.c b/app/src/cli.c index c5230521..07c8fd03 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -621,17 +621,20 @@ static const struct sc_option options[] = { { .longopt_id = OPT_NEW_DISPLAY, .longopt = "new-display", - .argdesc = "[x][/][:r]", + .argdesc = "[x][/][:r[]]", .optional_arg = true, .text = "Create a new display with the specified resolution and " "density. If not provided, they default to the main display " - "dimensions and DPI. Add ':r' to make the display resizable.\n" + "dimensions and DPI. Add ':r' to make the display resizable. " + "Optionally add a resolution factor after ':r' to scale the " + "display size (between 0.1 and 10.0).\n" "Examples:\n" " --new-display=1920x1080\n" " --new-display=1920x1080/420 # force 420 dpi\n" " --new-display=1920x1080/420:r # resizable display\n" - " --new-display=:r # resizable with main display size\n" - " --new-display=/420:r # resizable with main display size and 420 dpi\n" + " --new-display=1920x1080/420:r2.0 # resizable with 2x resolution\n" + " --new-display=:r0.5 # resizable with main display size and half resolution\n" + " --new-display=/420:r1.5 # resizable with main display size, 420 dpi and 1.5x resolution\n" " --new-display # main display size and density\n" " --new-display=/240 # main display size and 240 dpi", }, diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 44bdcb3c..1aa93337 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -803,7 +803,30 @@ aoa_complete: options->window_title ? options->window_title : info->device_name; // Check if new_display is resizable (contains :r) - bool resizable_new_display = options->new_display && strstr(options->new_display, ":r") != NULL; + bool resizable_new_display = false; + float resolution_factor = 1.0f; + + if (options->new_display) { + const char *r_pos = strstr(options->new_display, ":r"); + if (r_pos) { + resizable_new_display = true; + + // Check if there's a resolution factor after ":r" + const char *factor_start = r_pos + 2; // Skip ":r" + if (*factor_start && (*factor_start >= '0' && *factor_start <= '9')) { + // Parse the resolution factor + char *end; + resolution_factor = strtof(factor_start, &end); + if (resolution_factor <= 0.1f) { + // Prevent too small factors + resolution_factor = 0.1f; + } else if (resolution_factor > 10.0f) { + // Prevent too large factors + resolution_factor = 10.0f; + } + } + } + } struct sc_screen_params screen_params = { .video = options->video_playback, @@ -828,6 +851,7 @@ aoa_complete: .fullscreen = options->fullscreen, .start_fps_counter = options->start_fps_counter, .resizable_new_display = resizable_new_display, + .resolution_factor = resolution_factor, }; if (!sc_screen_init(&s->screen, &screen_params)) { diff --git a/app/src/screen.c b/app/src/screen.c index 520dc325..35447a40 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -355,6 +355,9 @@ sc_screen_init(struct sc_screen *screen, screen->req.start_fps_counter = params->start_fps_counter; screen->resizable_new_display = params->resizable_new_display; + screen->resolution_factor = params->resolution_factor; + + LOGI("Resolution factor: %.2f", screen->resolution_factor); bool ok = sc_frame_buffer_init(&screen->fb); if (!ok) { @@ -957,14 +960,30 @@ sc_screen_send_resize_display(struct sc_screen *screen, int width, int height) { LOGD("Display size too small, ignoring resize: %dx%d", width, height); return; } + + // Apply resolution factor if in resizable mode + int adjusted_width = width; + int adjusted_height = height; + + if (screen->resizable_new_display && screen->resolution_factor != 1.0f) { + adjusted_width = (int)(width * screen->resolution_factor); + adjusted_height = (int)(height * screen->resolution_factor); + + // Ensure minimum size + if (adjusted_width < 1) adjusted_width = 1; + if (adjusted_height < 1) adjusted_height = 1; + + LOGD("Applying resolution factor %.2f: %dx%d -> %dx%d", + screen->resolution_factor, width, height, adjusted_width, adjusted_height); + } struct sc_control_msg msg; msg.type = SC_CONTROL_MSG_TYPE_RESIZE_DISPLAY; - msg.resize_display.width = (uint16_t) width; - msg.resize_display.height = (uint16_t) height; + msg.resize_display.width = (uint16_t) adjusted_width; + msg.resize_display.height = (uint16_t) adjusted_height; if (!sc_controller_push_msg(screen->im.controller, &msg)) { - LOGW("Could not request display resize to %dx%d", width, height); + LOGW("Could not request display resize to %dx%d", adjusted_width, adjusted_height); } } @@ -1060,6 +1079,7 @@ static Uint32 resize_timer_callback(Uint32 interval, void *param) { // Round to multiple of 8 to match server-side rounding and avoid quality degradation int rounded_w = (width + 4) & ~7; // Round to nearest multiple of 8 int rounded_h = (height + 4) & ~7; // Round to nearest multiple of 8 + LOGD("[RESIZE_FINISHED] Notifying display resize: %dx%d (rounded from %dx%d)", rounded_w, rounded_h, width, height); sc_screen_send_resize_display(screen, rounded_w, rounded_h); return 0; diff --git a/app/src/screen.h b/app/src/screen.h index 088bb4ab..c9a58309 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -85,6 +85,7 @@ struct sc_screen { AVFrame *resume_frame; bool resizable_new_display; + float resolution_factor; // Factor to multiply window size in resizable mode bool initial_setup; // track if we're in initial window setup phase bool content_driven_resize; // track if we're in content-driven resize (to avoid feedback loop) uint64_t last_resize_time; // timestamp of last resize to implement debouncing @@ -120,6 +121,7 @@ struct sc_screen_params { bool fullscreen; bool start_fps_counter; bool resizable_new_display; + float resolution_factor; // Factor to multiply window size in resizable mode }; // initialize screen, create window, renderer and texture (window is hidden) diff --git a/doc/virtual_display.md b/doc/virtual_display.md index 9f962127..ffb8ef0f 100644 --- a/doc/virtual_display.md +++ b/doc/virtual_display.md @@ -11,6 +11,17 @@ scrcpy --new-display # use the main display size and density scrcpy --new-display=/240 # use the main display size and 240 dpi ``` +You can make the display resizable by adding `:r` to the option. Additionally, you can specify a resolution factor after `:r` to scale the display size: + +```bash +scrcpy --new-display=1920x1080:r # resizable display +scrcpy --new-display=1920x1080:r2.0 # resizable with 2x resolution +scrcpy --new-display=:r0.5 # resizable with half resolution +scrcpy --new-display=/420:r1.5 # resizable with 1.5x resolution +``` + +The resolution factor must be between 0.1 and 10.0. + The new virtual display is destroyed on exit. ## Start app diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index e04340d2..18b8859f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -593,14 +593,34 @@ public class Options { // - "x:r" // - "/:r" // - ":r" + // - "x/:r" + // - "x:r" + // - "/:r" + // - ":r" if (newDisplay.isEmpty()) { return new NewDisplay(); } - // Check for resizable flag - boolean resizable = newDisplay.endsWith(":r"); - if (resizable) { - newDisplay = newDisplay.substring(0, newDisplay.length() - 2); + // Check for resizable flag and resolution factor + boolean resizable = false; + float resolutionFactor = 1.0f; + int rIndex = newDisplay.indexOf(":r"); + if (rIndex >= 0) { + resizable = true; + String factorStr = newDisplay.substring(rIndex + 2); + if (!factorStr.isEmpty()) { + try { + resolutionFactor = Float.parseFloat(factorStr); + if (resolutionFactor <= 0.1f) { + resolutionFactor = 0.1f; + } else if (resolutionFactor > 10.0f) { + resolutionFactor = 10.0f; + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid resolution factor: " + factorStr); + } + } + newDisplay = newDisplay.substring(0, rIndex); } String[] tokens = newDisplay.split("/"); @@ -622,7 +642,7 @@ public class Options { dpi = 0; } - return new NewDisplay(size, dpi, resizable); + return new NewDisplay(size, dpi, resizable, resolutionFactor); } private static Pair parseCaptureOrientation(String value) { diff --git a/server/src/main/java/com/genymobile/scrcpy/device/NewDisplay.java b/server/src/main/java/com/genymobile/scrcpy/device/NewDisplay.java index 34edccb3..7bdfab58 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/NewDisplay.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/NewDisplay.java @@ -4,16 +4,23 @@ public final class NewDisplay { private Size size; private int dpi; private final boolean resizable; + private float resolutionFactor; public NewDisplay() { // Auto size and dpi, not resizable this.resizable = false; + this.resolutionFactor = 1.0f; } public NewDisplay(Size size, int dpi, boolean resizable) { + this(size, dpi, resizable, 1.0f); + } + + public NewDisplay(Size size, int dpi, boolean resizable, float resolutionFactor) { this.size = size; this.dpi = dpi; this.resizable = resizable; + this.resolutionFactor = resolutionFactor; } public Size getSize() { @@ -35,4 +42,12 @@ public final class NewDisplay { public boolean hasExplicitDpi() { return dpi != 0; } + + public float getResolutionFactor() { + return resolutionFactor; + } + + public boolean hasResolutionFactor() { + return resolutionFactor != 1.0f; + } }