mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-04-21 01:33:36 +00:00
[#663] feat: resizable virtual display
This commit is contained in:
parent
f663bbec12
commit
526e4a81ed
13 changed files with 323 additions and 13 deletions
|
|
@ -621,14 +621,17 @@ 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]",
|
||||
.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.\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 # main display size and density\n"
|
||||
" --new-display=/240 # main display size and 240 dpi",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -802,6 +802,9 @@ 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 = options->new_display && strstr(options->new_display, ":r") != NULL;
|
||||
|
||||
struct sc_screen_params screen_params = {
|
||||
.video = options->video_playback,
|
||||
.controller = controller,
|
||||
|
|
@ -824,6 +827,7 @@ aoa_complete:
|
|||
.mipmaps = options->mipmaps,
|
||||
.fullscreen = options->fullscreen,
|
||||
.start_fps_counter = options->start_fps_counter,
|
||||
.resizable_new_display = resizable_new_display,
|
||||
};
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
|
|
|
|||
155
app/src/screen.c
155
app/src/screen.c
|
|
@ -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,12 +249,47 @@ 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.
|
||||
sc_screen_render(screen, true);
|
||||
int width = event->window.data1;
|
||||
int height = event->window.data2;
|
||||
if (screen->resizable_new_display) {
|
||||
if ((!sizes_are_close(width, screen->last_window_width, 2) || !sizes_are_close(height, screen->last_window_height, 2)) &&
|
||||
!screen->initial_setup && !screen->content_driven_resize) {
|
||||
screen->last_window_width = width;
|
||||
screen->last_window_height = height;
|
||||
// --- Resize diferido ---
|
||||
screen->pending_resize_width = width;
|
||||
screen->pending_resize_height = height;
|
||||
if (screen->resize_timer) {
|
||||
SDL_RemoveTimer(screen->resize_timer);
|
||||
}
|
||||
screen->resize_timer = SDL_AddTimer(RESIZE_FINISHED_DELAY, resize_timer_callback, screen);
|
||||
// --- Fin resize diferido ---
|
||||
sc_screen_render(screen, true);
|
||||
} else if (screen->initial_setup) {
|
||||
LOGD("[RESIZED] Initial setup resize ignored: %dx%d", width, height);
|
||||
sc_screen_render(screen, true);
|
||||
} else if (screen->content_driven_resize) {
|
||||
LOGD("[RESIZED] Content-driven resize ignored: %dx%d", width, height);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
} else {
|
||||
// Modo normal
|
||||
if (screen->content_driven_resize) {
|
||||
LOGD("[RESIZED] Content-driven resize, solo render");
|
||||
sc_screen_render(screen, true);
|
||||
return 0;
|
||||
}
|
||||
if (screen->initial_setup) {
|
||||
LOGD("[RESIZED] Initial setup, solo render");
|
||||
sc_screen_render(screen, true);
|
||||
return 0;
|
||||
}
|
||||
LOGD("[RESIZED] User resize detected: %dx%d", width, height);
|
||||
sc_screen_render(screen, true);
|
||||
sc_screen_send_resize_display(screen, width, height);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -333,6 +378,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 +391,8 @@ 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;
|
||||
|
||||
bool ok = sc_frame_buffer_init(&screen->fb);
|
||||
if (!ok) {
|
||||
return false;
|
||||
|
|
@ -508,6 +558,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 +591,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 +613,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 +644,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 +870,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 +916,27 @@ 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 ---
|
||||
screen->pending_resize_width = w;
|
||||
screen->pending_resize_height = 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 +973,31 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!sc_controller_push_msg(screen->im.controller, &msg)) {
|
||||
LOGW("Could not request display resize to %dx%d", width, 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 +1069,22 @@ 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;
|
||||
}
|
||||
LOGD("[RESIZE_FINISHED] Notifying display resize: %dx%d", width, height);
|
||||
sc_screen_send_resize_display(screen, width, height);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,11 @@ struct sc_screen {
|
|||
|
||||
bool paused;
|
||||
AVFrame *resume_frame;
|
||||
|
||||
bool resizable_new_display;
|
||||
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 +119,7 @@ struct sc_screen_params {
|
|||
|
||||
bool fullscreen;
|
||||
bool start_fps_counter;
|
||||
bool resizable_new_display;
|
||||
};
|
||||
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
|
|
|
|||
|
|
@ -589,10 +589,20 @@ public class Options {
|
|||
// - "<width>x<height>/<dpi>"
|
||||
// - "<width>x<height>"
|
||||
// - "/<dpi>"
|
||||
// - "<width>x<height>/<dpi>:r"
|
||||
// - "<width>x<height>:r"
|
||||
// - "/<dpi>: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);
|
||||
}
|
||||
|
||||
String[] tokens = newDisplay.split("/");
|
||||
|
||||
Size size;
|
||||
|
|
@ -612,7 +622,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) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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;
|
||||
|
||||
|
|
@ -53,6 +54,8 @@ public final class ControlMessage {
|
|||
private boolean on;
|
||||
private int vendorId;
|
||||
private int productId;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private ControlMessage() {
|
||||
}
|
||||
|
|
@ -166,6 +169,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 +260,12 @@ public final class ControlMessage {
|
|||
public int getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -331,6 +332,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 +758,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,23 @@ package com.genymobile.scrcpy.device;
|
|||
public final class NewDisplay {
|
||||
private Size size;
|
||||
private int dpi;
|
||||
private boolean resizable;
|
||||
|
||||
public NewDisplay() {
|
||||
// Auto size and dpi
|
||||
// Auto size and dpi, not resizable
|
||||
this.resizable = false;
|
||||
}
|
||||
|
||||
public NewDisplay(Size size, int dpi) {
|
||||
this.size = size;
|
||||
this.dpi = dpi;
|
||||
this.resizable = false;
|
||||
}
|
||||
|
||||
public NewDisplay(Size size, int dpi, boolean resizable) {
|
||||
this.size = size;
|
||||
this.dpi = dpi;
|
||||
this.resizable = resizable;
|
||||
}
|
||||
|
||||
public Size getSize() {
|
||||
|
|
@ -21,6 +30,10 @@ public final class NewDisplay {
|
|||
return dpi;
|
||||
}
|
||||
|
||||
public boolean isResizable() {
|
||||
return resizable;
|
||||
}
|
||||
|
||||
public boolean hasExplicitSize() {
|
||||
return size != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
private Size physicalSize; // the physical size of the display (without rotation)
|
||||
|
||||
private int dpi;
|
||||
private boolean resizable;
|
||||
|
||||
public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) {
|
||||
this.vdListener = vdListener;
|
||||
|
|
@ -78,6 +79,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
this.angle = options.getAngle();
|
||||
this.vdDestroyContent = options.getVDDestroyContent();
|
||||
this.vdSystemDecorations = options.getVDSystemDecorations();
|
||||
this.resizable = newDisplay.isResizable();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -197,7 +199,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");
|
||||
|
|
@ -264,4 +270,48 @@ 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);
|
||||
|
||||
// Resize the virtual display
|
||||
virtualDisplay.resize(newWidth, newHeight, newDpi);
|
||||
|
||||
// Update our internal state
|
||||
displaySize = new Size(newWidth, newHeight);
|
||||
dpi = newDpi;
|
||||
|
||||
Ln.i("Resized display to: " + newWidth + "x" + newHeight + "/" + newDpi);
|
||||
|
||||
// Trigger a reconfiguration
|
||||
invalidate();
|
||||
} catch (Exception e) {
|
||||
Ln.e("Could not resize display", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isResizable() {
|
||||
return resizable;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue