This commit is contained in:
San Juan 2026-04-20 11:44:06 +03:00 committed by GitHub
commit 7eb662c564
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 421 additions and 18 deletions

View file

@ -621,14 +621,20 @@ static const struct sc_option options[] = {
{
.longopt_id = OPT_NEW_DISPLAY,
.longopt = "new-display",
.argdesc = "[<width>x<height>][/<dpi>]",
.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.\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=1920x1080/420:r2.0 # resizable with 2x factor\n"
" --new-display=:r0.5 # resizable with main display size and 0.5x factor\n"
" --new-display=/420:r1.5 # resizable with main display size, 420 dpi and 1.5x factor\n"
" --new-display # main display size and density\n"
" --new-display=/240 # main display size and 240 dpi",
},

View file

@ -182,6 +182,10 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
size_t len = write_string_tiny(&buf[1], msg->start_app.name, 255);
return 1 + len;
}
case SC_CONTROL_MSG_TYPE_RESIZE_DISPLAY:
sc_write16be(&buf[1], msg->resize_display.width);
sc_write16be(&buf[3], msg->resize_display.height);
return 5;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
@ -318,6 +322,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
LOG_CMSG("reset video");
break;
case SC_CONTROL_MSG_TYPE_RESIZE_DISPLAY:
LOG_CMSG("resize display to %dx%d", msg->resize_display.width, msg->resize_display.height);
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;

View file

@ -43,6 +43,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
SC_CONTROL_MSG_TYPE_START_APP,
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
SC_CONTROL_MSG_TYPE_RESIZE_DISPLAY,
};
enum sc_copy_key {
@ -111,6 +112,10 @@ struct sc_control_msg {
struct {
char *name;
} start_app;
struct {
uint16_t width;
uint16_t height;
} resize_display;
};
};

View file

@ -541,6 +541,11 @@ sc_input_manager_process_key(struct sc_input_manager *im,
if (shift) {
reset_video(im);
} else {
// Disable MOD+R rotation in resizable virtual display mode
if (im->screen->resizable_new_display) {
LOGI("MOD+R rotation disabled in resizable virtual display mode");
return;
}
rotate_device(im);
}
}

View file

@ -802,6 +802,62 @@ aoa_complete:
const char *window_title =
options->window_title ? options->window_title : info->device_name;
// Check if new_display is resizable (contains :r)
bool resizable_new_display = false;
float resolution_factor = 1.0f;
bool auto_resolution_factor = false;
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;
}
} else {
// No explicit resolution factor provided, will calculate automatically
auto_resolution_factor = true;
LOGI("No resolution factor specified, will calculate automatically");
}
}
}
// If automatic resolution factor calculation was requested, calculate it based on the main display size
if (auto_resolution_factor && resizable_new_display) {
// Get the main display size
SDL_Rect display_bounds;
if (SDL_GetDisplayUsableBounds(0, &display_bounds) == 0) {
// Calculate the resolution factor based on the ratio between the display size and the requested window size
int display_width = display_bounds.w;
int display_height = display_bounds.h;
int window_width = options->window_width ? options->window_width : display_width / 2;
int window_height = options->window_height ? options->window_height : display_height / 2;
float width_factor = (float)display_width / window_width;
float height_factor = (float)display_height / window_height;
resolution_factor = width_factor < height_factor ? width_factor : height_factor;
if (resolution_factor < 0.1f) {
resolution_factor = 0.1f;
} else if (resolution_factor > 10.0f) {
resolution_factor = 10.0f;
}
LOGI("Calculated automatic resolution factor: %.2f (display: %dx%d, window: %dx%d)",
resolution_factor, display_width, display_height, window_width, window_height);
} else {
LOGW("Could not get display bounds, using default resolution factor: 1.0");
resolution_factor = 1.0f;
}
}
struct sc_screen_params screen_params = {
.video = options->video_playback,
.controller = controller,
@ -824,6 +880,8 @@ aoa_complete:
.mipmaps = options->mipmaps,
.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

@ -11,8 +11,18 @@
#define DISPLAY_MARGINS 96
// --- Fix: Ensure these are defined before any use ---
#define RESIZE_FINISHED_DELAY 200
static Uint32 resize_timer_callback(Uint32 interval, void *param);
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
// Prototipo para evitar error de declaración implícita
static bool sizes_are_close(int a, int b, int tolerance);
static void
sc_screen_send_resize_display(struct sc_screen *screen, int width, int height);
static inline struct sc_size
get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
struct sc_size oriented_size;
@ -239,11 +249,9 @@ static int
event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data;
assert(screen->video);
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
// Only handle rendering for the workaround, resize logic should be handled elsewhere
sc_screen_render(screen, true);
}
return 0;
@ -333,6 +341,9 @@ sc_screen_init(struct sc_screen *screen,
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
screen->initial_setup = true;
screen->content_driven_resize = false;
screen->last_resize_time = 0;
screen->video = params->video;
@ -343,6 +354,11 @@ sc_screen_init(struct sc_screen *screen,
screen->req.fullscreen = params->fullscreen;
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) {
return false;
@ -508,6 +524,10 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
SDL_ShowWindow(screen->window);
sc_screen_update_content_rect(screen);
// Mark initial setup as complete - now user resizing will trigger display resize
LOGD("Initial setup complete, enabling user resize detection");
screen->initial_setup = false;
}
void
@ -537,11 +557,20 @@ sc_screen_destroy(struct sc_screen *screen) {
sc_frame_buffer_destroy(&screen->fb);
}
// Utilidad para comparar tamaños con tolerancia
static bool sizes_are_close(int a, int b, int tolerance) {
return abs(a - b) <= tolerance;
}
static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
assert(screen->video);
if (screen->resizable_new_display) {
// En modo redimensionable, el display nunca debe modificar la ventana
// Por lo tanto, ignorar cualquier resize proveniente del display
return;
}
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
@ -550,7 +579,11 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size, true);
// Mark that we're doing a content-driven resize to avoid feedback loop
screen->content_driven_resize = true;
set_window_size(screen, target_size);
screen->content_driven_resize = false;
}
static void
@ -577,8 +610,11 @@ apply_pending_resize(struct sc_screen *screen) {
assert(!screen->maximized);
assert(!screen->minimized);
if (screen->resize_pending) {
// Mark that we're doing a content-driven resize to avoid feedback loop
screen->content_driven_resize = true;
resize_for_content(screen, screen->windowed_content_size,
screen->content_size);
screen->content_driven_resize = false;
screen->resize_pending = false;
}
}
@ -800,6 +836,17 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
bool
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
// --- Manejo especial para SDL_USEREVENT de resize diferido ---
if (event->type == SDL_USEREVENT && event->user.code == 0x5343525A && event->user.data1 == screen) {
// Timer expirado: notificar resize
if (screen->pending_resize_width > 0 && screen->pending_resize_height > 0) {
LOGD("[RESIZE_FINISHED] Notifying display resize: %dx%d", screen->pending_resize_width, screen->pending_resize_height);
sc_screen_send_resize_display(screen, screen->pending_resize_width, screen->pending_resize_height);
}
screen->resize_timer = 0;
return true;
}
// --- Fin manejo especial ---
switch (event->type) {
case SC_EVENT_SCREEN_INIT_SIZE: {
// The initial size is passed via screen->frame_size
@ -835,6 +882,37 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
if (screen->resizable_new_display) {
int w, h;
SDL_GetWindowSize(screen->window, &w, &h);
if ((!sizes_are_close(w, screen->last_window_width, 2) || !sizes_are_close(h, screen->last_window_height, 2)) &&
!screen->initial_setup && !screen->content_driven_resize) {
screen->last_window_width = w;
screen->last_window_height = h;
// --- Resize diferido ---
// Round to multiple of 8 to match server-side rounding and avoid quality degradation
int rounded_w = (w + 4) & ~7; // Round to nearest multiple of 8
int rounded_h = (h + 4) & ~7; // Round to nearest multiple of 8
// Resize window to rounded dimensions to maintain quality
if (rounded_w != w || rounded_h != h) {
SDL_SetWindowSize(screen->window, rounded_w, rounded_h);
LOGD("Window resized to rounded dimensions: %dx%d (from %dx%d)", rounded_w, rounded_h, w, h);
}
screen->pending_resize_width = rounded_w;
screen->pending_resize_height = rounded_h;
if (screen->resize_timer) {
SDL_RemoveTimer(screen->resize_timer);
}
screen->resize_timer = SDL_AddTimer(RESIZE_FINISHED_DELAY, resize_timer_callback, screen);
// --- Fin resize diferido ---
} else if (screen->initial_setup) {
LOGD("[SIZE_CHANGED] Initial setup resize ignored: %dx%d", w, h);
} else if (screen->content_driven_resize) {
LOGD("[SIZE_CHANGED] Content-driven resize ignored: %dx%d", w, h);
}
}
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
@ -871,9 +949,63 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return true;
}
static void
sc_screen_send_resize_display(struct sc_screen *screen, int width, int height) {
if (!screen->im.controller) {
return;
}
// Validate minimum size constraints to avoid Android errors
if (width < 1 || height < 1) {
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;
// Ensure maximum size to avoid encoder issues
// Most Android devices support up to 4K resolution for encoding
const int MAX_DIMENSION = 2048; // Safe limit for most devices
if (adjusted_width > MAX_DIMENSION || adjusted_height > MAX_DIMENSION) {
// Scale down proportionally to fit within limits
float scale = 1.0f;
if (adjusted_width > adjusted_height) {
scale = (float)MAX_DIMENSION / adjusted_width;
} else {
scale = (float)MAX_DIMENSION / adjusted_height;
}
adjusted_width = (int)(adjusted_width * scale);
adjusted_height = (int)(adjusted_height * scale);
LOGW("Limiting dimensions to %dx%d to avoid encoder issues", adjusted_width, adjusted_height);
}
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) 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", adjusted_width, adjusted_height);
}
}
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
int32_t x, int32_t y) {
assert(screen->video);
enum sc_orientation orientation = screen->orientation;
@ -945,3 +1077,26 @@ sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}
// --- Resize diferido ---
static Uint32 resize_timer_callback(Uint32 interval, void *param) {
(void)interval;
struct sc_screen *screen = param;
assert(screen->video);
int width = screen->pending_resize_width;
int height = screen->pending_resize_height;
screen->pending_resize_width = 0;
screen->pending_resize_height = 0;
// Ignorar tamaños inválidos
if (width < 1 || height < 1) {
LOGD("Ignoring invalid resize: %dx%d", width, height);
return 0;
}
// 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

@ -59,7 +59,21 @@ struct sc_screen {
// client orientation
enum sc_orientation orientation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
SDL_Rect rect;
// --- Añadido para evitar bucles y zonas negras en displays redimensionables ---
int last_window_width;
int last_window_height;
int last_display_width;
int last_display_height;
// -----------------------------------------------------------------------------
// --- Añadido para resize diferido ---
int pending_resize_width;
int pending_resize_height;
SDL_TimerID resize_timer;
// -----------------------------------
bool has_frame;
bool fullscreen;
bool maximized;
@ -69,6 +83,12 @@ struct sc_screen {
bool paused;
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
};
struct sc_screen_params {
@ -100,6 +120,8 @@ 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 factor
scrcpy --new-display=:r0.5 # resizable with 0.5x factor
scrcpy --new-display=/420:r1.5 # resizable with 1.5x factor
```
The resolution factor must be between 0.1 and 10.0.
The new virtual display is destroyed on exit.
## Start app

View file

@ -1,5 +1,8 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import android.util.Pair;
import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.audio.AudioSource;
import com.genymobile.scrcpy.device.Device;
@ -14,9 +17,6 @@ import com.genymobile.scrcpy.video.VideoCodec;
import com.genymobile.scrcpy.video.VideoSource;
import com.genymobile.scrcpy.wrappers.WindowManager;
import android.graphics.Rect;
import android.util.Pair;
import java.util.List;
import java.util.Locale;
@ -589,10 +589,26 @@ public class Options {
// - "<width>x<height>/<dpi>"
// - "<width>x<height>"
// - "/<dpi>"
// - "<width>x<height>/<dpi>:r"
// - "<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 and resolution factor
boolean resizable = false;
int rIndex = newDisplay.indexOf(":r");
if (rIndex >= 0) {
resizable = true;
newDisplay = newDisplay.substring(0, rIndex);
}
String[] tokens = newDisplay.split("/");
Size size;
@ -612,7 +628,7 @@ public class Options {
dpi = 0;
}
return new NewDisplay(size, dpi);
return new NewDisplay(size, dpi, resizable);
}
private static Pair<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {

View file

@ -25,12 +25,12 @@ public final class ControlMessage {
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
public static final int TYPE_START_APP = 16;
public static final int TYPE_RESET_VIDEO = 17;
public static final int TYPE_RESIZE_DISPLAY = 18;
public static final long SEQUENCE_INVALID = 0;
public static final int COPY_KEY_NONE = 0;
public static final int COPY_KEY_COPY = 1;
public static final int COPY_KEY_CUT = 2;
private int type;
private String text;
@ -53,6 +53,8 @@ public final class ControlMessage {
private boolean on;
private int vendorId;
private int productId;
private int width;
private int height;
private ControlMessage() {
}
@ -166,6 +168,14 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createResizeDisplay(int width, int height) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_RESIZE_DISPLAY;
msg.width = width;
msg.height = height;
return msg;
}
public int getType() {
return type;
}
@ -249,4 +259,12 @@ public final class ControlMessage {
public int getProductId() {
return productId;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}

View file

@ -48,6 +48,8 @@ public class ControlMessageReader {
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
case ControlMessage.TYPE_RESET_VIDEO:
return ControlMessage.createEmpty(type);
case ControlMessage.TYPE_RESIZE_DISPLAY:
return parseResizeDisplay();
case ControlMessage.TYPE_UHID_CREATE:
return parseUhidCreate();
case ControlMessage.TYPE_UHID_INPUT:
@ -166,6 +168,12 @@ public class ControlMessageReader {
return ControlMessage.createStartApp(name);
}
private ControlMessage parseResizeDisplay() throws IOException {
int width = dis.readUnsignedShort();
int height = dis.readUnsignedShort();
return ControlMessage.createResizeDisplay(width, height);
}
private Position parsePosition() throws IOException {
int x = dis.readInt();
int y = dis.readInt();

View file

@ -12,6 +12,7 @@ import com.genymobile.scrcpy.device.Position;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.video.NewDisplayCapture;
import com.genymobile.scrcpy.video.SurfaceCapture;
import com.genymobile.scrcpy.video.VirtualDisplayListener;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
@ -312,6 +313,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
break;
case ControlMessage.TYPE_ROTATE_DEVICE:
Device.rotateDevice(getActionDisplayId());
// The virtual display resize will be handled by the main display monitor
break;
case ControlMessage.TYPE_UHID_CREATE:
getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData());
@ -331,6 +333,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
case ControlMessage.TYPE_RESET_VIDEO:
resetVideo();
break;
case ControlMessage.TYPE_RESIZE_DISPLAY:
resizeDisplay(msg.getWidth(), msg.getHeight());
break;
default:
// do nothing
}
@ -754,4 +759,18 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
surfaceCapture.requestInvalidate();
}
}
private void resizeDisplay(int width, int height) {
if (surfaceCapture instanceof NewDisplayCapture) {
NewDisplayCapture newDisplayCapture = (NewDisplayCapture) surfaceCapture;
if (newDisplayCapture.isResizable()) {
Ln.i("Resizing display to " + width + "x" + height);
newDisplayCapture.resizeDisplay(width, height);
} else {
Ln.w("Display is not resizable");
}
} else {
Ln.w("Resize display not supported for current capture type");
}
}
}

View file

@ -3,14 +3,17 @@ package com.genymobile.scrcpy.device;
public final class NewDisplay {
private Size size;
private int dpi;
private final boolean resizable;
public NewDisplay() {
// Auto size and dpi
// Auto size and dpi, not resizable
this.resizable = false;
}
public NewDisplay(Size size, int dpi) {
public NewDisplay(Size size, int dpi, boolean resizable) {
this.size = size;
this.dpi = dpi;
this.resizable = resizable;
}
public Size getSize() {
@ -21,6 +24,10 @@ public final class NewDisplay {
return dpi;
}
public boolean isResizable() {
return resizable;
}
public boolean hasExplicitSize() {
return size != null;
}
@ -28,4 +35,5 @@ public final class NewDisplay {
public boolean hasExplicitDpi() {
return dpi != 0;
}
}

View file

@ -50,7 +50,7 @@ public class NewDisplayCapture extends SurfaceCapture {
private Size mainDisplaySize;
private int mainDisplayDpi;
private int maxSize;
private int displayImePolicy;
private final int displayImePolicy;
private final Rect crop;
private final boolean captureOrientationLocked;
private final Orientation captureOrientation;
@ -64,6 +64,7 @@ public class NewDisplayCapture extends SurfaceCapture {
private Size physicalSize; // the physical size of the display (without rotation)
private int dpi;
private final boolean resizable;
public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) {
this.vdListener = vdListener;
@ -79,6 +80,7 @@ public class NewDisplayCapture extends SurfaceCapture {
this.angle = options.getAngle();
this.vdDestroyContent = options.getVDDestroyContent();
this.vdSystemDecorations = options.getVDSystemDecorations();
this.resizable = newDisplay.isResizable();
}
@Override
@ -199,7 +201,11 @@ public class NewDisplayCapture extends SurfaceCapture {
ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy);
}
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
// Only start display size monitoring for non-resizable displays
// For resizable displays, we control the resizing manually from the client
if (!resizable) {
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
}
} catch (Exception e) {
Ln.e("Could not create display", e);
throw new AssertionError("Could not create display");
@ -266,4 +272,53 @@ public class NewDisplayCapture extends SurfaceCapture {
public void requestInvalidate() {
invalidate();
}
public void resizeDisplay(int newWidth, int newHeight) {
if (!resizable || virtualDisplay == null) {
return;
}
// Validate minimum size constraints
if (newWidth < 1 || newHeight < 1) {
Ln.w("Display size too small, ignoring resize: " + newWidth + "x" + newHeight);
return;
}
try {
// Calculate new DPI based on the size change
int newDpi = dpi;
if (displaySize != null) {
// Scale DPI proportionally to maintain similar pixel density
int oldMax = Math.max(displaySize.getWidth(), displaySize.getHeight());
int newMax = Math.max(newWidth, newHeight);
newDpi = (dpi * newMax) / oldMax;
}
// Ensure DPI is within valid range (Android requires DPI >= 1)
newDpi = Math.max(1, newDpi);
// Round to multiple of 8 to avoid quality degradation from encoding constraints
Size roundedSize = new Size(newWidth, newHeight).round8();
int roundedWidth = roundedSize.getWidth();
int roundedHeight = roundedSize.getHeight();
// Resize the virtual display with rounded dimensions
virtualDisplay.resize(roundedWidth, roundedHeight, newDpi);
// Update our internal state
displaySize = new Size(roundedWidth, roundedHeight);
dpi = newDpi;
Ln.i("Resized display to: " + roundedWidth + "x" + roundedHeight + "/" + newDpi + " (rounded from " + newWidth + "x" + newHeight + ")");
// Trigger a reconfiguration
invalidate();
} catch (Exception e) {
Ln.e("Could not resize display", e);
}
}
public boolean isResizable() {
return resizable;
}
}

View file

@ -199,7 +199,17 @@ public class SurfaceEncoder implements AsyncProcessor {
boolean eos;
do {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
int outputBufferId;
try {
// Use a finite timeout to handle MediaCodec reset scenarios gracefully
outputBufferId = codec.dequeueOutputBuffer(bufferInfo, 10000); // 10ms timeout
} catch (IllegalStateException e) {
// This can happen when MediaCodec is being reset (e.g., during display resize)
// The pending dequeue request gets cancelled
Ln.d("MediaCodec dequeue interrupted during reset: " + e.getMessage());
break; // Exit the loop gracefully
}
try {
eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
// On EOS, there might be data or not, depending on bufferInfo.size