feat: add resolution factor support for resizable virtual displays

This commit is contained in:
Reynaldo San Juan 2025-09-14 00:08:45 +02:00
parent 3ef31a1f58
commit 62647ad7cd
7 changed files with 108 additions and 13 deletions

View file

@ -621,17 +621,20 @@ static const struct sc_option options[] = {
{
.longopt_id = OPT_NEW_DISPLAY,
.longopt = "new-display",
.argdesc = "[<width>x<height>][/<dpi>][:r]",
.argdesc = "[<width>x<height>][/<dpi>][:r[<factor>]]",
.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",
},

View file

@ -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)) {

View file

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

View file

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

View file

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

View file

@ -593,14 +593,34 @@ public class Options {
// - "<width>x<height>:r"
// - "/<dpi>:r"
// - ":r"
// - "<width>x<height>/<dpi>:r<factor>"
// - "<width>x<height>:r<factor>"
// - "/<dpi>:r<factor>"
// - ":r<factor>"
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<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {

View file

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