mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-04-21 01:33:36 +00:00
Merge e568d07c3b into 4671927c34
This commit is contained in:
commit
7eb662c564
15 changed files with 421 additions and 18 deletions
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
165
app/src/screen.c
165
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,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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